阿里云首页 移动开发平台 mPaaS 相关技术圈

自定义 UI 下使用扫码功能

本文将引导您绘制自定义 UI 界面并将自定义 UI 扫码的能力添加到工程中。

该过程主要分为以下四个步骤:

  1. 创建依赖工程

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

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

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

操作步骤

创建依赖工程

  1. 单击 File > New > New Module

    1
  2. 选择 Android Library,单击 Next

    2
  3. 输入 Module name,单击 Finish

    3

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

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

    public class APSurfaceTexture extends SurfaceTexture {
    
     private static final String TAG = "APSurfaceTexture";
    
     public SurfaceTexture mSurface;
    
     public APSurfaceTexture() {
         super(0);
     }
    
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     @Override
     public void attachToGLContext(int texName) {
         mSurface.attachToGLContext(texName);
     }
    
     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     @Override
     public void detachFromGLContext() {
         try {
             mSurface.detachFromGLContext();
         } catch (Exception ex) {
             try {
                 Method nativeMethod = SurfaceTexture.class.getDeclaredMethod("nativeDetachFromGLContext");
                 nativeMethod.setAccessible(true);
                 int retCode = (Integer) nativeMethod.invoke(mSurface);
                 LoggerFactory.getTraceLogger().debug(TAG, "nativeDetachFromGLContext invoke retCode:" + retCode);
             } catch (Exception e) {
                 LoggerFactory.getTraceLogger().error(TAG, "nativeDetachFromGLContext invoke exception:" + e.getMessage());
             }
             LoggerFactory.getTraceLogger().error(TAG, "mSurface.detachFromGLContext() exception:" + ex.getMessage());
         }
     }
    
     @Override
     public boolean equals(Object o) {
         return mSurface.equals(o);
     }
    
     @Override
     public long getTimestamp() {
         return mSurface.getTimestamp();
     }
    
     @Override
     public void getTransformMatrix(float[] mtx) {
         mSurface.getTransformMatrix(mtx);
     }
    
     @Override
     public void release() {
         super.release();
         mSurface.release();
     }
    
     @Override
     public int hashCode() {
         return mSurface.hashCode();
     }
    
     @TargetApi(Build.VERSION_CODES.KITKAT)
     @Override
     public void releaseTexImage() {
         mSurface.releaseTexImage();
     }
    
     @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
     @Override
     public void setDefaultBufferSize(int width, int height) {
         mSurface.setDefaultBufferSize(width, height);
     }
    
     @Override
     public void setOnFrameAvailableListener(OnFrameAvailableListener listener) {
         mSurface.setOnFrameAvailableListener(listener);
     }
    
     @Override
     public String toString() {
         return mSurface.toString();
     }
    
     @Override
     public void updateTexImage() {
         mSurface.updateTexImage();
     }
    }
  2. customwidget 包中添加 APTextureView 类,让其继承 TextureView 类,实现图像流的显示。

    public class APTextureView extends TextureView {
    
     private static final String TAG = "APTextureView";
    
     private Field mSurfaceField;
    
     public APTextureView(Context context) {
         super(context);
     }
    
     public APTextureView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
    
     public APTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }
    
     @Override
     protected void onDetachedFromWindow() {
         try {
             super.onDetachedFromWindow();
         } catch (Exception ex) {
             LoggerFactory.getTraceLogger().error(TAG, "onDetachedFromWindow exception:" + ex.getMessage());
         }
     }
    
     @Override
     public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
         super.setSurfaceTexture(surfaceTexture);
         afterSetSurfaceTexture();
     }
    
     private void afterSetSurfaceTexture() {
         LoggerFactory.getTraceLogger().debug(TAG, "afterSetSurfaceTexture Build.VERSION.SDK_INT:" + Build.VERSION.SDK_INT);
         if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 20) {
             return;
         }
    
         try {
             if (mSurfaceField == null) {
                 mSurfaceField = TextureView.class.getDeclaredField("mSurface");
                 mSurfaceField.setAccessible(true);
             }
    
             SurfaceTexture innerSurface = (SurfaceTexture) mSurfaceField.get(this);
             if (innerSurface != null) {
                 if (!(innerSurface instanceof APSurfaceTexture)) {
                     APSurfaceTexture wrapSurface = new APSurfaceTexture();
                     wrapSurface.mSurface = innerSurface;
                     mSurfaceField.set(this, wrapSurface);
                     LoggerFactory.getTraceLogger().debug(TAG, "afterSetSurfaceTexture wrap mSurface");
                 }
             }
         } catch (Exception ex) {
             LoggerFactory.getTraceLogger().error(TAG, "afterSetSurfaceTexture exception:" + ex.getMessage());
         }
     }
    }
  3. com.example.custom 包中创建 Utils 类,实现图片的转换。

    public class Utils {
    
     private static String TAG = "Utils";
    
     public static void toast(Context context, String msg) {
         Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
     }
    
     public static Bitmap changeBitmapColor(Bitmap bitmap, int color) {
         int bitmap_w = bitmap.getWidth();
         int bitmap_h = bitmap.getHeight();
         int[] arrayColor = new int[bitmap_w * bitmap_h];
    
         int count = 0;
         for (int i = 0; i < bitmap_h; i++) {
             for (int j = 0; j < bitmap_w; j++) {
    
                 int originColor = bitmap.getPixel(j, i);
                 // 非透明区域
                 if (originColor != 0) {
                     originColor = color;
                 }
    
                 arrayColor[count] = originColor;
                 count++;
             }
         }
         return Bitmap.createBitmap(arrayColor, bitmap_w, bitmap_h, Bitmap.Config.ARGB_8888);
     }
    
     public static Bitmap uri2Bitmap(Context context, Uri uri) {
         Bitmap bitmap = null;
         InputStream in;
         try {
             in = context.getContentResolver().openInputStream(uri);
             if (in != null) {
                 bitmap = BitmapFactory.decodeStream(in);
                 in.close();
             }
         } catch (Exception e) {
             LoggerFactory.getTraceLogger().error(TAG, "uri2Bitmap: Exception " + e.getMessage());
         }
         return bitmap;
     }
    }
  4. custom 中创建 res > values > attrs.xml 文件并添加如下代码。

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
     <declare-styleable name="scan">
         <attr name="shadowColor" format="color" />
     </declare-styleable>
    </resources>
  5. customres > drawable 文件夹中粘贴如下 资源文件

    12
  6. customwidget 包中添加 FinderView 类,让其继承 View 类,并添加如下代码。实现扫码窗口、边角及周边阴影的绘制功能。

    public class FinderView extends View {
    
    private static final int DEFAULT_SHADOW_COLOR = 0x96000000;
    
    private int scanWindowLeft, scanWindowTop, scanWindowRight, scanWindowBottom;
    private Bitmap leftTopCorner, rightTopCorner, leftBottomCorner, rightBottomCorner;
    private Paint paint;
    private int shadowColor;
    
    public FinderView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }
    
    public FinderView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }
    
    private void init(Context context, AttributeSet attrs) {
        applyConfig(context, attrs);
        setVisibility(INVISIBLE);
        initCornerBitmap(context);
    
        paint = new Paint();
        paint.setAntiAlias(true);
    }
    
    private void applyConfig(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.scan);
            shadowColor = typedArray.getColor(R.styleable.scan_shadowColor, DEFAULT_SHADOW_COLOR);
            typedArray.recycle();
        }
    }
    //初始化扫码窗口边角样式
    private void initCornerBitmap(Context context) {
        Resources res = context.getResources();
        leftTopCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_left_top);
        rightTopCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_right_top);
        leftBottomCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_left_bottom);
        rightBottomCorner = BitmapFactory.decodeResource(res, R.drawable.scan_window_corner_right_bottom);
    }
    
    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        drawShadow(canvas);
        drawCorner(canvas);
    }
    //绘制扫码窗口边角样式
    private void drawCorner(Canvas canvas) {
        paint.setAlpha(255);
        canvas.drawBitmap(leftTopCorner, scanWindowLeft, scanWindowTop, paint);
        canvas.drawBitmap(rightTopCorner, scanWindowRight - rightTopCorner.getWidth(), scanWindowTop, paint);
        canvas.drawBitmap(leftBottomCorner, scanWindowLeft, scanWindowBottom - leftBottomCorner.getHeight(), paint);
        canvas.drawBitmap(rightBottomCorner, scanWindowRight - rightBottomCorner.getWidth(), scanWindowBottom - rightBottomCorner.getHeight(), paint);
    }
    //绘制扫码周边阴影
    private void drawShadow(Canvas canvas) {
        paint.setColor(shadowColor);
        canvas.drawRect(0, 0, getWidth(), scanWindowTop, paint);
        canvas.drawRect(0, scanWindowTop, scanWindowLeft, scanWindowBottom, paint);
        canvas.drawRect(scanWindowRight, scanWindowTop, getWidth(), scanWindowBottom, paint);
        canvas.drawRect(0, scanWindowBottom, getWidth(), getHeight(), paint);
    }
    
    /**
     * 根据 RayView 的位置决定扫码窗口的位置
     */
    public void setScanWindowLocation(int left, int top, int right, int bottom) {
        scanWindowLeft = left;
        scanWindowTop = top;
        scanWindowRight = right;
        scanWindowBottom = bottom;
        invalidate();
        setVisibility(VISIBLE);
    }
    
    public void setShadowColor(int shadowColor) {
        this.shadowColor = shadowColor;
    }
    //设置扫码窗口边角颜色
    public void setCornerColor(int angleColor) {
        leftTopCorner = Utils.changeBitmapColor(leftTopCorner, angleColor);
        rightTopCorner = Utils.changeBitmapColor(rightTopCorner, angleColor);
        leftBottomCorner = Utils.changeBitmapColor(leftBottomCorner, angleColor);
        rightBottomCorner = Utils.changeBitmapColor(rightBottomCorner, angleColor);
    }
    }
  7. customwidget 包中添加 RayView 类,让其继承 ImageView 类,并添加如下代码。实现扫描射线的绘制功能。

    public class RayView extends ImageView {
    
    private FinderView mFinderView;
    private ScaleAnimation scanAnimation;
    private int[] location = new int[2];
    
    public RayView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    public RayView(Context context) {
        super(context);
    }
    
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    
        // 设置 FinderView 中扫码窗口的位置
        getLocationOnScreen(location);
        if (mFinderView != null) {
            mFinderView.setScanWindowLocation(location[0], location[1], location[0] + getWidth(), location[1] + getHeight());
        }
    }
    
    public void startScanAnimation() {
        setVisibility(VISIBLE);
        if (scanAnimation == null) {
            scanAnimation = new ScaleAnimation(1.0f, 1.0f, 0.0f, 1.0f);
            scanAnimation.setDuration(3000L);
            scanAnimation.setFillAfter(true);
            scanAnimation.setRepeatCount(Animation.INFINITE);
            scanAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
        }
        startAnimation(scanAnimation);
    }
    
    public void stopScanAnimation() {
        setVisibility(INVISIBLE);
        if (scanAnimation != null) {
            this.clearAnimation();
            scanAnimation = null;
        }
    }
    
    public void setFinderView(FinderView FinderView) {
        mFinderView = FinderView;
    }
    }
  8. customres 中创建 layout > File > view_scan.xml 文件,并添加如下代码,绘制扫描页面的布局界面。

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android">
    
        <com.example.custom.widget.FinderView
            android:id="@+id/finder_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:gravity="center_vertical"
            android:orientation="horizontal">
    
            <ImageView
                android:id="@+id/back"
                android:layout_width="48dp"
                android:layout_height="48dp"
                android:scaleType="center"
                android:src="@drawable/icon_back" />
    
            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center"
                android:text="@string/custom_title"
                android:textColor="#ffffff"
                android:textSize="16sp" />
    
            <ImageView
                android:id="@+id/gallery"
                android:layout_width="34dp"
                android:layout_height="34dp"
                android:layout_marginEnd="10dp"
                android:layout_marginRight="10dp"
                android:scaleType="fitXY"
                android:src="@drawable/selector_scan_from_gallery" />
    
            <ImageView
                android:id="@+id/torch"
                android:layout_width="34dp"
                android:layout_height="34dp"
                android:layout_marginEnd="10dp"
                android:layout_marginRight="10dp"
                android:scaleType="fitXY"
                android:src="@drawable/selector_torch" />
        </LinearLayout>
    
        <com.example.custom.widget.RayView
            android:id="@+id/ray_view"
            android:layout_width="270dp"
            android:layout_height="280dp"
            android:layout_centerInParent="true"
            android:background="@drawable/custom_scan_ray" />
    
        <TextView
            android:id="@+id/tip_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/ray_view"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="10dp"
            android:includeFontPadding="false"
            android:text="@string/scan_tip"
            android:textColor="#7fffffff"
            android:textSize="14sp" />
    
    </merge>
  9. widget 包中添加 ScanView 类,让其继承 RelativeLayout 类,并添加如下代码。实现扫码相关的 View 与扫码引擎的交互功能。

    public class ScanView extends RelativeLayout {
    
    private RayView mRayView;
    
    public ScanView(Context context) {
        super(context);
        init(context);
    }
    
    public ScanView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }
    
    public ScanView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }
    
    private void init(Context ctx) {
        LayoutInflater.from(ctx).inflate(R.layout.view_scan, this, true);
        FinderView finderView = (FinderView) findViewById(R.id.finder_view);
        mRayView = (RayView) findViewById(R.id.ray_view);
        mRayView.setFinderView(finderView);
    }
    
    public void onStartScan() {
        mRayView.startScanAnimation();
    }
    
    public void onStopScan() {
        mRayView.stopScanAnimation();
    }
    
    public float getCropWidth() {
        return mRayView.getWidth() * 1.1f;
    }
    
    public Rect getScanRect(Camera camera, int previewWidth, int previewHeight) {
        if (camera == null) {
            return null;
        }
        int[] location = new int[2];
        mRayView.getLocationOnScreen(location);
        Rect r = new Rect(location[0], location[1],
                location[0] + mRayView.getWidth(), location[1] + mRayView.getHeight());
        Camera.Size size;
        try {
            size = camera.getParameters().getPreviewSize();
        } catch (Exception e) {
            return null;
        }
        if (size == null) {
            return null;
        }
        double rateX = (double) size.height / (double) previewWidth;
        double rateY = (double) size.width / (double) previewHeight;
        // 裁剪框大小 = 网格动画框大小*1.1
        int expandX = (int) (mRayView.getWidth() * 0.05);
        int expandY = (int) (mRayView.getHeight() * 0.05);
        Rect resRect = new Rect(
                (int) ((r.top - expandY) * rateY),
                (int) ((r.left - expandX) * rateX),
                (int) ((r.bottom + expandY) * rateY),
                (int) ((r.right + expandX) * rateX));
    
        Rect finalRect = new Rect(
                resRect.left < 0 ? 0 : resRect.left,
                resRect.top < 0 ? 0 : resRect.top,
                resRect.width() > size.width ? size.width : resRect.width(),
                resRect.height() > size.height ? size.height : resRect.height());
    
        Rect rect1 = new Rect(
                finalRect.left / 4 * 4,
                finalRect.top / 4 * 4,
                finalRect.right / 4 * 4,
                finalRect.bottom / 4 * 4);
    
        int max = Math.max(rect1.right, rect1.bottom);
        int diff = Math.abs(rect1.right - rect1.bottom) / 8 * 4;
    
        Rect rect2;
        if (rect1.right > rect1.bottom) {
            rect2 = new Rect(rect1.left, rect1.top - diff, max, max);
        } else {
            rect2 = new Rect(rect1.left - diff, rect1.top, max, max);
        }
        return rect2;
    }
    }
  10. customlayout 文件夹中创建 activity_custom_scan.xml 文件并添加如下代码。绘制自定义扫码功能的主界面。

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.mpaas.aar.demo.custom.widget.APTextureView
            android:id="@+id/surface_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
        <com.mpaas.aar.demo.custom.widget.ScanView
            android:id="@+id/scan_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </FrameLayout>

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

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

    public class ScanHelper {
    
     private static class Holder {
         private static ScanHelper instance = new ScanHelper();
     }
    
     private ScanCallback scanCallback;
    
     private ScanHelper() {
     }
    
     public static ScanHelper getInstance() {
         return Holder.instance;
     }
    
     public void scan(Context context, ScanCallback scanCallback) {
         if (context == null) {
             return;
         }
         this.scanCallback = scanCallback;
         context.startActivity(new Intent(context, CustomScanActivity.class));
     }
    
     void notifyScanResult(boolean isProcessed, Intent resultData) {
         if (scanCallback != null) {
             scanCallback.onScanResult(isProcessed, resultData);
             scanCallback = null;
         }
     }
    
     public interface ScanCallback {
         void onScanResult(boolean isProcessed, Intent result);
     }
    }
  2. customcom.example.custom 包中添加 CustomScanActivity 类,让其继承 Activity 类。设置界面沉浸模式并创建资源文件对应的 ViewButton

    public class CustomScanActivity extends Activity {
     private final String TAG = CustomScanActivity.class.getSimpleName();
     private static final int REQUEST_CODE_PERMISSION = 1;
     private static final int REQUEST_CODE_PHOTO = 2;
     private ImageView mTorchBtn;
     private APTextureView mTextureView;
     private ScanView mScanView;
     private boolean isFirstStart = true;
     private boolean isPermissionGranted;
     private boolean isScanning;
     private boolean isPaused;
     private Rect scanRect;
     private MPScanner mpScanner;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_custom_scan);
    
         // 设置沉浸模式
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
             getWindow().setFlags(
                     WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
                     WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
         }
    
         mTextureView = findViewById(R.id.surface_view);
         mScanView = findViewById(R.id.scan_view);
         mTorchBtn = findViewById(R.id.torch);
    
     }
    
      @Override
     public void onPause() {
         super.onPause();
    
     }
    
     @Override
     public void onResume() {
         super.onResume();
    
     }
    
     @Override
     public void onDestroy() {
         super.onDestroy();
    
     }
    
     @Override
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    
     }
      @Override
     public void onBackPressed() {
         super.onBackPressed();
    
     }
    
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
    
     }
    }
  3. 实现打开手机相册的功能。

    1. CustomScanActivity 中创建 pickImageFromGallery 方法。

      private void pickImageFromGallery() {
       Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
       intent.setType("image/*");
       startActivityForResult(intent, REQUEST_CODE_PHOTO);
      }
    2. onCreate 方法中添加 gallery 的单击事件,并调用 pickImageFromGallery 方法。

       findViewById(R.id.gallery).setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               pickImageFromGallery();
           }
       });
  4. 实现切换手电开关的功能。

    1. CustomScanActivity 中创建 switchTorch 方法。

       private void switchTorch() {
           boolean torchOn = mpScanner.switchTorch();
           mTorchBtn.setSelected(torchOn);
       }
    2. onCreate 方法中添加 mTorchBtn 的单击事件,并调用 switchTorch 方法。

      mTorchBtn.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
                   switchTorch();
               }
           });
  5. CustomScanActivity 中创建 notifyScanResult 方法,onBackPressed 中调用 notifyScanResult 方法。

     private void notifyScanResult(boolean isProcessed, Intent resultData) {
         ScanHelper.getInstance().notifyScanResult(isProcessed, resultData);
     }
    
     @Override
     public void onBackPressed() {
         super.onBackPressed();
         notifyScanResult(false, null);
     }
  6. CustomScanActivityonCreate 方法中添加 back 的单击事件,并调用 onBackPressed 方法。

         findViewById(R.id.back).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 onBackPressed();
             }
         });
  7. CustomScanActivity 中创建 initMPScanner 方法,并使用 mpScanner 对象的 setRecognizeType 方法设置识别码的类型。

    private void initMPScanner() {
        mpScanner = new MPScanner(this);
        mpScanner.setRecognizeType(
                MPRecognizeType.QR_CODE,
                MPRecognizeType.BAR_CODE,
                MPRecognizeType.DM_CODE,
                MPRecognizeType.PDF417_CODE
        );
    }
  8. CustomScanActivity 中创建 onScanSuccess 方法,并实现如下代码。

    private void onScanSuccess(final MPScanResult result) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (result == null) {
                    notifyScanResult(true, null);
                } else {
                    Intent intent = new Intent();
                    intent.setData(Uri.parse(result.getText()));
                    notifyScanResult(true, intent);
                }
                CustomScanActivity.this.finish();
            }
        });
    }
  9. CustomScanActivity 中创建 initScanRect 方法,初始化扫描功能。

    1. 调用 mpScanner 对象的 getCamera 方法获取 Camera 对象并调用 mpScanner 对象的 setScanRegion 方法设置扫描区域。

      private void initScanRect() {
       if (scanRect == null) {
           scanRect = mScanView.getScanRect(
                   mpScanner.getCamera(), mTextureView.getWidth(), mTextureView.getHeight());
      
           float cropWidth = mScanView.getCropWidth();
           LoggerFactory.getTraceLogger().debug(TAG, "cropWidth: " + cropWidth);
           if (cropWidth > 0) {
               // 预览放大 = 屏幕宽 / 裁剪框宽
               WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
               float screenWith = wm.getDefaultDisplay().getWidth();
               float screenHeight = wm.getDefaultDisplay().getHeight();
               float previewScale = screenWith / cropWidth;
               if (previewScale < 1.0f) {
                   previewScale = 1.0f;
               }
               if (previewScale > 1.5f) {
                   previewScale = 1.5f;
               }
               LoggerFactory.getTraceLogger().debug(TAG, "previewScale: " + previewScale);
               Matrix transform = new Matrix();
               transform.setScale(previewScale, previewScale, screenWith / 2, screenHeight / 2);
               mTextureView.setTransform(transform);
           }
       }
       mpScanner.setScanRegion(scanRect);
      }
    2. 使用 mpScanner 对象的 setMPScanListener 方法实现扫描监听器的功能。

      mpScanner.setMPScanListener(new MPScanListener() {
          @Override
          public void onConfiguration() {
              mpScanner.setDisplayView(mTextureView);
          }
      
          @Override
          public void onStart() {
              if (!isPaused) {
                  runOnUiThread(new Runnable() {
                      @Override
                      public void run() {
                          if (!isFinishing()) {
                              initScanRect();
                              mScanView.onStartScan();
                          }
                      }
                  });
              }
          }
      
          @Override
          public void onSuccess(MPScanResult mpScanResult) {
              mpScanner.beep();
              onScanSuccess(mpScanResult);
          }
      
          @Override
          public void onError(MPScanError mpScanError) {
              if (!isPaused) {
                  runOnUiThread(new Runnable() {
                      @Override
                      public void run() {
                          Utils.toast(CustomScanActivity.this, getString(R.string.camera_open_error));
                      }
                  });
              }
          }
      });
    3. 使用 mpScanner 对象的 setMPImageGrayListener 方法实现识别图像灰度值的监听功能。

      mpScanner.setMPImageGrayListener(new MPImageGrayListener() {
          @Override
          public void onGetImageGray(int gray) {
              // 注意:该回调在昏暗环境下可能会连续多次执行
              if (gray < MPImageGrayListener.LOW_IMAGE_GRAY) {
                  runOnUiThread(new Runnable() {
                      @Override
                      public void run() {
                          Utils.toast(CustomScanActivity.this, "光线太暗,请打开手电筒");
                      }
                  });
              }
          }
      });
      }
  10. CustomScanActivity 中分别创建 startScanstopScan 方法,实现开启和关闭相机扫码权限。

    private void startScan() {
        try {
            mpScanner.openCameraAndStartScan();
            isScanning = true;
        } catch (Exception e) {
            isScanning = false;
            LoggerFactory.getTraceLogger().error(TAG, "startScan: Exception " + e.getMessage());
        }
    }
    
    private void stopScan() {
        mpScanner.closeCameraAndStopScan();
        mScanView.onStopScan();
        isScanning = false;
        if (isFirstStart) {
            isFirstStart = false;
        }
    }
  11. CustomScanActivity 中创建 onPermissionGranted 方法、checkCameraPermission 方法和 scanFromUri 方法。

    private void onPermissionGranted() {
        isPermissionGranted = true;
        startScan();
    }
    
    private void checkCameraPermission() {
        if (PermissionChecker.checkSelfPermission(
                this, Manifest.permission.CAMERA) != PermissionChecker.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_PERMISSION);
        } else {
            onPermissionGranted();
        }
    }
    
    private void scanFromUri(Uri uri) {
        final Bitmap bitmap = Utils.uri2Bitmap(this, uri);
        if (bitmap == null) {
            notifyScanResult(true, null);
            finish();
        } else {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    MPScanResult mpScanResult = mpScanner.scanFromBitmap(bitmap);
                    mpScanner.beep();
                    onScanSuccess(mpScanResult);
                }
            }, "scanFromUri").start();
        }
    }
  12. CustomScanActivityonCreate 方法中调用 checkCameraPermission 方法检查相机权限。

    checkCameraPermission();
  13. CustomScanActivityonPauseonResumeonDestroyonRequestPermissionsResultonActivityResult 方法中分别添加如下内容。

    @Override
    public void onPause() {
        super.onPause();
        isPaused = true;
        if (isScanning) {
            stopScan();
        }
    }
    
    @Override
    public void onResume() {
        super.onResume();
        isPaused = false;
        if (!isFirstStart && isPermissionGranted) {
            startScan();
        }
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        mpScanner.release();
    }   
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE_PERMISSION) {
            int length = Math.min(permissions.length, grantResults.length);
            for (int i = 0; i < length; i++) {
                if (TextUtils.equals(permissions[i], Manifest.permission.CAMERA)) {
                    if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                        Utils.toast(this, getString(R.string.camera_no_permission));
                    } else {
                        onPermissionGranted();
                    }
                    break;
                }
            }
        }
          @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (data == null) {
            return;
        }
        if (requestCode == REQUEST_CODE_PHOTO) {
            scanFromUri(data.getData());
        }
    }
    }
  14. customAndroidManifest.xml 文件中设置 CustomScanActivitycustom 的主入口。

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.mpaas.aar.demo.custom">
    
        <application>
            <meta-data
                android:name="com.google.android.actions"
                android:resource="@xml/actions" />
    
            <activity
                android:name=".CustomScanActivity"
                android:configChanges="orientation|keyboardHidden|navigation"
                android:exported="false"
                android:launchMode="singleTask"
                android:screenOrientation="portrait"
                android:theme="@android:style/Theme.NoTitleBar"
                android:windowSoftInputMode="adjustResize|stateHidden" />
        </application>
    
    </manifest>

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

  1. activity_main.xml 文件中,添加 Button,并设置 Button 的 ID 为 custom_ui_btn

     <Button
         android:id="@+id/custom_ui_btn"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="208dp"
         android:background="#108EE9"
         android:gravity="center"
         android:text="自定义 UI 下使用扫一扫"
         android:textColor="#ffffff"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintHorizontal_bias="0.0"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
  2. MainActivity 类中编写代码。添加 custom_ui_btn 按钮的单击事件。获取自定义 UI 界面,并使用自定义 UI 的扫码功能。代码如下所示:

    findViewById(R.id.custom_ui_btn).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 ScanHelper.getInstance().scan(MainActivity.this, new ScanHelper.ScanCallback() {
                     @Override
                     public void onScanResult(boolean isProcessed, Intent result) {
                         if (!isProcessed) {
                             // 扫码界面单击物理返回键或左上角返回键
                             return;
                         }
    
                         if (result == null || result.getData() == null) {
                             Toast.makeText(MainActivity.this, "扫码失败,请重试!", Toast.LENGTH_SHORT).show();
                             return;
                         }
                         new AlertDialog.Builder(MainActivity.this)
                                 .setMessage(result.getData().toString())
                                 .setPositiveButton(R.string.confirm, null)
                                 .create()
                                 .show();
                     }
                 });
             }
         });
  3. 编译运行工程后,单击 自定义 UI 下使用扫一扫 后即可使用自定义 UI 下的扫码功能。

  4. 扫描如下二维码,会弹出该二维码的信息。

    21