0%

自定义二维码扫描

前言

项目中需要对二维码进行扫描,第一个想到的自然就是Zxing。于是去查看了Google的官方Demo:Github:Zxing Demo

发现Demo中包含的元素实在是过多,于是决定自己搭建一个简易的二维码扫描工具,方便实现自己的需求,可能其中很多优化都没有去做,但是胜在简和易。

多简易?直接拉到最底部,看下使用文档吧。

正文

二维码扫描的逻辑应当是:

  1. 打开摄像头并获取摄像头加载的图像流数据
  2. 不断的截取图像中扫描框内的数据(扫描框其实是为了提高二维码的精度和识别率)
  3. 将二维码数据进行灰度处理
  4. 解析数据

绘制扫描框

  1. 创建自定义View

    1
    2
    3
    4
    5
    6
    /**
    * 二维码解析的view
    * Created by yooking on 2020/6/28.
    * Copyright (c) 2020 yooking. All rights reserved.
    */
    public class ZXingView extends View{}
  1. 重写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public ZXingView(Context context) {
    super(context);
    }

    public ZXingView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    initView(context, attrs);
    }

    public ZXingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initView(context, attrs);
    }
  1. 其中initView(Context context, @Nullable AttributeSet attrs)方法用于加载自定义布局

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    private void initView(Context context, @Nullable AttributeSet attrs) {
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ZXingView);
    //扫描框 宽度
    decodeWidth = typedArray.getDimension(R.styleable.ZXingView_decode_width, decodeWidth);
    //扫描框 高度
    decodeHeight = typedArray.getDimension(R.styleable.ZXingView_decode_height, decodeHeight);
    //扫描框 左上角 Y轴点坐标
    decodeTop = typedArray.getDimension(R.styleable.ZXingView_decode_top, decodeTop);
    //扫描框 左上角 X轴点坐标
    decodeLeft = typedArray.getDimension(R.styleable.ZXingView_decode_left, decodeLeft);

    //扫描框 线条 长度
    frameLength = typedArray.getDimension(R.styleable.ZXingView_frame_length, frameLength);
    //扫描框 线条 宽度
    frameWidth = typedArray.getDimension(R.styleable.ZXingView_frame_width, frameWidth);
    //扫描框 线条 颜色
    frameColor = typedArray.getColor(R.styleable.ZXingView_frame_color, frameColor);

    //扫描线条 宽度
    scanLineWidth = typedArray.getDimension(R.styleable.ZXingView_line_width,scanLineWidth);
    //扫描线条 模糊度(越大越模糊)
    scanLineRadius = typedArray.getDimension(R.styleable.ZXingView_line_radius,scanLineRadius);

    typedArray.recycle();
    }
  1. 界面初始化完毕后,定义各项数值(重写onMeasure

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (getMeasuredWidth() != 0) {
    measuredWidth = getMeasuredWidth();
    measuredHeight = getMeasuredHeight();

    //当扫描框宽度大于整体布局宽度 或者扫描框宽度为0时,扫描框宽度为整体布局的3/4
    if (decodeWidth == 0f || decodeWidth > measuredWidth) {
    decodeWidth = measuredWidth * 3 / 4f;
    }
    //当扫描框高度为0时,扫描框高度与宽度相等
    if (decodeHeight == 0f) {
    decodeHeight = decodeWidth * 1f;
    }
    //当扫描框高度超过整体布局高度时,扫描框高度为整体布局的3/4
    if (decodeHeight > measuredHeight) {
    decodeHeight = measuredHeight * 3 / 4f;
    }

    //当扫描框与左边间距为-1时,扫描框居中
    if (decodeLeft == -1f) {
    decodeLeft = (measuredWidth - decodeWidth) / 2f;
    }

    //当扫描框超出整体布局宽度时,扫描框居中
    if (decodeLeft + decodeWidth > measuredWidth) {
    decodeWidth = measuredWidth * 3 / 4f;
    decodeLeft = (measuredWidth - decodeWidth) / 2f;
    }

    //当扫描框与上方间距为-1时,扫描框距离上方为1/6整体高度
    if (decodeTop == -1f) {
    decodeTop = measuredHeight / 6f;
    }

    //当扫描框超出整体布局高度时,扫描框距离上方为1/6整体高度
    if (decodeTop + decodeHeight > measuredHeight) {
    decodeHeight = measuredHeight * 3 / 4f;
    decodeTop = (measuredHeight - decodeHeight) / 2f;
    }
    }
    }
  1. 开始绘制扫描框:

    初始化画笔:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    private void initPaint() {
    if (paint == null) {
    paint = new Paint();
    paint.setColor(frameColor);
    // paint.setStrokeWidth(frameWidth);
    }

    if (maskPaint == null) {
    maskPaint = new Paint();
    maskPaint.setColor(getResources().getColor(R.color.color_aa000000));
    }

    if (scanPaint == null) {
    scanPaint = new Paint();
    scanPaint.setColor(frameColor);
    scanPaint.setShadowLayer(scanLineRadius, 0F, 0F, frameColor);
    }
    }

    界面绘制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    initPaint();

    //绘制上方蒙版部分
    canvas.drawRect(0, 0, measuredWidth, decodeTop, maskPaint);
    //绘制左侧蒙版部分
    canvas.drawRect(0, decodeTop, decodeLeft, decodeTop + decodeHeight, maskPaint);
    //绘制右侧蒙版部分
    canvas.drawRect(decodeLeft + decodeWidth, decodeTop, measuredWidth, decodeTop + decodeHeight, maskPaint);
    //绘制下方蒙版部分
    canvas.drawRect(0, decodeTop + decodeHeight, measuredWidth, measuredHeight, maskPaint);

    //绘制方框

    //左上
    canvas.drawRect(decodeLeft, decodeTop, decodeLeft + frameLength, decodeTop + frameWidth, paint);
    canvas.drawRect(decodeLeft, decodeTop, decodeLeft + frameWidth, decodeTop + frameLength, paint);

    //右上
    canvas.drawRect(decodeLeft + decodeWidth - frameLength, decodeTop, decodeLeft + decodeWidth, decodeTop + frameWidth, paint);
    canvas.drawRect(decodeLeft + decodeWidth - frameWidth, decodeTop, decodeLeft + decodeWidth, decodeTop + frameLength, paint);

    //左下
    canvas.drawRect(decodeLeft, decodeTop + decodeHeight - frameLength, decodeLeft + frameWidth, decodeTop + decodeHeight, paint);
    canvas.drawRect(decodeLeft, decodeTop + decodeHeight - frameWidth, decodeLeft + frameLength, decodeTop + decodeHeight, paint);

    //右下
    canvas.drawRect(decodeLeft + decodeWidth - frameWidth, decodeTop + decodeHeight - frameLength, decodeLeft + decodeWidth, decodeTop + decodeHeight, paint);
    canvas.drawRect(decodeLeft + decodeWidth - frameLength, decodeTop + decodeHeight - frameWidth, decodeLeft + decodeWidth, decodeTop + decodeHeight, paint);

    //在方框内绘制扫描线条
    float moveY = decodeHeight * updateDuration / scanDuration;
    if (scanLineY == 0) {
    scanLineY = decodeTop;
    }
    scanLineY += moveY;
    if (scanLineY > decodeTop + decodeHeight) {
    scanLineY = decodeTop;
    }
    canvas.drawRect(decodeLeft, scanLineY, decodeLeft + decodeWidth, scanLineY + scanLineWidth, scanPaint);

    postInvalidateDelayed(updateDuration, (int) decodeLeft, (int) decodeTop, (int) (decodeLeft + decodeWidth), (int) (decodeTop + decodeHeight));
    }

打造Activity

  1. 创建AbstractZXingActivity抽象类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    /**
    * ZXing类 Activity类
    * Created by yooking on 2020/6/29.
    * Copyright (c) 2020 yooking. All rights reserved.
    */
    public abstract class AbstractZXingActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(getLayoutId());
    surfaceView = findViewById(getSurfaceId());
    holder = surfaceView.getHolder();

    holder.addCallback(holderCallback() == null ? defHolderCallback() : holderCallback());

    initCreate(savedInstanceState);
    }

    /**
    * 抽象方法 获取布局Id
    */
    public abstract @LayoutRes
    int getLayoutId();

    /**
    * 抽象方法 获取SurfaceViewId
    */
    public abstract @IdRes
    int getSurfaceId();

    public abstract void initCreate(@Nullable Bundle savedInstanceState);

    /**
    * 如果需要,可以自定义SurfaceHolder.Callback
    */
    public abstract SurfaceHolder.Callback holderCallback();

    /**
    * 如无需要,{@link this#holderCallback} return null即可
    */
    public SurfaceHolder.Callback defHolderCallback() {
    return new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
    CameraManager.getInstance().openCamera();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    try {
    CameraManager.getInstance().bindHolder(holder);
    CameraManager.getInstance().startPreview();

    //解析图像属于轻耗时操作,可能会导致界面卡顿,所以使用异步线程执行
    CameraManager.getInstance().cameraPreviewCallback(new Camera.PreviewCallback() {
    @Override
    public void onPreviewFrame(final byte[] data, final Camera camera) {
    if (!isNeedDecode) return;//如果不需要扫描直接return - (防止重复扫描同个二维码)

    long now = new Date().getTime();
    if (now - lastTime > shotDuration) {//设定经过duration时间后才会进行第二次二维码扫描(如果上次没成功的话)
    lastTime = now;
    AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
    //图像的灰度处理
    Bitmap bitmap = BitmapUtils.toGrayScale(screenshot(data, camera));
    String decodeStr;
    try {
    //解析二维码
    decodeStr = ZXingUtils.decode2String(bitmap);
    } catch (Exception e) {
    e.printStackTrace();
    decodeStr = null;
    }
    //二维码解析成功,回收bitmap
    if (bitmap != null)
    bitmap.recycle();
    if (callback != null && decodeStr != null && !decodeStr.isEmpty()) {
    callback.success(decodeStr);
    isNeedDecode = false;
    } else {
    //扫描不到数据,相机重新对焦
    CameraManager.getInstance().startFocus();
    }
    }
    });
    }
    }
    });
    } catch (
    IOException e) {
    e.printStackTrace();
    }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    CameraManager.getInstance().release();
    }
    };
    }
    }

结语

余下代码请在Demo中查阅,如有疑问,欢迎留言,使用方法请查看github:ReadMe

Github:Zxing Demo By yooking

------------本文结束感谢您的阅读------------

Thank you for your accept!