android开发二维码扫描
最近项目开发中,遇到了二维码的问题!于是就去Google,搜索结果提及最多的就是ZXing了!当然这也是Google推荐的!
ZXingGithub地址:https://code.google.com/p/zxing/
但是ZXing存在的问题也很明显:包太大,识别速度太慢,复杂的样式难以修改因此一些在ZXing基础上二次开发的库就出现了,其中比较著名的barcodescanner:https://github.com/dm77/barcodescanner:
barcodescanner简化了ZXing的集成和二次定制难度,
==方便快速集成和开发,并且扫描性能和结果比较稳定,个人感觉barcodescanner的特点:扫描速度快,样式很难定制(对UI需求不高的,可以使用),最重要的一个问题:使用barcodescaaner进入扫码页面的时候会整个页面会出来的比较卡,因此会有类似于灰屏的东西,最终也放弃了。
barcodescaanerDemo运行图:
下面就是今天的主题了,即小白在项目中最后选择的二维码扫描库:BGAQRCode-Android
Github地址:https://github.com/bingoogolapple/BGAQRCode-Android
BGAQRCode-Android是在barcodescanner基础上修改的,修改幅度较大,基本上可以满足大部分需求:包括扫码类型,效率,UI,当然他也有ZXing和ZBar两个依赖可供选择。
1. BGAQRCode-Android的功能介绍
ZXing 生成可自定义颜色、带 logo 的二维码 ZXing 扫描二维码 ZXing 识别图库中的二维码图片 可以设置用前置摄像头扫描 可以控制闪光灯,方便夜间使用 可以定制各式各样的扫描框 可定制全屏扫描或只识别扫描框区域内的二维码 ZBar 扫描二维码「扫描中文会有乱码,如果对中文有要求,请使用 ZXing
2. Gradle集成
ZXing:
dependencies {
compile 'com.google.zxing:core:3.2.1'
compile 'cn.bingoogolapple:bga-qrcodecore:1.1.7@aar'
compile 'cn.bingoogolapple:bga-zxing:1.1.7@aar'
}
ZBar
dependencies {
compile 'cn.bingoogolapple:bga-qrcodecore:1.1.7@aar'
compile 'cn.bingoogolapple:bga-zbar:1.1.7@aar'
}
3. 布局文件中的使用
ZXing
<cn.bingoogolapple.qrcode.zxing.ZXingView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:qrcv_animTime="300"
app:qrcv_barCodeTipText="将条码放入框内,即可自动扫描"
app:qrcv_barcodeRectHeight="40dp"
app:qrcv_borderColor="@color/ygkj_c1_1"
app:qrcv_borderSize="0.1dp"
app:qrcv_cornerColor="@color/ygkj_c1_1"
app:qrcv_cornerLength="20dp"
app:qrcv_cornerSize="3dp"
app:qrcv_isBarcode="false"
app:qrcv_isCenterVertical="false"
app:qrcv_isOnlyDecodeScanBoxArea="true"
app:qrcv_isScanLineReverse="true"
app:qrcv_isShowDefaultGridScanLineDrawable="false"
app:qrcv_isShowDefaultScanLineDrawable="true"
app:qrcv_isShowTipBackground="false"
app:qrcv_isShowTipTextAsSingleLine="false"
app:qrcv_isTipTextBelowRect="true"
app:qrcv_maskColor="#de000000"
app:qrcv_qrCodeTipText="@string/cll_bike_scan_prompt"
app:qrcv_rectWidth="240dp"
app:qrcv_scanLineColor="@color/ygkj_c1_1"
app:qrcv_scanLineMargin="0dp"
app:qrcv_scanLineSize="0.5dp"
app:qrcv_tipTextColor="#80ffffff"
app:qrcv_tipTextSize="14sp"
app:qrcv_toolbarHeight="40dp"
app:qrcv_topOffset="80dp" />
ZBar
<cn.bingoogolapple.qrcode.zbar.ZBarView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:qrcv_animTime="300"
app:qrcv_barCodeTipText="将条码放入框内,即可自动扫描"
app:qrcv_barcodeRectHeight="40dp"
app:qrcv_borderColor="@color/ygkj_c1_1"
app:qrcv_borderSize="0.1dp"
app:qrcv_cornerColor="@color/ygkj_c1_1"
app:qrcv_cornerLength="20dp"
app:qrcv_cornerSize="3dp"
app:qrcv_isBarcode="false"
app:qrcv_isCenterVertical="false"
app:qrcv_isOnlyDecodeScanBoxArea="true"
app:qrcv_isScanLineReverse="true"
app:qrcv_isShowDefaultGridScanLineDrawable="false"
app:qrcv_isShowDefaultScanLineDrawable="true"
app:qrcv_isShowTipBackground="false"
app:qrcv_isShowTipTextAsSingleLine="false"
app:qrcv_isTipTextBelowRect="true"
app:qrcv_maskColor="#de000000"
app:qrcv_qrCodeTipText="@string/cll_bike_scan_prompt"
app:qrcv_rectWidth="240dp"
app:qrcv_scanLineColor="@color/ygkj_c1_1"
app:qrcv_scanLineMargin="0dp"
app:qrcv_scanLineSize="0.5dp"
app:qrcv_tipTextColor="#80ffffff"
app:qrcv_tipTextSize="14sp"
app:qrcv_toolbarHeight="40dp"
app:qrcv_topOffset="80dp" />
4. 自定义属性说明
属性名 | 说明 | 默认值 |
---|---|---|
qrcv_topOffset | 扫描框距离 toolbar 底部的距离 | 90dp |
qrcv_cornerSize | 扫描框边角线的宽度 | 3dp |
qrcv_cornerLength | 扫描框边角线的长度 | 20dp |
qrcv_cornerColor | 扫描框边角线的颜色 | @android:color/white |
qrcv_rectWidth | 扫描框的宽度 | 200dp |
qrcv_barcodeRectHeight | 条码扫样式描框的高度 | 140dp |
qrcv_maskColor | 除去扫描框,其余部分阴影颜色 | 33FFFFFF |
qrcv_scanLineSize | 扫描线的宽度 | 1dp |
qrcv_scanLineColor | 扫描线的颜色「扫描线和默认的扫描线图片的颜色」 | @android:color/white |
qrcv_scanLineMargin | 扫描线距离上下或者左右边框的间距 | 0dp |
qrcv_isShowDefaultScanLineDrawable | 是否显示默认的图片扫描线「设置该属性后 qrcv_scanLineSize 将失效,可以通过 qrcv_scanLineColor | 设置扫描线的颜色,避免让你公司的UI单独给你出特定颜色的扫描线图片」 |
qrcv_customScanLineDrawable | 扫描线的图片资源「默认的扫描线图片样式不能满足你的需求时使用,设置该属性后 qrcv_isShowDefaultScanLineDrawable、qrcv_scanLineSize、qrcv_scanLineColor 将失效」 | null |
qrcv_borderSize | 扫描边框的宽度 | 1dp |
qrcv_borderColor | 扫描边框的颜色 | @android:color/white |
qrcv_animTime | 扫描线从顶部移动到底部的动画时间「单位为毫秒」 | 1000 |
qrcv_isCenterVertical | 扫描框是否垂直居中,该属性为true时会忽略 qrcv_topOffset 属性 | false |
qrcv_toolbarHeight | Toolbar 的高度,通过该属性来修正由 Toolbar 导致扫描框在垂直方向上的偏差 | 0dp |
qrcv_isBarcode | 是否是扫条形码 | false |
qrcv_tipText | 提示文案 | null |
qrcv_tipTextSize | 提示文案字体大小 | 14sp |
qrcv_tipTextColor | 提示文案颜色 | @android:color/white |
qrcv_isTipTextBelowRect | 提示文案是否在扫描框的底部 | false |
qrcv_tipTextMargin | 提示文案与扫描框之间的间距 | 20dp |
qrcv_isShowTipTextAsSingleLine | 是否把提示文案作为单行显示 | false |
qrcv_isShowTipBackground | 是否显示提示文案的背景 | false |
qrcv_tipBackgroundColor | 提示文案的背景色 | 22000000 |
qrcv_isScanLineReverse | 扫描线是否来回移动 | true |
qrcv_isShowDefaultGridScanLineDrawable | 是否显示默认的网格图片扫描线 | false |
qrcv_customGridScanLineDrawable | 扫描线的网格图片资源 | null |
qrcv_isOnlyDecodeScanBoxArea | 是否只识别扫描框区域的二维码 | false |
5. BGAQRCode-Android使用API
这里不介绍识别本地图片的使用
QRCodeView
/**
* 设置扫描二维码的代理
*
* @param delegate 扫描二维码的代理
*/
public void setDelegate(Delegate delegate)
/**
* 显示扫描框
*/
public void showScanRect()
/**
* 隐藏扫描框
*/
public void hiddenScanRect()
/**
* 打开后置摄像头开始预览,但是并未开始识别
*/
public void startCamera()
/**
* 打开指定摄像头开始预览,但是并未开始识别
*
* @param cameraFacing Camera.CameraInfo.CAMERA_FACING_BACK or Camera.CameraInfo.CAMERA_FACING_FRONT
*/
public void startCamera(int cameraFacing)
/**
* 关闭摄像头预览,并且隐藏扫描框
*/
public void stopCamera()
/**
* 延迟1.5秒后开始识别
*/
public void startSpot()
/**
* 延迟delay毫秒后开始识别
*
* @param delay
*/
public void startSpotDelay(int delay)
/**
* 停止识别
*/
public void stopSpot()
/**
* 停止识别,并且隐藏扫描框
*/
public void stopSpotAndHiddenRect()
/**
* 显示扫描框,并且延迟1.5秒后开始识别
*/
public void startSpotAndShowRect()
/**
* 打开闪光灯
*/
public void openFlashlight()
/**
* 关闭散光灯
*/
public void closeFlashlight()
QRCodeView.Delegate 扫描二维码的代理
/**
* 处理扫描结果
*
* @param result
*/
void onScanQRCodeSuccess(String result)
/**
* 处理打开相机出错
*/
void onScanQRCodeOpenCameraError()
6. 项目中使用
效果截图:(图一图二分别是Demo中运行和扫描的结果,图三是项目中的截图(共享单车模块))
使用方式:
public class MainActivity extends AppCompatActivity implements QRCodeView.Delegate {
private QRCodeView qrCodeView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
qrCodeView = (QRCodeView) findViewById(R.id.zxingview);
qrCodeView.setDelegate(this);
}
@Override
public void onScanQRCodeSuccess(String result) {
Toast.makeText(this, result, Toast.LENGTH_LONG).show();
}
@Override
public void onScanQRCodeOpenCameraError() {
}
@Override
protected void onStart() {
super.onStart();
qrCodeView.startSpotAndShowRect();
}
@Override
protected void onStop() {
qrCodeView.stopCamera();
super.onStop();
}
@Override
protected void onDestroy() {
qrCodeView.onDestroy();
super.onDestroy();
}
}
7. 源码简单分析
startSpotDelay()方法打开相机并使用handler开始了一个任务
public void startSpotDelay(int delay) {
mSpotAble = true;
startCamera();
// 开始前先移除之前的任务
mHandler.removeCallbacks(mOneShotPreviewCallbackTask);
mHandler.postDelayed(mOneShotPreviewCallbackTask, delay);
}
startCamera()方法打开相机,如果异常则会给我们一个onScanQRCodeOpenCameraError()回调
/**
* 打开指定摄像头开始预览,但是并未开始识别
*
* @param cameraFacing
*/
public void startCamera(int cameraFacing) {
if (mCamera != null) {
return;
}
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int cameraId = 0; cameraId < Camera.getNumberOfCameras(); cameraId++) {
Camera.getCameraInfo(cameraId, cameraInfo);
if (cameraInfo.facing == cameraFacing) {
startCameraById(cameraId);
break;
}
}
}
private void startCameraById(int cameraId) {
try {
mCamera = Camera.open(cameraId);
mPreview.setCamera(mCamera);
} catch (Exception e) {
if (mDelegate != null) {
mDelegate.onScanQRCodeOpenCameraError();
}
}
}
而Handler执行的任务最终落到Camera类中的setOneShotPreviewCallback(QRCodeView.this)方法,这是一个native关键字修饰的方法
QrCodeView中还要一个回调需要我们去关心
@Override
public void onPreviewFrame(final byte[] data, final Camera camera) {
if (mSpotAble) {
cancelProcessDataTask();
mProcessDataTask = new ProcessDataTask(camera, data, this) {
@Override
protected void onPostExecute(String result) {
if (mSpotAble) {
if (mDelegate != null && !TextUtils.isEmpty(result)) {
try {
mDelegate.onScanQRCodeSuccess(result);
} catch (Exception e) {
}
} else {
try {
camera.setOneShotPreviewCallback(QRCodeView.this);
} catch (Exception e) {
}
}
}
}
}.perform();
}
}
onPreviewFrame()方法是Camera的内部类接口PreviewCallback中定义的,用来接收扫描像素数据
public interface PreviewCallback
{
/**
* Called as preview frames are displayed. This callback is invoked
* on the event thread {@link #open(int)} was called from.
*
* <p>If using the {@link android.graphics.ImageFormat#YV12} format,
* refer to the equations in {@link Camera.Parameters#setPreviewFormat}
* for the arrangement of the pixel data in the preview callback
* buffers.
*
* @param data the contents of the preview frame in the format defined
* by {@link android.graphics.ImageFormat}, which can be queried
* with {@link android.hardware.Camera.Parameters#getPreviewFormat()}.
* If {@link android.hardware.Camera.Parameters#setPreviewFormat(int)}
* is never called, the default will be the YCbCr_420_SP
* (NV21) format.
* @param camera the Camera service object.
*/
void onPreviewFrame(byte[] data, Camera camera);
};
我们发现,扫描成功的结果就是在这里回传给我们的:mDelegate.onScanQRCodeSuccess(result);ProcessDataTask是一个继承AsyncTask的类,在doInBackground中对扫描的结果进行解析(个人理解)
public class ProcessDataTask extends AsyncTask<Void, Void, String> {
private Camera mCamera;
private byte[] mData;
private Delegate mDelegate;
public ProcessDataTask(Camera camera, byte[] data, Delegate delegate) {
mCamera = camera;
mData = data;
mDelegate = delegate;
}
public ProcessDataTask perform() {
if (Build.VERSION.SDK_INT >= 11) {
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
execute();
}
return this;
}
public void cancelTask() {
if (getStatus() != Status.FINISHED) {
cancel(true);
}
}
@Override
protected void onCancelled() {
super.onCancelled();
mDelegate = null;
}
@Override
protected String doInBackground(Void... params) {
Camera.Parameters parameters = mCamera.getParameters();
Camera.Size size = parameters.getPreviewSize();
int width = size.width;
int height = size.height;
byte[] rotatedData = new byte[mData.length];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
rotatedData[x * height + height - y - 1] = mData[x + y * width];
}
}
int tmp = width;
width = height;
height = tmp;
try {
if (mDelegate == null) {
return null;
}
return mDelegate.processData(rotatedData, width, height, false);
} catch (Exception e1) {
try {
return mDelegate.processData(rotatedData, width, height, true);
} catch (Exception e2) {
return null;
}
}
}
public interface Delegate {
String processData(byte[] data, int width, int height, boolean isRetry);
}
}
onPreviewFrame()的调用又在Camera类中的内部类EventHandler中,贴出有用代码:
private class EventHandler extends Handler
{
private final Camera mCamera;
public EventHandler(Camera c, Looper looper) {
super(looper);
mCamera = c;
}
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case CAMERA_MSG_PREVIEW_FRAME:
PreviewCallback pCb = mPreviewCallback;
if (pCb != null) {
if (mOneShot) {
// Clear the callback variable before the callback
// in case the app calls setPreviewCallback from
// the callback function
mPreviewCallback = null;
} else if (!mWithBuffer) {
// We're faking the camera preview mode to prevent
// the app from being flooded with preview frames.
// Set to oneshot mode again.
setHasPreviewCallback(true, false);
}
pCb.onPreviewFrame((byte[])msg.obj, mCamera);
}
return;
}
}
}
8. 总结
二维码扫描流程
1. 初始化相机,设置一些相机参数
2. 绑定SurfaceView,在SurfaceView上显示预览图像
3. 获取相机的一帧图像
4. 对图像进行一定的预处理,只保留亮度信息,成为灰度图像
5. 对灰度图像进行二维码解析
6. 返回解析结果
7.
注意:
- Android 6.0权限问题,使用BGAQRCode-Android时,可以将识别区域设置为扫描框的区域
以上就是对自己在项目过程中使用的二维码的总结,对某些地方的理解也不是很到位,希望对大家有所帮助!欢迎指正!
更多推荐
所有评论(0)