Android集成zxing扫码框架

       我们知道zxing是一个强大的处理二维码和条形码等的开源库,本篇文章记录一下自己在项目中集成zxing开源库的过程。

导入依赖

implementation 'com.google.zxing:core:3.3.3'

申请权限

       在AndroidManifest中申请相应权限:







导入相关代码和资源文件

       导入的代码文件如下(源码在末尾):

Android集成zxing扫码框架_第1张图片

      相关的资源文件:

      1、在res/values下新建ids.xml文件,引入下面id:













      2、在res/values下新建attrs.xml文件,加入扫码框的属性,主要是ViewfinderView在使用:



    
    
    
    
        
        
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

       3、在res下新建raw目录,导入beep.mp3,实现扫码成功的滴滴音效,BeepManager在使用

       上面是一些比较重要的资源。

       然后介绍一下几个主要的类:

       1、ViewfinderView:自定义扫描框,代码如下,因为有注释,就不多说明了。

public final class ViewfinderView extends View {

    private static final long ANIMATION_DELAY = 10L;
    private static final int OPAQUE = 1;
    private static final int CORNER_INSIDE = 1;   //四个边角在扫描区内
    private static final int CORNER_OUTSIDE = 2;  //四个边角在扫描区外

    private Paint paint;
    //扫描区四个边角的颜色
    private int cornerColor;
    //扫描区边角的大小
    private float cornerSize;
    //扫描区边角的宽度
    private float cornerStrokeWidth;
    //边角的方向,在扫描区域内还是扫描区域外
    private int cornerPosition;
    //扫描线颜色
    private int lineColor;
    //扫描线高度
    private float lineHeight;
    //扫描线移动距离
    private float lineMoveDistance;
    //扫描区域宽度度
    private float frameWidth;
    //扫描区域高度
    private float frameHeight;
    //扫描区域中心位置的X坐标,默认正中间,在onLayout中设置
    private float frameCenterX;
    //扫描区域中心位置的Y坐标,默认正中间,在onLayout中设置
    private float frameCenterY;
    //扫描区域边框颜色
    private int frameColor;
    //扫描区域边框宽度
    private float frameStrokeWidth;
    //模糊区域颜色
    private int maskColor;
    //扫描点的颜色
    private int resultPointColor;
    //扫描区域提示文本
    private String labelText;
    //扫描区域提示文本颜色
    private int labelTextColor;
    //扫描区域提示文本字体大小
    private float labelTextSize;
    //扫描区域提示文本的边距
    private float labelTextMargin;

    public static int scannerStart = 0;
    public static int scannerEnd = 0;

    private Collection possibleResultPoints;
    private Collection lastPossibleResultPoints;

    // This constructor is used when the class is built from an XML resource.
    public ViewfinderView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //初始化自定义属性信息
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView);
        cornerColor = ta.getColor(R.styleable.ViewfinderView_corner_color, getResources().getColor(R.color.colorPrimary));
        cornerSize = ta.getDimension(R.styleable.ViewfinderView_corner_size, dp2px(context, 28));
        cornerStrokeWidth = ta.getDimension(R.styleable.ViewfinderView_corner_stroke_width, dp2px(context, 4));
        cornerPosition = ta.getInt(R.styleable.ViewfinderView_corner_position, CORNER_INSIDE);
        lineColor = ta.getColor(R.styleable.ViewfinderView_line_color, getResources().getColor(R.color.colorPrimary));
        lineHeight = ta.getDimension(R.styleable.ViewfinderView_line_height, dp2px(context, 3));
        lineMoveDistance = ta.getDimension(R.styleable.ViewfinderView_line_move_distance, dp2px(context, 2));
        frameWidth = ta.getDimension(R.styleable.ViewfinderView_frame_width, dp2px(context, 220));
        frameHeight = ta.getDimension(R.styleable.ViewfinderView_frame_height, dp2px(context, 220));
        frameCenterX = ta.getDimension(R.styleable.ViewfinderView_frame_centerX, -1);
        frameCenterY = ta.getDimension(R.styleable.ViewfinderView_frame_centerY, -1);
        frameColor = ta.getColor(R.styleable.ViewfinderView_frame_color, Color.parseColor("#90FFFFFF"));
        frameStrokeWidth = ta.getDimension(R.styleable.ViewfinderView_frame_stroke_width, dp2px(context, 0.2f));
        maskColor = ta.getColor(R.styleable.ViewfinderView_mask_color, Color.parseColor("#60000000"));
        resultPointColor = ta.getColor(R.styleable.ViewfinderView_result_point_color, Color.TRANSPARENT);
        labelText = ta.getString(R.styleable.ViewfinderView_label_text);
        labelTextColor = ta.getColor(R.styleable.ViewfinderView_label_text_color, Color.WHITE);
        labelTextSize = ta.getDimension(R.styleable.ViewfinderView_label_text_size, sp2px(context, 15));
        labelTextMargin = ta.getDimension(R.styleable.ViewfinderView_label_text_margin, dp2px(context, 18));
        ta.recycle();
        paint = new Paint();
        paint.setAntiAlias(true);
        possibleResultPoints = new HashSet(5);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        //如果没有设置frameCenterX和frameCenterY默认布局正中间的X、Y坐标
        frameCenterX = (frameCenterX == -1) ? getWidth() / 2f : frameCenterX;
        frameCenterY = (frameCenterY == -1) ? getHeight() / 2f : frameCenterY;
        //设置扫描区域位置
        int leftOffset = (int) (frameCenterX - frameWidth / 2f);
        int topOffset = (int) (frameCenterY - frameHeight / 2f);
        //设置扫描区不超过屏幕
        leftOffset = leftOffset > 0 ? leftOffset : 0;
        topOffset = topOffset > 0 ? topOffset : 0;
        Rect rect = new Rect();
        rect.left = leftOffset;
        rect.top = topOffset;
        rect.right = (int) (leftOffset + frameWidth);
        rect.bottom = (int) (topOffset + frameHeight);
        CameraManager.get().setFramingRect(rect);
    }

    @Override
    public void onDraw(Canvas canvas) {
        Rect frame = CameraManager.get().getFramingRect();
        if (frame == null) {
            return;
        }
        if (scannerStart == 0 || scannerEnd == 0) {
            scannerStart = frame.top;
            scannerEnd = frame.bottom;
        }

        int width = canvas.getWidth();
        int height = canvas.getHeight();
        //绘制模糊区域
        drawExterior(canvas, frame, width, height);
        //绘制扫描区边框
        drawFrame(canvas, frame);
        //绘制边角
        drawCorner(canvas, frame);
        //绘制提示信息
        drawTextInfo(canvas, frame);
        //绘制扫描线
        drawScanLine(canvas, frame);
        //绘制闪烁点
        drawResultPoint(canvas, frame);

        // Request another update at the animation interval, but only repaint the laser line,
        // not the entire viewfinder mask.
        //指定重绘区域,该方法会在子线程中执行
        postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom);
    }

    // 绘制模糊区域 Draw the exterior (i.e. outside the framing rect) darkened
    private void drawExterior(Canvas canvas, Rect frame, int width, int height) {
        paint.setColor(maskColor);
        canvas.drawRect(0, 0, width, frame.top, paint);
        canvas.drawRect(0, frame.top, frame.left, frame.bottom, paint);
        canvas.drawRect(frame.right, frame.top, width, frame.bottom, paint);
        canvas.drawRect(0, frame.bottom, width, height, paint);
    }

    // 绘制扫描区边框 Draw a two pixel solid black border inside the framing rect
    private void drawFrame(Canvas canvas, Rect frame) {
        if (frameStrokeWidth > 0) {
            paint.setColor(frameColor);
            if (cornerPosition == CORNER_INSIDE) {  //边角在扫描区内
                //左边
                canvas.drawRect(frame.left, frame.top, frame.left + frameStrokeWidth, frame.bottom, paint);
                //上边
                canvas.drawRect(frame.left, frame.top, frame.right, frame.top + frameStrokeWidth, paint);
                //右边
                canvas.drawRect(frame.right - frameStrokeWidth, frame.top, frame.right, frame.bottom, paint);
                //下边
                canvas.drawRect(frame.left, frame.bottom - frameStrokeWidth, frame.right, frame.bottom, paint);
            } else {  //边角在扫描区外
                //左边
                canvas.drawRect(frame.left - frameStrokeWidth, frame.top - frameStrokeWidth,
                        frame.left, frame.bottom + frameStrokeWidth, paint);
                //上边
                canvas.drawRect(frame.left - frameStrokeWidth, frame.top - frameStrokeWidth,
                        frame.right + frameStrokeWidth, frame.top, paint);
                //右边
                canvas.drawRect(frame.right, frame.top - frameStrokeWidth, frame.right + frameStrokeWidth,
                        frame.bottom + frameStrokeWidth, paint);
                //下边
                canvas.drawRect(frame.left - frameStrokeWidth, frame.bottom, frame.right + frameStrokeWidth,
                        frame.bottom + frameStrokeWidth, paint);
            }
        }
    }

    //绘制边角
    private void drawCorner(Canvas canvas, Rect frame) {
        if (cornerSize > 0 && cornerStrokeWidth > 0) {
            paint.setColor(cornerColor);
            if (cornerPosition == CORNER_INSIDE) {  //绘制在扫描区域内区
                //左上
                canvas.drawRect(frame.left, frame.top, frame.left + cornerSize, frame.top + cornerStrokeWidth, paint);
                canvas.drawRect(frame.left, frame.top, frame.left + cornerStrokeWidth, frame.top + cornerSize, paint);
                //右上
                canvas.drawRect(frame.right - cornerSize, frame.top, frame.right, frame.top + cornerStrokeWidth, paint);
                canvas.drawRect(frame.right - cornerStrokeWidth, frame.top, frame.right, frame.top + cornerSize, paint);
                //左下
                canvas.drawRect(frame.left, frame.bottom - cornerSize, frame.left + cornerStrokeWidth, frame.bottom, paint);
                canvas.drawRect(frame.left, frame.bottom - cornerStrokeWidth, frame.left + cornerSize, frame.bottom, paint);
                //右下
                canvas.drawRect(frame.right - cornerSize, frame.bottom - cornerStrokeWidth, frame.right, frame.bottom, paint);
                canvas.drawRect(frame.right - cornerStrokeWidth, frame.bottom - cornerSize, frame.right, frame.bottom, paint);
            } else {  //绘制在扫描区域外区
                //左上
                canvas.drawRect(frame.left - cornerStrokeWidth, frame.top - cornerStrokeWidth,
                        frame.left - cornerStrokeWidth + cornerSize, frame.top, paint);
                canvas.drawRect(frame.left - cornerStrokeWidth, frame.top - cornerStrokeWidth,
                        frame.left, frame.top - cornerStrokeWidth + cornerSize, paint);
                //右上
                canvas.drawRect(frame.right + cornerStrokeWidth - cornerSize, frame.top - cornerStrokeWidth,
                        frame.right + cornerStrokeWidth, frame.top, paint);
                canvas.drawRect(frame.right, frame.top - cornerStrokeWidth,
                        frame.right + cornerStrokeWidth, frame.top - cornerStrokeWidth + cornerSize, paint);
                //左下
                canvas.drawRect(frame.left - cornerStrokeWidth, frame.bottom, frame.left - cornerStrokeWidth + cornerSize,
                        frame.bottom + cornerStrokeWidth, paint);
                canvas.drawRect(frame.left - cornerStrokeWidth, frame.bottom + cornerStrokeWidth - cornerSize,
                        frame.left, frame.bottom + cornerStrokeWidth, paint);
                //右下
                canvas.drawRect(frame.right + cornerStrokeWidth - cornerSize, frame.bottom,
                        frame.right + cornerStrokeWidth, frame.bottom + cornerStrokeWidth, paint);
                canvas.drawRect(frame.right, frame.bottom + cornerStrokeWidth - cornerSize,
                        frame.right + cornerStrokeWidth, frame.bottom + cornerStrokeWidth, paint);
            }
        }
    }

    //绘制文本
    private void drawTextInfo(Canvas canvas, Rect frame) {
        if (!TextUtils.isEmpty(labelText)) {
            paint.setColor(labelTextColor);
            paint.setTextSize(labelTextSize);
            paint.setTextAlign(Paint.Align.CENTER);
            Paint.FontMetrics fm = paint.getFontMetrics();
            float baseY = frame.bottom + labelTextMargin - fm.ascent;
            canvas.drawText(labelText, frame.left + frame.width() / 2, baseY, paint);
        }
    }

    //绘制扫描线
    private void drawScanLine(Canvas canvas, Rect frame) {
        if (lineHeight > 0) {
            paint.setColor(lineColor);
            RadialGradient radialGradient = new RadialGradient(
                    (float) (frame.left + frame.width() / 2),
                    (float) (scannerStart + lineHeight / 2),
                    360f,
                    lineColor,
                    shadeColor(lineColor),
                    Shader.TileMode.MIRROR);

            paint.setShader(radialGradient);
            if (scannerStart <= scannerEnd) {
                //椭圆
                RectF rectF = new RectF(frame.left + 2 * lineHeight, scannerStart, frame.right - 2 * lineHeight,
                        scannerStart + lineHeight);
                canvas.drawOval(rectF, paint);
                scannerStart += lineMoveDistance;
            } else {
                scannerStart = frame.top;
            }
            paint.setShader(null);
        }
    }

    private void drawResultPoint(Canvas canvas, Rect frame) {
        if (resultPointColor != Color.TRANSPARENT) {
            Collection currentPossible = possibleResultPoints;
            Collection currentLast = lastPossibleResultPoints;
            if (currentPossible.isEmpty()) {
                lastPossibleResultPoints = null;
            } else {
                possibleResultPoints = new HashSet(5);
                lastPossibleResultPoints = currentPossible;
                paint.setAlpha(OPAQUE);
                paint.setColor(resultPointColor);
                for (ResultPoint point : currentPossible) {
                    canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 6.0f, paint);
                }
            }
            if (currentLast != null) {
                paint.setAlpha(OPAQUE / 2);
                paint.setColor(resultPointColor);
                for (ResultPoint point : currentLast) {
                    canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 3.0f, paint);
                }
            }
        }
    }

    //处理颜色模糊
    public int shadeColor(int color) {
        String hax = Integer.toHexString(color);
        String result = "20" + hax.substring(2);
        return Integer.valueOf(result, 16);
    }

    public void drawViewfinder() {
        invalidate();
    }

    public void addPossibleResultPoint(ResultPoint point) {
        possibleResultPoints.add(point);
    }

    private int dp2px(Context context, float dpValue) {
        float density = context.getApplicationContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * density + 0.5f);
    }

    private int sp2px(Context context, float spValue) {
        float scaleDensity = context.getApplicationContext().getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * scaleDensity + 0.5f);
    }
    
}

       2、CaptureActivity:扫码的Activity基类,代码如下;

/**
 * Created by xuzhb on 2019/11/16
 * Desc:扫码的Activity类
 * 整个Activity最重要的两个控件是一个SurfaceView(摄像头)和一个ViewfinderView(扫描区)
 * 对于继承CaptureActivity的Activity子类来说,
 * 可以选择在自己的布局中定义和CaptureActivity的布局文件id相同的控件,
 * 这样即使它们在两个布局中表现不同也能执行相同的逻辑,包括其他控件
 * 或者选择重写getSurfaceView()和getViewfinderView()返回对应的两个控件,
 * 扫码最终是在handleDecode(Result result, Bitmap bitmap)处理扫描后的结果
 */
public class CaptureActivity extends AppCompatActivity implements SurfaceHolder.Callback {

    private static final String TAG = "CaptureActivity";
    private static final int IMAGE_PICKER = 1999;

    private BeepManager mBeepManager;
    private CaptureActivityHandler mHandler;
    private Vector mDecodeFormats;
    private String mCharacterSet;
    private InactivityTimer mInactivityTimer;
    private boolean hasSurface = false;
    private boolean isLightOn = false;  //是否打开闪光灯
    private boolean isPlayBeep = true;  //是否开启扫描后的滴滴声
    private boolean isVibrate = true;   //是否震动
    private String mPhotoPath;          //选中的图片路径

    private TitleBar mTitleBar;
    private SurfaceView mSurfaceView;
    private ViewfinderView mViewfinderView;
    private LinearLayout mLightLl;
    private ImageView mLightIv;
    private TextView mLightTv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        CameraManager.init(getApplicationContext());
        mBeepManager = new BeepManager(this);
        hasSurface = false;
        mInactivityTimer = new InactivityTimer(this);
        handleView(savedInstanceState);
        initView();
        initListener();
    }

    protected int getLayoutId() {
        return R.layout.activity_capture;
    }

    protected void handleView(@Nullable Bundle savedInstanceState) {

    }

    private void initView() {
        mTitleBar = findViewById(R.id.title_bar);
        mSurfaceView = findViewById(R.id.surfaceView);
        mViewfinderView = findViewById(R.id.viewfinderView);
        mLightLl = findViewById(R.id.light_ll);
        mLightIv = findViewById(R.id.light_iv);
        mLightTv = findViewById(R.id.light_tv);
    }

    protected void initListener() {
        //因为继承CaptureActivity的Activity子类的布局不一定包含id为title_bar和light_ll的控件,
        //没有的话如果子类通过super.initListener()覆写时会因为找不到而报异常,所以这里加了一个判空;
        //如果子类的布局中包含id相同的控件,则不需要在子类中再重写相同的逻辑
        if (mTitleBar != null) {
            StatusBarUtil.INSTANCE.darkModeAndPadding(this, mTitleBar, Color.BLACK, 0, false);
            mTitleBar.setOnLeftClickListener(v -> {
                finish();
                return null;
            });
            mTitleBar.setOnRightClickListener(v -> {
                openAlbum();  //打开相册选取图片扫描
                return null;
            });
        }
        if (mLightLl != null) {
            mLightLl.setOnClickListener(v -> switchLight());  //打开或关闭闪光灯
        }
    }

    //打开相册
    protected void openAlbum() {
        Intent intent = new Intent(Intent.ACTION_PICK, null);
        intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
        startActivityForResult(intent, IMAGE_PICKER);
    }

    //开启/关闭闪光灯
    private void switchLight() {
        if (CameraManager.get() != null) {
            if (isLightOn) {
                mLightTv.setText("轻触点亮");
                CameraManager.get().turnLightOffFlashLight();
            } else {
                mLightTv.setText("轻触关闭");
                CameraManager.get().turnOnFlashLight();
            }
            isLightOn = !isLightOn;
            mLightIv.setSelected(isLightOn);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        SurfaceHolder holder = getSurfaceView().getHolder();
        if (hasSurface) {
            initCamera(holder);
        } else {
            holder.addCallback(this);
            holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }
        mDecodeFormats = null;
        mCharacterSet = null;
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mHandler != null) {
            mHandler.quitSynchronously();
            mHandler = null;
        }
        CameraManager.get().closeDriver();
    }

    @Override
    protected void onDestroy() {
        mInactivityTimer.shutdown();
        mBeepManager.releaseRing();
        super.onDestroy();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (!hasSurface) {
            hasSurface = true;
            initCamera(holder);
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        hasSurface = false;
    }

    private void initCamera(SurfaceHolder holder) {
        try {
            CameraManager.get().openDriver(holder);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (mHandler == null) {
            mHandler = new CaptureActivityHandler(this, mDecodeFormats, mCharacterSet);
        }
    }

    //继承CaptureActivity的Activity类,如果SurfaceView的id和CaptureActivity布局中SurfaceView的id不同
    //需要重写这个方法,返回自己布局中的SurfaceView
    public SurfaceView getSurfaceView() {
        return mSurfaceView;
    }

    //继承CaptureActivity的Activity类,如果ViewfinderView的id和CaptureActivity布局中ViewfinderView的id不同
    //需要重写这个方法,返回自己布局中的ViewfinderView
    public ViewfinderView getViewfinderView() {
        return mViewfinderView;
    }

    public Handler getHandler() {
        return mHandler;
    }

    public void drawViewfinder() {
        getViewfinderView().drawViewfinder();
    }

    //处理扫描后的结果
    public void handleDecode(Result result, Bitmap bitmap) {
        mInactivityTimer.onActivity();
        if (result != null) {
            String text = result.getText();
            Log.i(TAG, "识别的结果:" + text);
            if (!TextUtils.isEmpty(text)) {  //识别成功
                playBeepSoundAndVibrate();
                returnQRCodeResult(text);
            } else {
                showToast("很抱歉,识别二维码失败!");
            }
        } else {
            showToast("未发现二维码!");
        }
    }

    private void playBeepSoundAndVibrate() {
        if (isPlayBeep) {
            mBeepManager.startRing();  //播放扫码的滴滴声
        }
        if (isVibrate) {
            Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
            if (vibrator != null) {
                vibrator.vibrate(200);  //震动200毫秒
            }
        }
    }

    //返回扫描结果
    private void returnQRCodeResult(String result) {
        Intent intent = new Intent();
        intent.putExtra(QRConstant.SCAN_QRCODE_RESULT, result);
        setResult(Activity.RESULT_OK, intent);
        finish();
    }

    private void showToast(CharSequence text) {
        runOnUiThread(() -> {
            ToastUtil.INSTANCE.showToast(text, true, false, getApplicationContext());
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == IMAGE_PICKER && resultCode == Activity.RESULT_OK) {
            if (data != null) {
                Uri uri = data.getData();
                if (uri != null) {
                    Cursor cursor = getContentResolver().query(uri, null, null, null, null);
                    if (cursor != null) {
                        if (cursor.moveToFirst()) {
                            mPhotoPath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                        }
                        cursor.close();
                        if (!TextUtils.isEmpty(mPhotoPath)) {
                            //可以加个提示正在扫描的加载框,如showLoadingDialog("正在扫描...")
                            new Thread(() -> {
                                handleDecode(QRCodeUtil.decodeImage(mPhotoPath), null);
                                //取消加载框,dismissLoadingDialog()
                            }).start();
                        } else {
                            Log.e(TAG, "未找到图片");
                        }
                    }
                }
            }
        }
    }
}

 看一下使用的例子

Android集成zxing扫码框架_第2张图片      Android集成zxing扫码框架_第3张图片

最后,附上整个项目的github地址,注:项目使用了视图绑定ViewBinding,所以需要使用AndroidStudio 3.6.x版本。

你可能感兴趣的:(Android框架篇)