1 前言
、、、
我们的APP中一般都需要扫描二维码和条形码,基本上使用Zbar或者Zxing开源框架来实现,本文讲解zxing使用说明。
ZXing ("Zebra Crossing") barcode scanning library for Java, Android。
ZXing是我们经常使用的条形码、二维码生产和扫描开源项目。
1) ZXing源码路径
...
Zxing源码可以支持Android使用,但是Git 下的源码并没有完整的Android工程,我们可以自己新建一个工程,然后将相应的内容按照路径拷贝到我们的工程内部,基本上就可以运行成功了。以下问题的解决也是在这个工程里,具体可以下载
2) 整理后的源码
...
效果图如下:
如上图所示,竖屏时扫描不了barcode。下面主要介绍解决方法
2 解决方案
第一步:CameraManager.java 修改这个函数 getFramingRectInPreview
源代码如下;
public synchronized Rect getFramingRectInPreview() {
if (framingRectInPreview == null) {
Rect framingRect = getFramingRect();
if (framingRect == null) {
return null;
}
Rect rect = new Rect(framingRect);
Point cameraResolution = configManager.getCameraResolution();
Point screenResolution = configManager.getScreenResolution();
if (cameraResolution == null || screenResolution == null) {
// Called early, before init even finished
return null;
}
rect.left = rect.left * cameraResolution.x / screenResolution.x;
rect.right = rect.right * cameraResolution.x / screenResolution.x;
rect.top = rect.top * cameraResolution.y / screenResolution.y;
rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
framingRectInPreview = rect;
}
return framingRectInPreview;
}
修改后:
public synchronized Rect getFramingRectInPreview() {
if (framingRectInPreview == null) {
Rect framingRect = getFramingRect();
if (framingRect == null) {
return null;
}
Rect rect = new Rect(framingRect);
Point cameraResolution = configManager.getCameraResolution();
Point screenResolution = configManager.getScreenResolution();
if (cameraResolution == null || screenResolution == null) {
// Called early, before init even finished
return null;
}
if(screenResolution.x>screenResolution.y){
/**
* 横屏模式保持不变
*/
rect.left = rect.left * cameraResolution.x / screenResolution.x;
rect.right = rect.right * cameraResolution.x / screenResolution.x;
rect.top = rect.top * cameraResolution.y / screenResolution.y;
rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y;
framingRectInPreview = rect;
}else {
/**
* 竖屏模式 X y 兑换位置,一般 cameraResolution.x > cameraResolution.y
*/
rect.left = rect.left * cameraResolution.y / screenResolution.x;
rect.right = rect.right * cameraResolution.y / screenResolution.x;
rect.top = rect.top * cameraResolution.x / screenResolution.y;
rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
framingRectInPreview = rect;
}
}
return framingRectInPreview;
}
此时运行会出现错误,原因是高宽比不一致
Process: com.google.zxing.client.android, PID: 15752
java.lang.IllegalArgumentException: Crop rectangle does not fit within image data.
at com.google.zxing.PlanarYUVLuminanceSource.<init>(PlanarYUVLuminanceSource.java:50)
at com.google.zxing.client.android.camera.CameraManager.buildLuminanceSource(CameraManager.java:341)
at com.google.zxing.client.android.DecodeHandler.decode(DecodeHandler.java:79)
at com.google.zxing.client.android.DecodeHandler.handleMessage(DecodeHandler.java:59)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:176)
at com.google.zxing.client.android.DecodeThread.run(DecodeThread.java:108)2020-07-28 17:05:27.489 15752-15921/com.google.zxing.client.android I/Process: Sending signal. PID: 15752 SIG: 9
第二步: CameraManager.java 修改 buildLuminanceSource来解决第一步的问题。
public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
Rect rect = getFramingRectInPreview();
if (rect == null) {
return null;
// Go ahead and assume it's YUV rather than die.
return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height(), false);
}
修改后
public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
Rect rect = getFramingRectInPreview();
if (rect == null) {
return null;
}
/**
* 如果竖屏进行数据翻转
*/
Point screenResolution = configManager.getScreenResolution();
if(screenResolution.x<screenResolution.y) {
byte[] rotatedData = new byte[data.length];
int newWidth = height;
int newHeight = width;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
rotatedData[x * newWidth + newWidth - 1 - y] = data[x + y * width];
}
}
return new PlanarYUVLuminanceSource(rotatedData, newWidth, newHeight, rect.left, rect.top, rect.width(), rect.height(), false);
}
// Go ahead and assume it's YUV rather than die.
return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height(), false);
}
修改完成后,软件可以正常工作了,如下图所示
第三步,
参考文献中
在CameraConfigurationUtils.java文件中,需要在findBestPreviewSizeValue方法中将screenAspectRatio分情况设置,因为screenAspectRatio的要求总是大值比小值。
将
double screenAspectRatio = (double) screenResolution.x / (double) screenResolution.y;
修改为:
double screenAspectRatio;
if (screenResolution.x < screenResolution.y) { // 竖屏
screenAspectRatio = (double) screenResolution.y / (double) screenResolution.x;
} else {
screenAspectRatio = (double) screenResolution.x / (double) screenResolution.y;
}
这里没有看出来效果 ,需要跟踪下源码?具体说明如下:
//kyle 2020年7月31日 修改
/**
- 原来默认横屏,x>y
- 摄像头参数 width>height ,因此竖屏时 摄像头高宽比大于0 ,手机屏幕实际使用高宽比小于0 ,也能找到最佳参数,但是不一定是最好的,因此做如下修改
*/
double screenAspectRatio = screenResolution.x >screenResolution.y?(screenResolution.x / (double) screenResolution.y):(screenResolution.y / (double) screenResolution.x);
第四步,原参考文献中
在CameraConfigurationUtils.java文件中,删除如下代码,原因是对于镜头分辨率高,而屏幕分辨率低的手机,这段代码直接导致扫码插件采用较低的分辨率去生成用于解析的位图,所以直接去掉。然后你就会发现低分辨率的手机,对二维码、条码的识别率显著提高。
if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {
Point exactPoint = new Point(realWidth, realHeight);
Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
return exactPoint;
}
3 自定义CaptureActivity
zxing解码和captureactivity完全绑定,我根据原有代码设计了 BaseCaptureActivity,大家可以根据自己的需求,自定义view和自己处理解码后的操作。
abstract public class BaseCaptureActivity extends AppCompatActivity implements SurfaceHolder.Callback{
abstract public @LayoutRes int layoutId();
/**
* 主要是控制页面自定义部分内容
*/
abstract public void initView();
abstract public void onScanResult(Result rawResult);
.......
}
目前没有修改surfaceview 和viewfinder的名称,因此xml中名称不可以变
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.google.zxing.client.android.ViewfinderView
android:layout_centerHorizontal="true"
android:id="@+id/viewfinder_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
使用方法如下:
public class DeviceCaptureActivity extends BaseCaptureActivity {
@Override
public int layoutId() {
return R.layout.activity_device_capture;
}
@Override
public void initView() {
}
@Override
public void onScanResult(Result rawResult) {
Log.e("kzq", "onScanResult: "+rawResult);
Toast.makeText(this,"result:"+rawResult.getText(),Toast.LENGTH_LONG).show();
restartPreviewAfterDelay(500);
}
}
4 viewfinder 位置问题?
还有一个问题,zxing 默认扫描框居中,有需要的需要自己修改 CameraManager
/**
- 用于控制扫描框Y轴坐标,1是居中,2就是原来的top/2
- 这里其实是缩小倍数
*/
private int heightPercent=1;
public void setHeightPercent(int divisor){
if(divisor<=0)divisor=1;
this.heightPercent=divisor;
}
第二步,这个就是扫描窗口的位置大小
getFramingRect() int topOffset = (screenResolution.y - height) / 2;
修改为
int topOffset = (screenResolution.y - height) / 2/this.heightPercent;
dierbu
setManualFramingRect
int topOffset = (screenResolution.y - height) / 2/this.heightPercent;
设置private int heightPercent=2;