Use the scan feature with a custom UI

更新时间:
复制 MD 格式

This topic describes how to create a custom UI and add the scan feature to your project.

To use the scan feature with a custom UI, see the code sample.

This procedure includes the following four steps:

  1. Create a dependency project

  2. Create and define the UI in the dependency project

  3. Use the scan feature in the dependency project

  4. Call the scan feature with the custom UI from the main project

Procedure

Create a dependency project

  1. Click File > New > New Module.

  2. Select Android Library and click Next.

  3. Enter a Module name and click Finish.

Create and define the UI in the dependency project

  1. In the com.example.custom package of the custom module, create a widget package. In the widget package, add the APSurfaceTexture class that inherits from the SurfaceTexture class to obtain the image stream.

    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. In the widget package of the custom module, add the APTextureView class that inherits from the TextureView class to display the image stream.

    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. In the com.example.custom package, create the Utils class to implement image transformation.

    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);
                 // Non-transparent area
                 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. In the custom module, create the res/values/attrs.xml file and add the following code.

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
     <declare-styleable name="scan">
         <attr name="shadowColor" format="color" />
     </declare-styleable>
    </resources>
  5. In the res/drawable folder of the custom module, paste the following resource files.

    12

  6. Add a FinderView class that inherits from the View class to the widget package in custom. Then, add the following code to draw the scan window, its corners, and the surrounding shade.

    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();
        }
    }
    // Initialize the corner style of the scan window.
    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);
    }
    // Draw the corner style of the scan window.
    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);
    }
    // Draw the shade around the scan window.
    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);
    }
    
    /**
     * Determine the position of the scan window based on the position of 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;
    }
    // Set the corner color of the scan window.
    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. Add the RayView class to the widget package in custom, make it inherit from the ImageView class, and add the following code to implement the scanning ray feature.

    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);
    
        // Set the position of the scan window in 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. In the res directory of the custom module, create the layout/view_scan.xml file, and add the following code to define the layout of the scan page.

    <?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. In the widget package, add the ScanView class that inherits from the RelativeLayout class, and add the following code. This class handles the interaction between the scan-related View and the DPI engine.

    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;
        // Clipping box size = Grid animation box size * 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. In the layout folder of the custom module, create the activity_custom_scan.xml file and add the following code. This file defines the main interface for the custom scan feature.

    <?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>

Use the scan feature in the dependency project

  1. In the com.example.custom package of the custom module, add the ScanHelper class and add the following code. This class calls the scan feature and retrieves the scan callback result.

    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. In the com.example.custom package of the custom module, add the CustomScanActivity class that inherits from the Activity class. Set the immersive mode for the interface and create the View and Button objects that correspond to the resource files.

    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);
    
         // Set the immersive mode.
         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. Implement the feature to open the phone gallery.

    1. In CustomScanActivity, create the pickImageFromGallery method.

      private void pickImageFromGallery() {
       Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
       intent.setType("image/*");
       startActivityForResult(intent, REQUEST_CODE_PHOTO);
      }
    2. In the onCreate method, add a click event for gallery and call the pickImageFromGallery method.

       findViewById(R.id.gallery).setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               pickImageFromGallery();
           }
       });
  4. Implement the feature to switch the torch on or off.

    1. In CustomScanActivity, create the switchTorch method.

       private void switchTorch() {
           boolean torchOn = mpScanner.switchTorch();
           mTorchBtn.setSelected(torchOn);
       }
    2. In the onCreate method, add a click event for mTorchBtn and call the switchTorch method.

      mTorchBtn.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
                   switchTorch();
               }
           });
  5. In CustomScanActivity, create the notifyScanResult method and call it in onBackPressed.

     private void notifyScanResult(boolean isProcessed, Intent resultData) {
         ScanHelper.getInstance().notifyScanResult(isProcessed, resultData);
     }
    
     @Override
     public void onBackPressed() {
         super.onBackPressed();
         notifyScanResult(false, null);
     }
  6. In the onCreate method of CustomScanActivity, add a click event for back and call the onBackPressed method.

         findViewById(R.id.back).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 onBackPressed();
             }
         });
  7. In CustomScanActivity, create the initMPScanner method and use the setRecognizeType method of the mpScanner object to set the recognition type.

    private void initMPScanner() {
        mpScanner = new MPScanner(this);
        mpScanner.setRecognizeType(
                MPRecognizeType.QR_CODE,
                MPRecognizeType.BAR_CODE,
                MPRecognizeType.DM_CODE,
                MPRecognizeType.PDF417_CODE
        );
    }
  8. In CustomScanActivity, create the onScanSuccess method and implement the following code.

    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. In CustomScanActivity, create the initScanRect method to initialize the scan feature.

    1. Call the getCamera method of the mpScanner object to obtain the Camera object, and call the setScanRegion method of the mpScanner object to set the scan area.

      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) {
               // Preview magnification = Screen width / Clipping box width
               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. Use the setMPScanListener method of the mpScanner object to implement the scan listener feature.

      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. Use the setMPImageGrayListener method of the mpScanner object to implement the listener to detect the grayscale value of the image.

      mpScanner.setMPImageGrayListener(new MPImageGrayListener() {
          @Override
          public void onGetImageGray(int gray) {
              // Note: This callback may be executed multiple times in a dim environment.
              if (gray < MPImageGrayListener.LOW_IMAGE_GRAY) {
                  runOnUiThread(new Runnable() {
                      @Override
                      public void run() {
                          Utils.toast(CustomScanActivity.this, "The light is too dim. Turn on the flashlight.");
                      }
                  });
              }
          }
      });
      }
  10. In CustomScanActivity, create the startScan and stopScan methods to start and stop camera scanning.

    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. In CustomScanActivity, create the onPermissionGranted, checkCameraPermission, and scanFromUri methods.

    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. In the onCreate method of CustomScanActivity, call the checkCameraPermission method to check camera permissions.

    checkCameraPermission();
  13. In the onPause, onResume, onDestroy, onRequestPermissionsResult, and onActivityResult methods of CustomScanActivity, add the following content.

    @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. In the AndroidManifest.xml file of the custom module, set CustomScanActivity as the main entry point for the module.

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.mpaas.aar.demo.custom">
    
        <application>
            <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>

Call the scan feature with the custom UI from the main project

  1. In the activity_main.xml file, add a Button and set its ID to 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="Scan with Custom 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. In the MainActivity class, add a click event for the custom_ui_btn button. This event retrieves the custom UI and uses its scan feature. The code is as follows:

    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) {
                             // The physical Back button or the Back button in the upper-left corner is clicked on the scan screen.
                             return;
                         }
    
                         if (result == null || result.getData() == null) {
                             Toast.makeText(MainActivity.this, "Scan failed. Please retry.", Toast.LENGTH_SHORT).show();
                             return;
                         }
                         new AlertDialog.Builder(MainActivity.this)
                                 .setMessage(result.getData().toString())
                                 .setPositiveButton(R.string.confirm, null)
                                 .create()
                                 .show();
                     }
                 });
             }
         });
  3. Compile and run the project. Then, click Scan with Custom UI to open the custom scan interface.

  4. Scan a QR code. The information from the QR code is displayed.