全部产品

自定义 UI 下使用扫码功能

更新时间:2020-09-16 17:40:34

本文将引导您绘制自定义 UI 界面并将自定义 UI 扫码的能力添加到工程中。
该过程主要分为以下四个步骤:

  1. 创建依赖工程
  2. 在依赖工程中创建定义 UI 界面
  3. 在依赖工程中使用扫码功能
  4. 在主工程中调用自定义 UI 下的扫码功能

操作步骤

创建依赖工程

  1. 点击 File > New > New Module
    1
  2. 选择 Android Library,点击 Next
    2
  3. 输入 Moudle name,点击 Finish
    3

在依赖工程中创建定义 UI 界面

  1. 在 custom 的 com.example.custom 包中创建 widget 包。在 widget 包中添加 APSurfaceTexture 类,让其继承 SurfaceTexture 类,以获取图像流。

    1. public class APSurfaceTexture extends SurfaceTexture {
    2. private static final String TAG = "APSurfaceTexture";
    3. public SurfaceTexture mSurface;
    4. public APSurfaceTexture() {
    5. super(0);
    6. }
    7. @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    8. @Override
    9. public void attachToGLContext(int texName) {
    10. mSurface.attachToGLContext(texName);
    11. }
    12. @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    13. @Override
    14. public void detachFromGLContext() {
    15. try {
    16. mSurface.detachFromGLContext();
    17. } catch (Exception ex) {
    18. try {
    19. Method nativeMethod = SurfaceTexture.class.getDeclaredMethod("nativeDetachFromGLContext");
    20. nativeMethod.setAccessible(true);
    21. int retCode = (Integer) nativeMethod.invoke(mSurface);
    22. LoggerFactory.getTraceLogger().debug(TAG, "nativeDetachFromGLContext invoke retCode:" + retCode);
    23. } catch (Exception e) {
    24. LoggerFactory.getTraceLogger().error(TAG, "nativeDetachFromGLContext invoke exception:" + e.getMessage());
    25. }
    26. LoggerFactory.getTraceLogger().error(TAG, "mSurface.detachFromGLContext() exception:" + ex.getMessage());
    27. }
    28. }
    29. @Override
    30. public boolean equals(Object o) {
    31. return mSurface.equals(o);
    32. }
    33. @Override
    34. public long getTimestamp() {
    35. return mSurface.getTimestamp();
    36. }
    37. @Override
    38. public void getTransformMatrix(float[] mtx) {
    39. mSurface.getTransformMatrix(mtx);
    40. }
    41. @Override
    42. public void release() {
    43. super.release();
    44. mSurface.release();
    45. }
    46. @Override
    47. public int hashCode() {
    48. return mSurface.hashCode();
    49. }
    50. @TargetApi(Build.VERSION_CODES.KITKAT)
    51. @Override
    52. public void releaseTexImage() {
    53. mSurface.releaseTexImage();
    54. }
    55. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
    56. @Override
    57. public void setDefaultBufferSize(int width, int height) {
    58. mSurface.setDefaultBufferSize(width, height);
    59. }
    60. @Override
    61. public void setOnFrameAvailableListener(OnFrameAvailableListener listener) {
    62. mSurface.setOnFrameAvailableListener(listener);
    63. }
    64. @Override
    65. public String toString() {
    66. return mSurface.toString();
    67. }
    68. @Override
    69. public void updateTexImage() {
    70. mSurface.updateTexImage();
    71. }
    72. }
  2. 在 custom 的 widget 包中添加 APTextureView 类,让其继承 TextureView 类,实现图像流的显示。

    1. public class APTextureView extends TextureView {
    2. private static final String TAG = "APTextureView";
    3. private Field mSurfaceField;
    4. public APTextureView(Context context) {
    5. super(context);
    6. }
    7. public APTextureView(Context context, AttributeSet attrs) {
    8. super(context, attrs);
    9. }
    10. public APTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
    11. super(context, attrs, defStyleAttr);
    12. }
    13. @Override
    14. protected void onDetachedFromWindow() {
    15. try {
    16. super.onDetachedFromWindow();
    17. } catch (Exception ex) {
    18. LoggerFactory.getTraceLogger().error(TAG, "onDetachedFromWindow exception:" + ex.getMessage());
    19. }
    20. }
    21. @Override
    22. public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
    23. super.setSurfaceTexture(surfaceTexture);
    24. afterSetSurfaceTexture();
    25. }
    26. private void afterSetSurfaceTexture() {
    27. LoggerFactory.getTraceLogger().debug(TAG, "afterSetSurfaceTexture Build.VERSION.SDK_INT:" + Build.VERSION.SDK_INT);
    28. if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 20) {
    29. return;
    30. }
    31. try {
    32. if (mSurfaceField == null) {
    33. mSurfaceField = TextureView.class.getDeclaredField("mSurface");
    34. mSurfaceField.setAccessible(true);
    35. }
    36. SurfaceTexture innerSurface = (SurfaceTexture) mSurfaceField.get(this);
    37. if (innerSurface != null) {
    38. if (!(innerSurface instanceof APSurfaceTexture)) {
    39. APSurfaceTexture wrapSurface = new APSurfaceTexture();
    40. wrapSurface.mSurface = innerSurface;
    41. mSurfaceField.set(this, wrapSurface);
    42. LoggerFactory.getTraceLogger().debug(TAG, "afterSetSurfaceTexture wrap mSurface");
    43. }
    44. }
    45. } catch (Exception ex) {
    46. LoggerFactory.getTraceLogger().error(TAG, "afterSetSurfaceTexture exception:" + ex.getMessage());
    47. }
    48. }
    49. }
  3. com.example.custom 包中创建 Utils 类,实现图片的转换。

    1. public class Utils {
    2. private static String TAG = "Utils";
    3. public static void toast(Context context, String msg) {
    4. Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
    5. }
    6. public static Bitmap changeBitmapColor(Bitmap bitmap, int color) {
    7. int bitmap_w = bitmap.getWidth();
    8. int bitmap_h = bitmap.getHeight();
    9. int[] arrayColor = new int[bitmap_w * bitmap_h];
    10. int count = 0;
    11. for (int i = 0; i < bitmap_h; i++) {
    12. for (int j = 0; j < bitmap_w; j++) {
    13. int originColor = bitmap.getPixel(j, i);
    14. // 非透明区域
    15. if (originColor != 0) {
    16. originColor = color;
    17. }
    18. arrayColor[count] = originColor;
    19. count++;
    20. }
    21. }
    22. return Bitmap.createBitmap(arrayColor, bitmap_w, bitmap_h, Bitmap.Config.ARGB_8888);
    23. }
    24. public static Bitmap uri2Bitmap(Context context, Uri uri) {
    25. Bitmap bitmap = null;
    26. InputStream in;
    27. try {
    28. in = context.getContentResolver().openInputStream(uri);
    29. if (in != null) {
    30. bitmap = BitmapFactory.decodeStream(in);
    31. in.close();
    32. }
    33. } catch (Exception e) {
    34. LoggerFactory.getTraceLogger().error(TAG, "uri2Bitmap: Exception " + e.getMessage());
    35. }
    36. return bitmap;
    37. }
    38. }
  4. 右键点击 custom > New > Directory
    4

  5. 在弹出框中输入 res,创建 res 文件夹。
    5
  6. 右键点击 custom 的 res > New > Directory
    6
  7. 在弹出框中输入 values,创建 values 文件夹。
    7
  8. 右键点击 custom 的 values > New > File
    8
  9. 在弹出框中输入 attrs.xml,创建 attrs.xml 文件。
    9
  10. 在 attrs.xml 文件中添加如下代码。
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <resources>
    3. <declare-styleable name="scan">
    4. <attr name="shadowColor" format="color" />
    5. </declare-styleable>
    6. </resources>
  11. 右键点击 custom 的 res > New > Directory
    10
  12. 在弹出框中输入 drawable,创建 drawable 文件夹。
    11
  13. 向 drawable 文件夹中粘贴如下 资源文件
    12
  14. 在 custom 的 widget 包中添加 FinderView 类,让其继承 View 类,并添加如下代码。实现扫码窗口、边角及周边阴影的绘制功能。

    1. public class FinderView extends View {
    2. private static final int DEFAULT_SHADOW_COLOR = 0x96000000;
    3. private int scanWindowLeft, scanWindowTop, scanWindowRight, scanWindowBottom;
    4. private Bitmap leftTopCorner, rightTopCorner, leftBottomCorner, rightBottomCorner;
    5. private Paint paint;
    6. private int shadowColor;
    7. public FinderView(Context context, AttributeSet attrs, int defStyle) {
    8. super(context, attrs, defStyle);
    9. init(context, attrs);
    10. }
    11. public FinderView(Context context, AttributeSet attrs) {
    12. super(context, attrs);
    13. init(context, attrs);
    14. }
    15. private void init(Context context, AttributeSet attrs) {
    16. applyConfig(context, attrs);
    17. setVisibility(INVISIBLE);
    18. initCornerBitmap(context);
    19. paint = new Paint();
    20. paint.setAntiAlias(true);
    21. }
    22. private void applyConfig(Context context, AttributeSet attrs) {
    23. if (attrs != null) {
    24. TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.scan);
    25. shadowColor = typedArray.getColor(R.styleable.scan_shadowColor, DEFAULT_SHADOW_COLOR);
    26. typedArray.recycle();
    27. }
    28. }
    29. //初始化扫码窗口边角样式
    30. private void initCornerBitmap(Context context) {
    31. Resources res = context.getResources();
    32. leftTopCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_left_top);
    33. rightTopCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_right_top);
    34. leftBottomCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_left_bottom);
    35. rightBottomCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_right_bottom);
    36. }
    37. @Override
    38. public void draw(Canvas canvas) {
    39. super.draw(canvas);
    40. drawShadow(canvas);
    41. drawCorner(canvas);
    42. }
    43. //绘制扫码窗口边角样式
    44. private void drawCorner(Canvas canvas) {
    45. paint.setAlpha(255);
    46. canvas.drawBitmap(leftTopCorner, scanWindowLeft, scanWindowTop, paint);
    47. canvas.drawBitmap(rightTopCorner, scanWindowRight - rightTopCorner.getWidth(), scanWindowTop, paint);
    48. canvas.drawBitmap(leftBottomCorner, scanWindowLeft, scanWindowBottom - leftBottomCorner.getHeight(), paint);
    49. canvas.drawBitmap(rightBottomCorner, scanWindowRight - rightBottomCorner.getWidth(), scanWindowBottom - rightBottomCorner.getHeight(), paint);
    50. }
    51. //绘制扫码周边阴影
    52. private void drawShadow(Canvas canvas) {
    53. paint.setColor(shadowColor);
    54. canvas.drawRect(0, 0, getWidth(), scanWindowTop, paint);
    55. canvas.drawRect(0, scanWindowTop, scanWindowLeft, scanWindowBottom, paint);
    56. canvas.drawRect(scanWindowRight, scanWindowTop, getWidth(), scanWindowBottom, paint);
    57. canvas.drawRect(0, scanWindowBottom, getWidth(), getHeight(), paint);
    58. }
    59. /**
    60. * 根据 RayView 的位置决定扫码窗口的位置
    61. */
    62. public void setScanWindowLocation(int left, int top, int right, int bottom) {
    63. scanWindowLeft = left;
    64. scanWindowTop = top;
    65. scanWindowRight = right;
    66. scanWindowBottom = bottom;
    67. invalidate();
    68. setVisibility(VISIBLE);
    69. }
    70. public void setShadowColor(int shadowColor) {
    71. this.shadowColor = shadowColor;
    72. }
    73. //设置扫码窗口边角颜色
    74. public void setCornerColor(int angleColor) {
    75. leftTopCorner = Utils.changeBitmapColor(leftTopCorner, angleColor);
    76. rightTopCorner = Utils.changeBitmapColor(rightTopCorner, angleColor);
    77. leftBottomCorner = Utils.changeBitmapColor(leftBottomCorner, angleColor);
    78. rightBottomCorner = Utils.changeBitmapColor(rightBottomCorner, angleColor);
    79. }
    80. }
  15. 在 custom 的 widget 包中添加 RayView 类,让其继承 ImageView 类,并添加如下代码。实现扫描射线的绘制功能。

    1. public class RayView extends ImageView {
    2. private FinderView mFinderView;
    3. private ScaleAnimation scanAnimation;
    4. private int[] location = new int[2];
    5. public RayView(Context context, AttributeSet attrs) {
    6. super(context, attrs);
    7. }
    8. public RayView(Context context) {
    9. super(context);
    10. }
    11. @Override
    12. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    13. super.onLayout(changed, left, top, right, bottom);
    14. // 设置 FinderView 中扫码窗口的位置
    15. getLocationOnScreen(location);
    16. if (mFinderView != null) {
    17. mFinderView.setScanWindowLocation(location[0], location[1], location[0] + getWidth(), location[1] + getHeight());
    18. }
    19. }
    20. public void startScanAnimation() {
    21. setVisibility(VISIBLE);
    22. if (scanAnimation == null) {
    23. scanAnimation = new ScaleAnimation(1.0f, 1.0f, 0.0f, 1.0f);
    24. scanAnimation.setDuration(3000L);
    25. scanAnimation.setFillAfter(true);
    26. scanAnimation.setRepeatCount(Animation.INFINITE);
    27. scanAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
    28. }
    29. startAnimation(scanAnimation);
    30. }
    31. public void stopScanAnimation() {
    32. setVisibility(INVISIBLE);
    33. if (scanAnimation != null) {
    34. this.clearAnimation();
    35. scanAnimation = null;
    36. }
    37. }
    38. public void setFinderView(FinderView FinderView) {
    39. mFinderView = FinderView;
    40. }
    41. }
  16. 右键点击 custom 的 res > New > Directory
    13

  17. 在弹出框中输入 layout,创建 layout 文件夹。
    14
  18. 右键点击 custom 的 layout > New > File
    15
  19. 在弹出框中输入 view_scan.xml,创建 view_scan.xml 文件。
    16
  20. view_scan.xml 文件中添加如下代码,绘制扫描页面的布局界面。

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <merge xmlns:android="http://schemas.android.com/apk/res/android">
    3. <com.example.custom.widget.FinderView
    4. android:id="@+id/finder_view"
    5. android:layout_width="match_parent"
    6. android:layout_height="match_parent" />
    7. <LinearLayout
    8. android:layout_width="match_parent"
    9. android:layout_height="wrap_content"
    10. android:layout_marginTop="20dp"
    11. android:gravity="center_vertical"
    12. android:orientation="horizontal">
    13. <ImageView
    14. android:id="@+id/back"
    15. android:layout_width="48dp"
    16. android:layout_height="48dp"
    17. android:scaleType="center"
    18. android:src="@drawable/icon_back" />
    19. <TextView
    20. android:layout_width="0dp"
    21. android:layout_height="wrap_content"
    22. android:layout_weight="1"
    23. android:gravity="center"
    24. android:text="@string/custom_title"
    25. android:textColor="#ffffff"
    26. android:textSize="16sp" />
    27. <ImageView
    28. android:id="@+id/gallery"
    29. android:layout_width="34dp"
    30. android:layout_height="34dp"
    31. android:layout_marginEnd="10dp"
    32. android:layout_marginRight="10dp"
    33. android:scaleType="fitXY"
    34. android:src="@drawable/selector_scan_from_gallery" />
    35. <ImageView
    36. android:id="@+id/torch"
    37. android:layout_width="34dp"
    38. android:layout_height="34dp"
    39. android:layout_marginEnd="10dp"
    40. android:layout_marginRight="10dp"
    41. android:scaleType="fitXY"
    42. android:src="@drawable/selector_torch" />
    43. </LinearLayout>
    44. <com.example.custom.widget.RayView
    45. android:id="@+id/ray_view"
    46. android:layout_width="270dp"
    47. android:layout_height="280dp"
    48. android:layout_centerInParent="true"
    49. android:background="@drawable/custom_scan_ray" />
    50. <TextView
    51. android:id="@+id/tip_tv"
    52. android:layout_width="wrap_content"
    53. android:layout_height="wrap_content"
    54. android:layout_below="@+id/ray_view"
    55. android:layout_centerHorizontal="true"
    56. android:layout_marginTop="10dp"
    57. android:includeFontPadding="false"
    58. android:text="@string/scan_tip"
    59. android:textColor="#7fffffff"
    60. android:textSize="14sp" />
    61. </merge>
  21. 在 widget 包中添加 ScanView 类,让其继承 RelativeLayout 类,并添加如下代码。实现扫码相关的 View 与扫码引擎的交互功能。

    1. public class ScanView extends RelativeLayout {
    2. private RayView mRayView;
    3. public ScanView(Context context) {
    4. super(context);
    5. init(context);
    6. }
    7. public ScanView(Context context, AttributeSet attrs) {
    8. super(context, attrs);
    9. init(context);
    10. }
    11. public ScanView(Context context, AttributeSet attrs, int defStyle) {
    12. super(context, attrs, defStyle);
    13. init(context);
    14. }
    15. private void init(Context ctx) {
    16. LayoutInflater.from(ctx).inflate(R.layout.view_scan, this, true);
    17. FinderView finderView = (FinderView) findViewById(R.id.finder_view);
    18. mRayView = (RayView) findViewById(R.id.ray_view);
    19. mRayView.setFinderView(finderView);
    20. }
    21. public void onStartScan() {
    22. mRayView.startScanAnimation();
    23. }
    24. public void onStopScan() {
    25. mRayView.stopScanAnimation();
    26. }
    27. public float getCropWidth() {
    28. return mRayView.getWidth() * 1.1f;
    29. }
    30. public Rect getScanRect(Camera camera, int previewWidth, int previewHeight) {
    31. if (camera == null) {
    32. return null;
    33. }
    34. int[] location = new int[2];
    35. mRayView.getLocationOnScreen(location);
    36. Rect r = new Rect(location[0], location[1],
    37. location[0] + mRayView.getWidth(), location[1] + mRayView.getHeight());
    38. Camera.Size size;
    39. try {
    40. size = camera.getParameters().getPreviewSize();
    41. } catch (Exception e) {
    42. return null;
    43. }
    44. if (size == null) {
    45. return null;
    46. }
    47. double rateX = (double) size.height / (double) previewWidth;
    48. double rateY = (double) size.width / (double) previewHeight;
    49. // 裁剪框大小 = 网格动画框大小*1.1
    50. int expandX = (int) (mRayView.getWidth() * 0.05);
    51. int expandY = (int) (mRayView.getHeight() * 0.05);
    52. Rect resRect = new Rect(
    53. (int) ((r.top - expandY) * rateY),
    54. (int) ((r.left - expandX) * rateX),
    55. (int) ((r.bottom + expandY) * rateY),
    56. (int) ((r.right + expandX) * rateX));
    57. Rect finalRect = new Rect(
    58. resRect.left < 0 ? 0 : resRect.left,
    59. resRect.top < 0 ? 0 : resRect.top,
    60. resRect.width() > size.width ? size.width : resRect.width(),
    61. resRect.height() > size.height ? size.height : resRect.height());
    62. Rect rect1 = new Rect(
    63. finalRect.left / 4 * 4,
    64. finalRect.top / 4 * 4,
    65. finalRect.right / 4 * 4,
    66. finalRect.bottom / 4 * 4);
    67. int max = Math.max(rect1.right, rect1.bottom);
    68. int diff = Math.abs(rect1.right - rect1.bottom) / 8 * 4;
    69. Rect rect2;
    70. if (rect1.right > rect1.bottom) {
    71. rect2 = new Rect(rect1.left, rect1.top - diff, max, max);
    72. } else {
    73. rect2 = new Rect(rect1.left - diff, rect1.top, max, max);
    74. }
    75. return rect2;
    76. }
    77. }
  22. 右键点击 custom 的 layout > New > File
    17
  23. 在弹出框中输入 activity_custom_scan.xml,创建 activity_custom_scan.xml 文件。
    18
  24. activity_custom_scan.xml 文件中添加如下代码。绘制自定义扫码功能的主界面。

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:layout_width="match_parent"
    4. android:layout_height="match_parent">
    5. <com.mpaas.aar.demo.custom.widget.APTextureView
    6. android:id="@+id/surface_view"
    7. android:layout_width="match_parent"
    8. android:layout_height="match_parent" />
    9. <com.mpaas.aar.demo.custom.widget.ScanView
    10. android:id="@+id/scan_view"
    11. android:layout_width="match_parent"
    12. android:layout_height="match_parent" />
    13. </FrameLayout>

在依赖工程中使用扫码功能

  1. 在 custom 的 com.example.custom 包中添加 ScanHelper 类,并添加如下代码。调用扫码功能以及获取扫码结果的回调结果。

    1. public class ScanHelper {
    2. private static class Holder {
    3. private static ScanHelper instance = new ScanHelper();
    4. }
    5. private ScanCallback scanCallback;
    6. private ScanHelper() {
    7. }
    8. public static ScanHelper getInstance() {
    9. return Holder.instance;
    10. }
    11. public void scan(Context context, ScanCallback scanCallback) {
    12. if (context == null) {
    13. return;
    14. }
    15. this.scanCallback = scanCallback;
    16. context.startActivity(new Intent(context, CustomScanActivity.class));
    17. }
    18. void notifyScanResult(boolean isProcessed, Intent resultData) {
    19. if (scanCallback != null) {
    20. scanCallback.onScanResult(isProcessed, resultData);
    21. scanCallback = null;
    22. }
    23. }
    24. public interface ScanCallback {
    25. void onScanResult(boolean isProcessed, Intent result);
    26. }
    27. }
  2. 在 custom 的 com.example.custom 包中添加 CustomScanActivity 类,让其继承 Activity 类。设置界面沉浸模式并创建资源文件对应的 View 和 Button。

    1. public class CustomScanActivity extends Activity {
    2. private final String TAG = CustomScanActivity.class.getSimpleName();
    3. private static final int REQUEST_CODE_PERMISSION = 1;
    4. private static final int REQUEST_CODE_PHOTO = 2;
    5. private ImageView mTorchBtn;
    6. private APTextureView mTextureView;
    7. private ScanView mScanView;
    8. private boolean isFirstStart = true;
    9. private boolean isPermissionGranted;
    10. private boolean isScanning;
    11. private boolean isPaused;
    12. private Rect scanRect;
    13. private MPScanner mpScanner;
    14. @Override
    15. protected void onCreate(Bundle savedInstanceState) {
    16. super.onCreate(savedInstanceState);
    17. setContentView(R.layout.activity_custom_scan);
    18. // 设置沉浸模式
    19. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    20. getWindow().setFlags(
    21. WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
    22. WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    23. }
    24. mTextureView = findViewById(R.id.surface_view);
    25. mScanView = findViewById(R.id.scan_view);
    26. mTorchBtn = findViewById(R.id.torch);
    27. }
    28. @Override
    29. public void onPause() {
    30. super.onPause();
    31. }
    32. @Override
    33. public void onResume() {
    34. super.onResume();
    35. }
    36. @Override
    37. public void onDestroy() {
    38. super.onDestroy();
    39. }
    40. @Override
    41. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    42. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    43. }
    44. @Override
    45. public void onBackPressed() {
    46. super.onBackPressed();
    47. }
    48. @Override
    49. public void onActivityResult(int requestCode, int resultCode, Intent data) {
    50. super.onActivityResult(requestCode, resultCode, data);
    51. }
    52. }
  3. 在 CustomScanActivity 中创建 pickImageFromGallery 方法,实现打开手机相册的功能。
    1. private void pickImageFromGallery() {
    2. Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    3. intent.setType("image/*");
    4. startActivityForResult(intent, REQUEST_CODE_PHOTO);
    5. }
  4. 在 CustomScanActivity 的 onCreate 方法中添加 _ _gallery 的点击事件,并调用 pickImageFromGallery 方法。
    1. findViewById(R.id.gallery).setOnClickListener(new View.OnClickListener() {
    2. @Override
    3. public void onClick(View v) {
    4. pickImageFromGallery();
    5. }
    6. });
  5. 在 switchTorch 方法中实现切换手电开关的功能。
    1. private void switchTorch() {
    2. boolean torchOn = mpScanner.switchTorch();
    3. mTorchBtn.setSelected(torchOn);
    4. }
  6. 在 CustomScanActivity 的 onCreate 方法中添加 mTorchBtn 的点击事件,并调用 switchTorch 方法。
    1. mTorchBtn.setOnClickListener(new View.OnClickListener() {
    2. @Override
    3. public void onClick(View v) {
    4. switchTorch();
    5. }
    6. });
  7. 在 CustomScanActivity 中创建 notifyScanResult 方法。
    1. private void notifyScanResult(boolean isProcessed, Intent resultData) {
    2. ScanHelper.getInstance().notifyScanResult(isProcessed, resultData);
    3. }
  8. 在 onBackPressed 中调用 notifyScanResult 方法。
    1. @Override
    2. public void onBackPressed() {
    3. super.onBackPressed();
    4. notifyScanResult(false, null);
    5. }
  9. 在 CustomScanActivity 的 onCreate 方法中添加 _ _back 的点击事件,并调用 onBackPressed 方法。```java
    1. findViewById(R.id.back).setOnClickListener(new View.OnClickListener() {
    2. @Override
    3. public void onClick(View v) {
    4. onBackPressed();
    5. }
    6. });
    ``
  10. 在 CustomScanActivity 中创建 initMPScanner 方法,并使用 mpScanner 对象的 setRecognizeType 方法设置识别码的类型。
    1. private void initMPScanner() {
    2. mpScanner = new MPScanner(this);
    3. mpScanner.setRecognizeType(
    4. MPRecognizeType.QR_CODE,
    5. MPRecognizeType.BAR_CODE,
    6. MPRecognizeType.DM_CODE,
    7. MPRecognizeType.PDF417_CODE
    8. );
    9. }
  11. 在 CustomScanActivity 中创建 onScanSuccess 方法,并实现如下代码。
    1. private void onScanSuccess(final MPScanResult result) {
    2. runOnUiThread(new Runnable() {
    3. @Override
    4. public void run() {
    5. if (result == null) {
    6. notifyScanResult(true, null);
    7. } else {
    8. Intent intent = new Intent();
    9. intent.setData(Uri.parse(result.getText()));
    10. notifyScanResult(true, intent);
    11. }
    12. CustomScanActivity.this.finish();
    13. }
    14. });
    15. }
  12. 在 CustomScanActivity 中创建 initScanRect 方法,初始化扫描功能。调用 mpScanner 对象的 getCamera 方法获取 Camera 对象 并调用 mpScanner 对象的 setScanRegion 方法设置扫描区域。

    1. private void initScanRect() {
    2. if (scanRect == null) {
    3. scanRect = mScanView.getScanRect(
    4. mpScanner.getCamera(), mTextureView.getWidth(), mTextureView.getHeight());
    5. float cropWidth = mScanView.getCropWidth();
    6. LoggerFactory.getTraceLogger().debug(TAG, "cropWidth: " + cropWidth);
    7. if (cropWidth > 0) {
    8. // 预览放大 = 屏幕宽 / 裁剪框宽
    9. WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    10. float screenWith = wm.getDefaultDisplay().getWidth();
    11. float screenHeight = wm.getDefaultDisplay().getHeight();
    12. float previewScale = screenWith / cropWidth;
    13. if (previewScale < 1.0f) {
    14. previewScale = 1.0f;
    15. }
    16. if (previewScale > 1.5f) {
    17. previewScale = 1.5f;
    18. }
    19. LoggerFactory.getTraceLogger().debug(TAG, "previewScale: " + previewScale);
    20. Matrix transform = new Matrix();
    21. transform.setScale(previewScale, previewScale, screenWith / 2, screenHeight / 2);
    22. mTextureView.setTransform(transform);
    23. }
    24. }
    25. mpScanner.setScanRegion(scanRect);
    26. }
  13. 在 initMPScanner 方法中使用 mpScanner 对象的 setMPScanListener 方法实现扫描监听器的功能。

    1. mpScanner.setMPScanListener(new MPScanListener() {
    2. @Override
    3. public void onConfiguration() {
    4. mpScanner.setDisplayView(mTextureView);
    5. }
    6. @Override
    7. public void onStart() {
    8. if (!isPaused) {
    9. runOnUiThread(new Runnable() {
    10. @Override
    11. public void run() {
    12. if (!isFinishing()) {
    13. initScanRect();
    14. mScanView.onStartScan();
    15. }
    16. }
    17. });
    18. }
    19. }
    20. @Override
    21. public void onSuccess(MPScanResult mpScanResult) {
    22. mpScanner.beep();
    23. onScanSuccess(mpScanResult);
    24. }
    25. @Override
    26. public void onError(MPScanError mpScanError) {
    27. if (!isPaused) {
    28. runOnUiThread(new Runnable() {
    29. @Override
    30. public void run() {
    31. Utils.toast(CustomScanActivity.this, getString(R.string.camera_open_error));
    32. }
    33. });
    34. }
    35. }
    36. });
  14. 在 initMPScanner 方法中使用 mpScanner 对象的 setMPImageGrayListener 方法实现识别图像灰度值的监听功能。
    1. mpScanner.setMPImageGrayListener(new MPImageGrayListener() {
    2. @Override
    3. public void onGetImageGray(int gray) {
    4. // 注意:该回调在昏暗环境下可能会连续多次执行
    5. if (gray < MPImageGrayListener.LOW_IMAGE_GRAY) {
    6. runOnUiThread(new Runnable() {
    7. @Override
    8. public void run() {
    9. Utils.toast(CustomScanActivity.this, "光线太暗,请打开手电筒");
    10. }
    11. });
    12. }
    13. }
    14. });
    15. }
  15. 在 CustomScanActivity 中分别创建 startScan 和 stopScan 方法,实现开启和关闭相机扫码权限。

    1. private void startScan() {
    2. try {
    3. mpScanner.openCameraAndStartScan();
    4. isScanning = true;
    5. } catch (Exception e) {
    6. isScanning = false;
    7. LoggerFactory.getTraceLogger().error(TAG, "startScan: Exception " + e.getMessage());
    8. }
    9. }
    10. private void stopScan() {
    11. mpScanner.closeCameraAndStopScan();
    12. mScanView.onStopScan();
    13. isScanning = false;
    14. if (isFirstStart) {
    15. isFirstStart = false;
    16. }
    17. }
  16. 在 CustomScanActivity 中创建 onPermissionGranted 方法。
    1. private void onPermissionGranted() {
    2. isPermissionGranted = true;
    3. startScan();
    4. }
  17. 在 CustomScanActivity 中创建 checkCameraPermission 方法。
    1. private void checkCameraPermission() {
    2. if (PermissionChecker.checkSelfPermission(
    3. this, Manifest.permission.CAMERA) != PermissionChecker.PERMISSION_GRANTED) {
    4. ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_PERMISSION);
    5. } else {
    6. onPermissionGranted();
    7. }
    8. }
  18. 在 CustomScanActivity 中创建 scanFromUri 方法。
    1. private void scanFromUri(Uri uri) {
    2. final Bitmap bitmap = Utils.uri2Bitmap(this, uri);
    3. if (bitmap == null) {
    4. notifyScanResult(true, null);
    5. finish();
    6. } else {
    7. new Thread(new Runnable() {
    8. @Override
    9. public void run() {
    10. MPScanResult mpScanResult = mpScanner.scanFromBitmap(bitmap);
    11. mpScanner.beep();
    12. onScanSuccess(mpScanResult);
    13. }
    14. }, "scanFromUri").start();
    15. }
    16. }
  19. 在 CustomScanActivity 的 onCreate 方法中调用 checkCameraPermission 方法检查相机权限。
    1. checkCameraPermission();
  20. 在 CustomScanActivity 的 onPause、onResume、onDestroy、onRequestPermissionsResult 和 onActivityResult 方法中分别添加如下内容。

    1. @Override
    2. public void onPause() {
    3. super.onPause();
    4. isPaused = true;
    5. if (isScanning) {
    6. stopScan();
    7. }
    8. }
    9. @Override
    10. public void onResume() {
    11. super.onResume();
    12. isPaused = false;
    13. if (!isFirstStart && isPermissionGranted) {
    14. startScan();
    15. }
    16. }
    17. @Override
    18. public void onDestroy() {
    19. super.onDestroy();
    20. mpScanner.release();
    21. }
    22. @Override
    23. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    24. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    25. if (requestCode == REQUEST_CODE_PERMISSION) {
    26. int length = Math.min(permissions.length, grantResults.length);
    27. for (int i = 0; i < length; i++) {
    28. if (TextUtils.equals(permissions[i], Manifest.permission.CAMERA)) {
    29. if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
    30. Utils.toast(this, getString(R.string.camera_no_permission));
    31. } else {
    32. onPermissionGranted();
    33. }
    34. break;
    35. }
    36. }
    37. }
    38. @Override
    39. public void onActivityResult(int requestCode, int resultCode, Intent data) {
    40. super.onActivityResult(requestCode, resultCode, data);
    41. if (data == null) {
    42. return;
    43. }
    44. if (requestCode == REQUEST_CODE_PHOTO) {
    45. scanFromUri(data.getData());
    46. }
    47. }
    48. }
  21. 在 custom 的 AndroidManifest.xml 文件中设置 CustomScanActivity 为 custom 的主入口。

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    3. package="com.mpaas.aar.demo.custom">
    4. <application>
    5. <meta-data
    6. android:name="com.google.android.actions"
    7. android:resource="@xml/actions" />
    8. <activity
    9. android:name=".CustomScanActivity"
    10. android:configChanges="orientation|keyboardHidden|navigation"
    11. android:exported="false"
    12. android:launchMode="singleTask"
    13. android:screenOrientation="portrait"
    14. android:theme="@android:style/Theme.NoTitleBar"
    15. android:windowSoftInputMode="adjustResize|stateHidden" />
    16. </application>
    17. </manifest>

在主工程中调用自定义 UI 下的扫码功能

  1. activity_main.xml 文件中,添加 Button,并设置 Button 的 id 为 custom_ui_btn
    1. <Button
    2. android:id="@+id/custom_ui_btn"
    3. android:layout_width="match_parent"
    4. android:layout_height="wrap_content"
    5. android:layout_marginTop="208dp"
    6. android:background="#108EE9"
    7. android:gravity="center"
    8. android:text="自定义 UI 下使用扫一扫"
    9. android:textColor="#ffffff"
    10. app:layout_constraintEnd_toEndOf="parent"
    11. app:layout_constraintHorizontal_bias="0.0"
    12. app:layout_constraintStart_toStartOf="parent"
    13. app:layout_constraintTop_toTopOf="parent" />
  1. MainActivity 类中编写代码。添加 custom_ui_btn 按钮的点击事件。获取自定义 UI 界面,并使用自定义 UI 的扫码功能。代码如下所示:

    1. findViewById(R.id.custom_ui_btn).setOnClickListener(new View.OnClickListener() {
    2. @Override
    3. public void onClick(View v) {
    4. ScanHelper.getInstance().scan(MainActivity.this, new ScanHelper.ScanCallback() {
    5. @Override
    6. public void onScanResult(boolean isProcessed, Intent result) {
    7. if (!isProcessed) {
    8. // 扫码界面点击物理返回键或左上角返回键
    9. return;
    10. }
    11. if (result == null || result.getData() == null) {
    12. Toast.makeText(MainActivity.this, "扫码失败,请重试!", Toast.LENGTH_SHORT).show();
    13. return;
    14. }
    15. new AlertDialog.Builder(MainActivity.this)
    16. .setMessage(result.getData().toString())
    17. .setPositiveButton(R.string.confirm, null)
    18. .create()
    19. .show();
    20. }
    21. });
    22. }
    23. });
  2. 编译运行工程后,界面如下:

  3. 点击 自定义 UI 下使用扫一扫 后即可使用自定义 UI 下的扫码功能。
  4. 扫描如下二维码。
    21
  5. 会弹出该二维码的信息。