1. 程式人生 > >二維碼的掃描和生成(zxing-android-embedded)的基礎使用

二維碼的掃描和生成(zxing-android-embedded)的基礎使用

簡述:這個部落格主要記載zxing-android-embedded的簡單使用,如何替換相機的佈局,如何去掉生成二維碼的空白等等一系列問題.

zxing-android-embedded的使用

  1.首先新增依賴

    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.journeyapps:zxing-android-embedded:3.6.0'

2.生成二維碼

    private void createQrCode(){
        BarcodeEncoder barcodeEncoder = new BarcodeEncoder();
        try {
            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, 400, 400);
            bitMatrix =deleteWhite(bitMatrix); //刪除二維碼周邊的空白處 
            qrCodeBitmap=barcodeEncoder.createBitmap(bitMatrix);
            qrCodeImg.setImageBitmap(qrCodeBitmap);
        } catch (WriterException e) {
            e.printStackTrace();
        }
    }

3.掃描二維碼

   /**
     * 掃描二維碼
     */
    private void scanQrCode(){
        IntentIntegrator intentIntegrator = new IntentIntegrator(this);
        intentIntegrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE); //設定掃描的型別
        intentIntegrator.setOrientationLocked(false);  //方向鎖定
        intentIntegrator.setCaptureActivity(CustomCaptureActivity.class);
        intentIntegrator.setCameraId(0); //前置相機還是後置相機
        intentIntegrator.setBeepEnabled(false); //是否發出成功的聲音
        intentIntegrator.setBarcodeImageEnabled(true);
        intentIntegrator.initiateScan();
    }

方向鎖定的話,你還需要設定CaptureActivity,假如你是按依賴新增的而不是匯入原始碼,那麼需要

public class CustomCaptureActivity extends CaptureActivity {
}

在AndroidManifest裡面設定

<activity android:name=".custom.CustomCaptureActivity"
            android:screenOrientation="fullSensor"
            tools:replace="screenOrientation"/>

在掃描完成之後,他會自動返回到onActivityResult()函式裡面

  @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
        if(result != null) {
            if(result.getContents() == null) {
                Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

這裡的result.getContents()就是二維碼內含的值


刪除二維碼生成時候的周圍的空白

zing-android-embedde原始生成的二維碼周圍會有很多的空白,所以自己需要刪除空白

 /**
     * 刪除二維碼的空白區域
     * @param matrix
     * @return
     */
    private static BitMatrix deleteWhite(BitMatrix matrix)
    {
        int[] rec = matrix.getEnclosingRectangle();
        //數字7 代表了留的空白區域的大小
        int resWidth = rec[2] + 10;
        int resHeight = rec[3] + 10;
        BitMatrix resMatrix = new BitMatrix(resWidth, resHeight);
        resMatrix.clear();
        for (int i = 10; i < resWidth; i++)
        {
            for (int j = 10; j < resHeight; j++)
            {
                if(matrix.get(i+rec[0],j+rec[1]))
                    resMatrix.set(i,j);
            }
        }
        return resMatrix;
    }


自定義二維碼掃描時候掃描的大小

因為zxing-android-embedded生成的介面比較難看,所以一般需要修改原生的介面。一般是修改原始碼或者是繼承

本文因為是採用依賴導成jar進來的,所以採用的是繼承的方式。

public class CustomCaptureActivity extends CaptureActivity {

    private ImageView oldBackImg;

    private CustomDecoratedBarcodeView customDecoratedBarcodeView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        oldBackImg = findViewById(R.id.oldBackImg);
        oldBackImg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });
    }

    @Override
    protected DecoratedBarcodeView initializeContent(){
        setContentView(R.layout.custom_activity_zxing_capture);
        customDecoratedBarcodeView= findViewById(R.id.zxing_custom_barcode_scanner);
        return customDecoratedBarcodeView;
    }
}

最主要的是inittializeContent()方法,這個方法可以設定除了掃描以外的介面,比如加一個標題欄等等

DecoratedBarcodeView 就是掃描最重要的組成部分,他由兩個View組成,第一個BarcodeView就是掃描的視窗,

第二個Viewfinder有效果。 所以我們如果想繪製掃描的介面可以重寫Viewfineder的onDraw()方法

package backup.wingos.com.wingbackup.custom;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;

import com.journeyapps.barcodescanner.ViewfinderView;

import backup.wingos.com.wingbackup.R;

/**
 * Created by xiongbo on 2018/11/14.
 */

public class CustomViewfinderView extends ViewfinderView {

    private static  final String TAG = "CustomViewfinderView";

    /**
     * 畫相機四個角落的值
     */
    private Paint customPaint;
    /**
     * 獲取相機的區域
     */
    private Rect frame;
    /**
     * 修改這個值 可以修改角落繪製的顏色
     */
    private int color;
    /**
     * 四個角落的寬度
     */
    private int cornerWidth;
    /**
     * 四個角落的厚度
     */
    private int cornerHeight;
    /**
     * 你需要在相機下面顯示的文字
     */
    private String text;
    /**
     * 中間橫線豎直的位置
     */
    private int direction;
    /**
     * 代表了第一次繪製
     */
    private boolean isFirst;
    /**
     * 是否獲取二維碼的結果
     */
    private boolean isResult;
    /**
     * 是否退出執行緒
     */
    private boolean isExit;

    private Thread thread;

    public CustomViewfinderView(Context context, AttributeSet attrs) {
        super(context, attrs);
        customPaint = new Paint();
        customPaint.setStyle(Paint.Style.FILL);
        color=getResources().getColor(R.color.colorPrimary);
        customPaint.setAntiAlias(true);
        cornerWidth=50;
        cornerHeight=8;
        text="Scan the QR code from new phone";
        isFirst = true;
        isResult=false;
        isExit=false;
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(!isExit){
                    setDirection(direction+2);
                    try {
                        thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.refreshSizes();
        if (framingRect == null || previewFramingRect == null) {
            return;
        }
        frame = framingRect;
        if(isFirst){
            isFirst=false;
            direction=frame.top+10;
        }
        customPaint.setColor(color);
        customPaint.setStrokeWidth((float) 1.5);
        /**
         * 繪製四條邊框
         */
        canvas.drawLine(frame.left,frame.top,frame.right,frame.top,customPaint);
        canvas.drawLine(frame.left,frame.top,frame.left,frame.bottom,customPaint);
        canvas.drawLine(frame.right,frame.top,frame.right,frame.bottom,customPaint);
        canvas.drawLine(frame.right,frame.bottom,frame.left,frame.bottom,customPaint);

        final int width = canvas.getWidth();
        final int height = canvas.getHeight();
        // Draw the exterior (i.e. outside the framing rect) darkened
        paint.setColor(resultBitmap != null ? resultColor : maskColor);
        canvas.drawRect(0, 0, width, frame.top, paint);
        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
        canvas.drawRect(0, frame.bottom + 1, width, height, paint);

        if (resultBitmap != null) {
            // Draw the opaque result bitmap over the scanning rectangle
            paint.setAlpha(CURRENT_POINT_OPACITY);
            canvas.drawBitmap(resultBitmap, null, frame, paint);
            isResult=true;
        } else {
            customPaint.setAlpha(100);
            Rect rect = new Rect(frame.left,frame.top,frame.right, direction);
            canvas.drawRect(rect,customPaint);
            customPaint.setAlpha(255);
            customPaint.setStrokeWidth(3);
            canvas.drawLine(frame.left,direction,frame.right,direction,customPaint);
            if(direction>=frame.bottom){
                direction=frame.top;
            }
        }
        if(isResult){
            isExit=true;
        }else{
            isExit=false;
        }


        drawCorner(1,canvas);
        drawCorner(2,canvas);
        drawCorner(3,canvas);
        drawCorner(4,canvas);
        drawText(text,canvas);
    }

    public void setColor(int color){
        this.color=color;
        invalidate();
    }

    /**
     * 繪製四個角落 type: 1--左上 2--右上 3--左下 4--右下
     */
    private void drawCorner(int type,Canvas canvas){
        Rect verticalRect=null;
        Rect horizontalRect =null;
        if(type==1){
            horizontalRect = new Rect(frame.left,frame.top,frame.left+cornerWidth,frame.top+cornerHeight);
            verticalRect = new Rect(frame.left,frame.top,frame.left+cornerHeight,frame.top+cornerWidth);
        }else if(type==2){
            horizontalRect = new Rect(frame.right-cornerWidth,frame.top,frame.right,frame.top+cornerHeight);
            verticalRect = new Rect(frame.right-cornerHeight,frame.top,frame.right,frame.top+cornerWidth);
        }else if(type==3){
            horizontalRect = new Rect(frame.left,frame.bottom-cornerWidth,frame.left+cornerHeight,frame.bottom);
            verticalRect = new Rect(frame.left,frame.bottom-cornerHeight,frame.left+cornerWidth,frame.bottom);
        }else{
            horizontalRect = new Rect(frame.right-cornerWidth,frame.bottom-cornerHeight,frame.right,frame.bottom);
            verticalRect = new Rect(frame.right-cornerHeight,frame.bottom-cornerWidth,frame.right,frame.bottom);
        }
        canvas.drawRect(horizontalRect,customPaint);
        canvas.drawRect(verticalRect,customPaint);
    }

    public void setText(String text){
        this.text=text;
    }

    private void drawText(String text,Canvas canvas){
        customPaint.setColor(getResources().getColor(R.color.colorWhite));
        customPaint.setTextSize(50);
        canvas.drawText(text,frame.left-30,frame.bottom+100,customPaint);
    }

    private void setDirection(int direction){
        this.direction=direction;
        invalidate();
    }

    public void stopThread(){
        isExit=true;
        if(thread!=null){
            thread=null;
        }
    }

}
首先我們的framingRect 代表了視窗區域,我們繪製的時候不能繪製在視窗的區域裡面,要不然會攔住視窗,因為Viewfinder是繪製在BarcodeView的上方的.修改視窗的大小,其實是有屬性的,他們自定義View的style如下


<?xml version="1.0" encoding="UTF-8"?>
<resources>

  <declare-styleable name="zxing_view">
    <attr name="zxing_scanner_layout" format="reference"/>
  </declare-styleable>

  <declare-styleable name="zxing_camera_preview">
    <attr name="zxing_framing_rect_width" format="dimension" /> //視窗大小
    <attr name="zxing_framing_rect_height" format="dimension" />
    <attr name="zxing_use_texture_view" format="boolean" />
    <attr name="zxing_preview_scaling_strategy" format="enum">
      <enum name="centerCrop" value="1" />
      <enum name="fitCenter" value="2" />
      <enum name="fitXY" value="3" />
    </attr>
  </declare-styleable>

  <declare-styleable name="zxing_finder">
    <attr name="zxing_possible_result_points" format="color"/>
    <attr name="zxing_result_view" format="color"/>
    <attr name="zxing_viewfinder_laser" format="color"/>
    <attr name="zxing_viewfinder_mask" format="color"/> //繪製背景的顏色
  </declare-styleable>

</resources>

我們自定義的話,可以把這個style複製到自己專案裡面。也可以直接用;只要在BarcodeView設定屬性就好了

  <backup.wingos.com.wingbackup.custom.CustomBarcodeView
        android:id="@+id/zxing_custom_barcode_surface"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:zxing_framing_rect_height="240dp"
        app:zxing_framing_rect_width="240dp" />


    <backup.wingos.com.wingbackup.custom.CustomViewfinderView
        android:id="@+id/zxing_custom_viewfinder_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:zxing_viewfinder_mask="#757575" />

這樣就完成效果了。而且原生的話,關閉會有一定的延時效果,所以我們可以把這個延時關閉掉,影響使用者體驗

    /**
     * 造成了延時效果
     * 本來為 2000000000 修改成了 10000000
     */
    @Override
    public void pauseAndWait() {
        CameraInstance instance = getCameraInstance();
        pause();
        long startTime = System.nanoTime();
        while(instance != null && !instance.isCameraClosed()) {
            if(System.nanoTime() - startTime > 10000000) {
                // Don't wait for longer than 2 seconds
                break;
            }
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                break;
            }
        }
    }
這需要在CustomBarcodeView裡面修改的,因為原生的BarcodeView在退出的時候會阻塞主執行緒2s鍾。