一款好用的二維碼掃描組件

簡(jiǎn)介

之前項(xiàng)目中使用到掃描功能,那時(shí)邏輯業(yè)務(wù)和UI是完全耦合在一起,不好維護(hù),也難移植。趁著這次新項(xiàng)目中要使用掃一掃的功能,就將二維碼掃描單獨(dú)提出作為一個(gè)組件庫(kù),與業(yè)務(wù)完全分離。最終掃描結(jié)果通過(guò)回調(diào)的方式提供給調(diào)用者,用戶可以在自己的app中處理掃描結(jié)果。庫(kù)仿造Universal-Image-Loader進(jìn)行封裝,提供一個(gè)配置文件,可簡(jiǎn)單配置掃一掃界面的樣式,實(shí)現(xiàn)用戶UI定制。提供一個(gè)控制類(lèi),用戶通過(guò)其提供的接口與組件進(jìn)行交互,內(nèi)部實(shí)現(xiàn)相對(duì)于用戶都是透明的。實(shí)現(xiàn)效果如下圖所示:

具體實(shí)現(xiàn)

組件是調(diào)用zxing進(jìn)行二維碼的編解碼計(jì)算,這部分不是本文研究的重點(diǎn)。本文主要關(guān)注以下幾點(diǎn):

  • 掃描界面的繪制
  • 掃描結(jié)果如何回調(diào)給用戶
  • 組件是如何封裝的
  • 如何使用該組件

界面繪制

ViewfinderView是我們的掃描界面,實(shí)在onDraw方法中繪制。這里我直接上代碼,關(guān)鍵部分會(huì)有注釋

@Override
    public void onDraw(Canvas canvas) {
        //中間的掃描框,你要修改掃描框的大小,去CameraManager里面修改
        CameraManager.init(this.getContext().getApplicationContext());

        Rect frame = null;
        try {
            frame = CameraManager.get().getFramingRect();
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }


        if (frame == null) {
            return;
        }

        //獲取屏幕的寬和高
        int width = canvas.getWidth();
        int height = canvas.getHeight();

        paint.setColor(maskColor);

        //畫(huà)出掃描框外面的陰影部分,共四個(gè)部分,掃描框的上面到屏幕上面,掃描框的下面到屏幕下面
        //掃描框的左邊面到屏幕左邊,掃描框的右邊到屏幕右邊
        canvas.drawRect(0, 0, width, frame.top, paint); //上
        canvas.drawRect(0, frame.top, frame.left, frame.bottom - 1, paint);  //左
        canvas.drawRect(frame.right, frame.top, width, frame.bottom - 1, paint); //右
        canvas.drawRect(0, frame.bottom - 1, width, height, paint);

        paint.setColor(0xffffffff);
        canvas.drawLine(frame.left + 1, frame.top + 1, frame.right - 1, frame.top + 1, paint);
        canvas.drawLine(frame.left + 1,frame.top + 1,frame.left + 1,frame.bottom - 1, paint);
        canvas.drawLine(frame.left + 1,frame.bottom - 1,frame.right -1,frame.bottom - 1,paint);
        canvas.drawLine(frame.right -1,frame.top + 1,frame.right - 1,frame.bottom - 1,paint);

        if (resultBitmap != null) {
            // Draw the opaque result bitmap over the scanning rectangle
            paint.setAlpha(OPAQUE);
            canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);
        } else {

            //畫(huà)掃描框邊上的角,總共8個(gè)部分
            paint.setColor(angleColor);
            canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate,
                    frame.top + CORNER_WIDTH, paint);
            canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH, frame.top
                    + ScreenRate, paint);
            canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right,
                    frame.top + CORNER_WIDTH, paint);
            canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right, frame.top
                    + ScreenRate, paint);
            canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left
                    + ScreenRate, frame.bottom, paint);
            canvas.drawRect(frame.left, frame.bottom - ScreenRate,
                    frame.left + CORNER_WIDTH, frame.bottom, paint);
            canvas.drawRect(frame.right - ScreenRate, frame.bottom - CORNER_WIDTH,
                    frame.right, frame.bottom, paint);
            canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom - ScreenRate,
                    frame.right, frame.bottom, paint);

            // 如果設(shè)置了slideIcon,則顯示
            if(mSlideIcon != null){
//              mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.capture_add_scanning);

                BitmapDrawable bd = (BitmapDrawable) mSlideIcon;
                mBitmap = bd.getBitmap();

                //繪制中間的線,每次刷新界面,中間的線往下移動(dòng)SPEEN_DISTANCE
                if (mBitmap != null){
                    mBitmap = Bitmap.createScaledBitmap(mBitmap, frame.right - frame.left, mBitmap.getHeight(), true);
                }


                //初始化中間線滑動(dòng)的最上邊和最下邊
                if(!isFirst){
                    isFirst = true;
                    slideTop = frame.top + mBitmap.getHeight();
                    slideBottom = frame.bottom;
                }

                slideTop += SPEEN_DISTANCE;
                if(slideTop >= frame.bottom){
                    slideTop = frame.top + mBitmap.getHeight();
                }

                canvas.drawBitmap(mBitmap, frame.left, slideTop - mBitmap.getHeight(), paint);
            }else{
                //初始化中間線滑動(dòng)的最上邊和最下邊
                if(!isFirst){
                    isFirst = true;
                    slideTop = frame.top + MIDDLE_LINE_WIDTH;
                    slideBottom = frame.bottom;
                }

                slideTop += SPEEN_DISTANCE;
                if(slideTop >= frame.bottom){
                    slideTop = frame.top + MIDDLE_LINE_WIDTH;
                }

                canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH,frame.right - MIDDLE_LINE_PADDING, slideTop, paint);

            }

            // 畫(huà)掃描框下面的字
            paint.setColor(mTipColor);
            paint.setTextSize(TEXT_SIZE * density);
            paint.setTextAlign(Paint.Align.CENTER);
            paint.setTypeface(Typeface.create("System", Typeface.NORMAL));
            canvas.drawText(scanTip, width/2, (float) (frame.bottom + (float)mTipmMargin * density), paint);
            
            //只刷新掃描框的內(nèi)容,其他地方不刷新
            postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom);

        }
    }

其中中間的掃描框,是在CameraManager中實(shí)現(xiàn)。

public Rect getFramingRect() {
    Point screenResolution = configManager.getScreenResolution();
    if (framingRect == null) {
      if (camera == null) {
        return null;
      }

      if (screenResolution == null){
        return null;
      }

      int width;
      int height;
      int topOffset;
      int leftOffset;
      int rightOffset;

      mFrameRate = QrScanProxy.getInstance().getScanFrameRectRate();

      width = (int) (screenResolution.x * mFrameRate);
      if (width < MIN_FRAME_WIDTH) {
        width = MIN_FRAME_WIDTH;
      }
      height = width;
      leftOffset = (screenResolution.x - width) / 2;
      topOffset = DeviceUtil.dip2px(DEFAULT_FRAME_MARGIN_TOP);
      if((height + topOffset) > screenResolution.y){
        topOffset = screenResolution.y - height;
      }

      framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
      Log.d(TAG, "Calculated framing rect: " + framingRect);
    }
    return framingRect;
  }

掃描結(jié)果回調(diào)

在CaptureActivityHandler中,有一個(gè)handleMessage方法。掃描的二維碼信息會(huì)在這邊進(jìn)行分發(fā)。

 @Override
  public void handleMessage(Message message) {
    ...

    } else if (message.what == R.id.decode_succeeded) {
      Log.d(TAG, "Got decode succeeded message");
      state = State.SUCCESS;
      Bundle bundle = message.getData();
      Bitmap barcode = bundle == null ? null :
              (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);

      activity.handleDecode((Result) message.obj, barcode);
      ...

組件封裝

對(duì)外部調(diào)用者,提供QrScan.java和QrScanConfiguration.java,前者是組件提供給外部用于和組件交互的方法,后者用于配置組件的相關(guān)屬性。在組件內(nèi)部,有一個(gè)代理類(lèi)QrScanProxy.java,所有外部設(shè)置的屬性作用于內(nèi)部,都是通過(guò)這個(gè)類(lèi)進(jìn)行分發(fā)。具體實(shí)現(xiàn)大家可以參考源碼

使用

  • build.gradle配置
compile 'com.netease.scan:lib-qr-scan:1.0.0'
  • AndroidManifest配置
    // 設(shè)置權(quán)限
     <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
    
    // 注冊(cè)activity
    <activity android:name="com.netease.scan.ui.CaptureActivity"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.AppCompat.NoActionBar"/>
  • 初始化
    在需要使用此組件的Activity的onCreate方法中,或者在自定義Application的onCreate方法中初始化。
/**
 * @author hzzhengrui
 * @Date 16/10/27
 * @Description
 */
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

//        // 默認(rèn)配置
//        QrScanConfiguration configuration = QrScanConfiguration.createDefault(this);

        // 自定義配置
        QrScanConfiguration configuration = new QrScanConfiguration.Builder(this)
                .setTitleHeight(53)
                .setTitleText("來(lái)掃一掃")
                .setTitleTextSize(18)
                .setTitleTextColor(R.color.white)
                .setTipText("將二維碼放入框內(nèi)掃描~")
                .setTipTextSize(14)
                .setTipMarginTop(40)
                .setTipTextColor(R.color.white)
                .setSlideIcon(R.mipmap.capture_add_scanning)
                .setAngleColor(R.color.white)
                .setMaskColor(R.color.black_80)
                .setScanFrameRectRate((float) 0.8)
                .build();
        QrScan.getInstance().init(configuration);
    }
}
  • 啟動(dòng)掃描并處理掃描結(jié)果
QrScan.getInstance().launchScan(MainActivity.this, new IScanModuleCallBack() {
                    @Override
                    public void OnReceiveDecodeResult(final Context context, String result) {
                        mCaptureContext = (CaptureActivity)context;

                        AlertDialog dialog = new AlertDialog.Builder(mCaptureContext)
                                .setMessage(result)
                                .setCancelable(false)
                                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        dialog.dismiss();
                                        QrScan.getInstance().restartScan(mCaptureContext);
                                    }
                                })
                                .setPositiveButton("關(guān)閉", new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        dialog.dismiss();
                                        QrScan.getInstance().finishScan(mCaptureContext);
                                    }
                                })
                                .create();
                        dialog.show();
                    }
                });

最后附上源碼地址:https://github.com/yushiwo/QrScan

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容