全部產品
Search
文件中心

Mobile Platform as a Service:自訂 UI 下使用掃碼功能

更新時間:Jul 13, 2024

本文將引導您繪製自訂 UI 介面並將自訂 UI 掃碼的能力添加到工程中。

如需在自訂 UI 下使用掃碼功能,請參考 程式碼範例

該過程主要分為以下四個步驟:

  1. 建立依賴工程

  2. 在依賴工程中建立定義 UI 介面

  3. 在依賴工程中使用掃碼功能

  4. 在主工程中調用自訂 UI 下的掃碼功能

操作步驟

建立依賴工程

  1. 單擊 File > New > New Moduleimage.png

  2. 選擇 Android Library,單擊 Nextimage.png

  3. 輸入 Module name,單擊 Finishimage.png

在依賴工程中建立定義 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>
            <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 下的掃碼功能。image.pngimage.png

  4. 掃描二維碼,會彈出該二維碼的資訊。