最近项目开发中,遇到了二维码的问题!于是就去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_toolbarHeightToolbar 的高度,通过该属性来修正由 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时,可以将识别区域设置为扫描框的区域

以上就是对自己在项目过程中使用的二维码的总结,对某些地方的理解也不是很到位,希望对大家有所帮助!欢迎指正!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐