前言
项目中需要对二维码进行扫描,第一个想到的自然就是Zxing。于是去查看了Google的官方Demo:Github:Zxing Demo
发现Demo中包含的元素实在是过多,于是决定自己搭建一个简易的二维码扫描工具,方便实现自己的需求,可能其中很多优化都没有去做,但是胜在简和易。
多简易?直接拉到最底部,看下使用文档吧。
正文
二维码扫描的逻辑应当是:
- 打开摄像头并获取摄像头加载的图像流数据
- 不断的截取图像中扫描框内的数据(扫描框其实是为了提高二维码的精度和识别率)
- 将二维码数据进行灰度处理
- 解析数据
绘制扫描框
创建自定义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
2
3
4
5
6
7
8
9
10
11
12
13public 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);
}
其中
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
25private 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();
}
界面初始化完毕后,定义各项数值(重写
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18private 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
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
创建
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 {
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
int getLayoutId();
/**
* 抽象方法 获取SurfaceViewId
*/
public abstract
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() {
public void surfaceCreated(SurfaceHolder holder) {
CameraManager.getInstance().openCamera();
}
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() {
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() {
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();
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
CameraManager.getInstance().release();
}
};
}
}
结语
余下代码请在Demo中查阅,如有疑问,欢迎留言,使用方法请查看github:ReadMe
Github:Zxing Demo By yooking