Android 二维码扫描(仿微信界面),根据Google zxing

Android 二维码扫描(仿微信界面),根据Google zxing

Android项目开发中经常会用到二维码扫描,例如登陆、支付等谷歌方面已经有了一个开源库(地址: https://github.com/zxing/zxing),里面的内容还是比较多的,如果想深入学习这方面的内容,还是不错的。当然了还是要根据实际需求去选择,所以我们就选择类库中的核心Jar包就行了(地址:https://github.com/ASCN-BJ/ZhaoYun/blob/master/qrcodelibrary/libs/core-3.3.1.jar)。
GoogleZxing中的原装的类库有一个问题就是竖屏的时候会导致无法识别,原因是竖屏的时候没有对相机的预览效果进行处理,所以在项目中,进行了处理,使得竖屏的时候的识别能够成功。所以正好看了一下,就当学习了。
效果图
Android 二维码扫描(仿微信界面),根据Google zxing_第1张图片
Android 二维码扫描(仿微信界面),根据Google zxing_第2张图片
Android 二维码扫描(仿微信界面),根据Google zxing_第3张图片
- 二维码的扫描
zxing中的二维码识别界面主要是在CaptureActivity中,基本原理通过获取相机的预览界面然后对获取到的数据进行处理,预览效果是在PreviewCallback中获取数据,然后通过DecodeHandler发送数据,具体识别是在DecodeHandler类中处理。

      @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        Point cameraResolution = configManager.getCameraResolution();
        Handler thePreviewHandler = previewHandler;
//        if (cameraResolution != null && thePreviewHandler != null) {
//            Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
//                    cameraResolution.y, data);
//            message.sendToTarget();
//            previewHandler = null;
//        } else {
//            Log.d(TAG, "Got preview callback, but no handler or resolution available");
//        }
//        Log.d(TAG, "onPreviewFrame: " + "onRunning");
        if (cameraResolution != null && thePreviewHandler != null) {
            //add by tancolo
            Point screenResolution = configManager.getScreenResolution();
            Message message;
            if (screenResolution.x < screenResolution.y) {
                // portrait
                message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.y,
                        cameraResolution.x, data);
            } else {
                // landscape
                message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,
                        cameraResolution.y, data);
            }
            message.sendToTarget();
            previewHandler = null;
        }
    }

可以看到在onPreviewFrame中将具体byte[] data数据发送出去,同时将预览效果的宽高发送出去,用于后续的处理。
接下来看具体二维码识别的代码

  @Override
    public void handleMessage(Message message) {
        if (message == null || !running) {
            return;
        }
        if (message.what == R.id.decode) {
            decode((byte[]) message.obj, message.arg1, message.arg2);

        } else if (message.what == R.id.quit) {
            running = false;
            Looper.myLooper().quit();

        }
    }
 private void decode(byte[] data, int width, int height) {
        long start = System.currentTimeMillis();
        //add by tancolo
        if (width < height) {
            // portrait
            byte[] rotatedData = new byte[data.length];
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++)
                    rotatedData[y * width + width - x - 1] = data[y + x * height];
            }
            data = rotatedData;
        }
        //end add


        Result rawResult = null;
        PlanarYUVLuminanceSource source =     activity.getCameraManager().buildLuminanceSource(data, width, height);
        if (source != null) {
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
            try {
                rawResult = multiFormatReader.decodeWithState(bitmap);//具体识别方法,获取到数据
            } catch (ReaderException re) {
                // continue
            } finally {
                multiFormatReader.reset();
            }
        }

        Handler handler = activity.getHandler();
        if (rawResult != null) {
            // Don't log the barcode contents for security.
            long end = System.currentTimeMillis();
            Log.d(TAG, "Found barcode in " + (end - start) + " ms");
            if (handler != null) {
                Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult);
                Bundle bundle = new Bundle();
                bundleThumbnail(source, bundle);
                message.setData(bundle);
                message.sendToTarget();
            }
        } else {
            if (handler != null) {
                Message message = Message.obtain(handler, R.id.decode_failed);
                message.sendToTarget();
            }
        }
    }
  • 二维码的生成
这里写代码片
  try {
mQRCodeEncoder2 = new QRCodeEncoder2(smallerDimension);
 //生成二维码
Bitmap bitmap = mQRCodeEncoder2.encodeAsBitmap(text,    BarcodeFormat.QR_CODE);
 if (bitmap == null) {
 qrCodeEncoder = null;
 return true;
}
  iv_image_view.setImageBitmap(bitmap);
  } catch (WriterException e) {
   qrCodeEncoder = null;
 }

具体识别

    public Bitmap encodeAsBitmap(String contentsToEncode, BarcodeFormat format) throws WriterException {
        if (contentsToEncode == null) {
            return null;
        }
        Map hints = null;
        String encoding = guessAppropriateEncoding(contentsToEncode);
        if (encoding != null) {
            hints = new EnumMap<>(EncodeHintType.class);
            hints.put(EncodeHintType.CHARACTER_SET, encoding);
        }
        BitMatrix result;
        try {
            format = BarcodeFormat.valueOf(format.toString());
            result = new MultiFormatWriter().encode(contentsToEncode, format, dimension, dimension, hints);
        } catch (IllegalArgumentException iae) {
            // Unsupported format
            return null;
        }
        int width = result.getWidth();
        int height = result.getHeight();
        int[] pixels = new int[width * height];
        for (int y = 0; y < height; y++) {
            int offset = y * width;
            for (int x = 0; x < width; x++) {
                pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;//生成二维码数据
            }
        }
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
        return bitmap;
    }
  • 本地图片的识别
public static String getResult(Bitmap bitmap) {
        //Zxing自带的解析类
        byte data[];
        int[] datas = new int[bitmap.getWidth() * bitmap.getHeight()];
        data = new byte[bitmap.getWidth() * bitmap.getHeight()];
        bitmap.getPixels(datas, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        for (int i = 0; i < datas.length; i++) {
            data[i] = (byte) datas[i];
        }
        Set decodeFormats = new HashSet<>();
        decodeFormats.addAll(DecodeFormatManager.PRODUCT_FORMATS);
        decodeFormats.addAll(DecodeFormatManager.INDUSTRIAL_FORMATS);
        decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
        decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
        decodeFormats.addAll(DecodeFormatManager.AZTEC_FORMATS);
        decodeFormats.addAll(DecodeFormatManager.PDF417_FORMATS);
        Map hints = new EnumMap<>(DecodeHintType.class);
        hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
        MultiFormatReader multiFormatReader = new MultiFormatReader();
        multiFormatReader.setHints(hints);
        Result rawResult = null;
        PlanarYUVLuminanceSource source = buildLuminanceSource(data, bitmap.getWidth(), bitmap.getHeight());
        if (source != null) {
            BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
            try {
                rawResult = multiFormatReader.decodeWithState(bitmap1);
            } catch (ReaderException re) {
                // continue
            } finally {
                multiFormatReader.reset();
            }
        }

        return rawResult != null ? rawResult.getText() : "";
    }
  • 手势添加
    微信中我们可以发现有手势识别,主要是双击效果,多点触控效果,手电筒(根据传感器显示手电筒图标)
public final class ViewfinderView extends View implements ScaleGestureDetector.OnScaleGestureListener,
        View.OnTouchListener{

    private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
    private static final long ANIMATION_DELAY = 80L;
    private static final int CURRENT_POINT_OPACITY = 0xA0;
    private static final int MAX_RESULT_POINTS = 20;
    private static final int POINT_SIZE = 6;

    private CameraManager cameraManager;
    private final Paint paint;
    private Bitmap resultBitmap;
    private final int maskColor;
    private final int resultColor;
    private final int laserColor;
    private final int resultPointColor;
    private int scannerAlpha;
    private List possibleResultPoints;
    private List lastPossibleResultPoints;
    private Rect tmpRect;
    private Bitmap tmpBitmap;
    private float cornerWidth;//方角宽度
    private float cornerLength;//方角长度
    private int mStrokeWidth;//内圈寬度
    private int corner_rect_length;//内圈颜色
    private int cornerColor;//角度颜色
    private String description;//描述文字
    private int textColor;
    private boolean textVisible;
    private float textMargin;//描述文本距离上面的距离
    private float text_size;//描述文本字体大小
    private ScaleGestureDetector scaleGestureDetector;
    private GestureDetector gestureDetector;
    private boolean zoomMaxFlag = true;

    // This constructor is used when the class is built from an XML resource.
    public ViewfinderView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // Initialize these once for performance rather than calling them every time in onDraw().
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Resources resources = getResources();
        resultColor = resources.getColor(R.color.result_view);
        laserColor = resources.getColor(R.color.viewfinder_laser);
        resultPointColor = resources.getColor(R.color.possible_result_points);
        scannerAlpha = 0;
        possibleResultPoints = new ArrayList<>(5);
        lastPossibleResultPoints = null;
        tmpRect = new Rect();
        tmpBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.scan);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView);
        cornerWidth = a.getDimensionPixelSize(R.styleable.ViewfinderView_corner_width, dip2px(context, 4));
        cornerLength = a.getDimensionPixelSize(R.styleable.ViewfinderView_corner_length, dip2px(context, 20));
        mStrokeWidth = a.getDimensionPixelSize(R.styleable.ViewfinderView_corner_rect_length, dip2px(context, 1));
        cornerColor = a.getColor(R.styleable.ViewfinderView_corner_color, Color.parseColor("#62e203"));
        corner_rect_length = a.getColor(R.styleable.ViewfinderView_corner_rect_color, Color.parseColor("#66ffffff"));
        maskColor = a.getColor(R.styleable.ViewfinderView_mask_color, resources.getColor(R.color.viewfinder_mask));
        description = a.getString(R.styleable.ViewfinderView_text_description);
        if (TextUtils.isEmpty(description)) {
            description = "将二维码/条码放入框内,即可自动扫描";
        }
        textColor = a.getColor(R.styleable.ViewfinderView_text_color, Color.parseColor("#66ffffff"));
        textVisible = a.getBoolean(R.styleable.ViewfinderView_text_visible, true);
        textMargin = a.getDimensionPixelSize(R.styleable.ViewfinderView_text_margin, dip2px(context, 20));
        text_size = a.getDimensionPixelSize(R.styleable.ViewfinderView_text_size, sp2px(context, 14));
        a.recycle();
        setOnTouchListener(this);
        scaleGestureDetector = new ScaleGestureDetector(context, this);
        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
            @Override
            public boolean onDoubleTap(MotionEvent e) {
                if (zoomMaxFlag) {
                    if (mCameraZoomListener != null) {
                        mCameraZoomListener.onZooming(true, false, true, 1);
                    }
                    zoomMaxFlag = false;
                } else {
                    if (mCameraZoomListener != null) {
                        mCameraZoomListener.onZooming(true, false, false, 1);
                    }
                    zoomMaxFlag = true;
                }
                return true;
            }

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
//                if (mCameraZoomListener != null) {
//                    //判断点击事件是否在
//                    mCameraZoomListener.onZooming(false, true, false, 1);
//                }
                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                super.onLongPress(e);
//                if (mCameraZoomListener != null) {
//                    //判断点击事件是否在
//                    mCameraZoomListener.onZooming(false, true, false, 1);
//                }
            }
        });
    }

    public void setCameraManager(CameraManager cameraManager) {
        this.cameraManager = cameraManager;
    }

    @SuppressLint("DrawAllocation")
    @Override
    public void onDraw(Canvas canvas) {
        if (cameraManager == null) {
            return; // not ready yet, early draw before done configuring
        }
        Rect frame = cameraManager.getFramingRect();
        Rect previewFrame = cameraManager.getFramingRectInPreview();
        if (frame == null || previewFrame == null) {
            return;
        }
        int width = canvas.getWidth();
        int height = canvas.getHeight();

        // Draw the exterior (i.e. outside the framing rect) darkened
        paint.setColor(resultBitmap != null ? resultColor : maskColor);
        canvas.drawRect(0, 0, width, frame.top, paint);
        canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
        canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
        canvas.drawRect(0, frame.bottom + 1, width, height, paint);
        if (resultBitmap != null) {
            // Draw the opaque result bitmap over the scanning rectangle
            paint.setAlpha(CURRENT_POINT_OPACITY);
            canvas.drawBitmap(resultBitmap, null, frame, paint);
        } else {

            // Draw a red "laser scanner" line through the middle to show decoding is active
            paint.setColor(laserColor);
//            paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
            scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
//            int middle = frame.height() / 2 + frame.top;
//            canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint);//移除红线

            float scaleX = frame.width() / (float) previewFrame.width();
            float scaleY = frame.height() / (float) previewFrame.height();

            List currentPossible = possibleResultPoints;
            List currentLast = lastPossibleResultPoints;
            int frameLeft = frame.left;
            int frameTop = frame.top;
            if (currentPossible.isEmpty()) {
                lastPossibleResultPoints = null;
            } else {
                possibleResultPoints = new ArrayList<>(5);
                lastPossibleResultPoints = currentPossible;
                paint.setAlpha(CURRENT_POINT_OPACITY);
                paint.setColor(resultPointColor);
                synchronized (currentPossible) {
                    for (ResultPoint point : currentPossible) {
                        canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                                frameTop + (int) (point.getY() * scaleY),
                                POINT_SIZE, paint);
                    }
                }
            }
            if (currentLast != null) {
                paint.setAlpha(CURRENT_POINT_OPACITY / 2);
                paint.setColor(resultPointColor);
                synchronized (currentLast) {
                    float radius = POINT_SIZE / 2.0f;
                    for (ResultPoint point : currentLast) {
                        canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX),
                                frameTop + (int) (point.getY() * scaleY),
                                radius, paint);
                    }
                }
            }
            drawRectCorner(frame, canvas);
            drawBitmap(frame, canvas);
            drawBottomText(frame, canvas);


            // Request another update at the animation interval, but only repaint the laser line,
            // not the entire viewfinder mask.
//            postInvalidateDelayed(ANIMATION_DELAY,
//                    frame.left - POINT_SIZE,
//                    frame.top - POINT_SIZE,
//                    frame.right + POINT_SIZE,
//                    frame.bottom + POINT_SIZE);
        }
    }

    public void drawViewfinder() {
        Bitmap resultBitmap = this.resultBitmap;
        this.resultBitmap = null;
        if (resultBitmap != null) {
            resultBitmap.recycle();
        }
        invalidate();
    }

    /**
     * Draw a bitmap with the result points highlighted instead of the live scanning display.
     *
     * @param barcode An image of the decoded barcode.
     */
    public void drawResultBitmap(Bitmap barcode) {
        resultBitmap = barcode;
        invalidate();
    }

    public void addPossibleResultPoint(ResultPoint point) {
        List points = possibleResultPoints;
        synchronized (points) {
            points.add(point);
            int size = points.size();
            if (size > MAX_RESULT_POINTS) {
                // trim it
                points.subList(0, size - MAX_RESULT_POINTS / 2).clear();
            }
        }
    }

    private ValueAnimator animator;
    private float x;
    private boolean flag = true;

    /*画四个角,如果通过绘制path的方式会卡*/
    private void drawRectCorner(Rect frame, Canvas canvas) {
        //画内部rect
        paint.setStrokeWidth(mStrokeWidth);
        paint.setColor(corner_rect_length);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawRect(frame, paint);
        paint.reset();
        //画四角
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(cornerColor);
        //左上角
        canvas.drawRect(frame.left, frame.top, frame.left + cornerLength, frame.top + cornerWidth, paint);
        canvas.drawRect(frame.left, frame.top, frame.left + cornerWidth, frame.top + cornerLength, paint);
        //右上角
        canvas.drawRect(frame.right - cornerLength, frame.top, frame.right, frame.top + cornerWidth, paint);
        canvas.drawRect(frame.right - cornerWidth, frame.top, frame.right, frame.top + cornerLength, paint);
        //左下角
        canvas.drawRect(frame.left, frame.bottom - cornerLength, frame.left + cornerWidth, frame.bottom, paint);
        canvas.drawRect(frame.left, frame.bottom - cornerWidth, frame.left + cornerLength, frame.bottom, paint);
        //右下角
        canvas.drawRect(frame.right - cornerLength, frame.bottom - cornerWidth, frame.right, frame.bottom, paint);
        canvas.drawRect(frame.right - cornerWidth, frame.bottom - cornerLength, frame.right, frame.bottom, paint);
        paint.reset();
    }

    /*画扫描线*/
    private void drawBitmap(final Rect frame, Canvas canvas) {
        if (animator == null) {
            animator = ValueAnimator.ofFloat(frame.top, frame.bottom);
            animator.setDuration(3000);
            animator.setRepeatCount(ValueAnimator.INFINITE);
            animator.setRepeatMode(ValueAnimator.RESTART);
            animator.setInterpolator(new LinearInterpolator());
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    x = (Float) animation.getAnimatedValue();
                    postInvalidate(
                            frame.left - POINT_SIZE,
                            frame.top - POINT_SIZE,
                            frame.right + POINT_SIZE,
                            frame.bottom + POINT_SIZE);
                }
            });
        }

        if (flag) {
            animator.start();
            flag = false;
        }
        tmpRect.set(frame.left, (int) x, frame.right, (int) x + 40);
        canvas.drawBitmap(tmpBitmap, null, tmpRect, paint);
    }

    private Rect tmpRect1 = new Rect();

    private void drawBottomText(Rect frame, Canvas canvas) {
        if (textVisible) {
            paint.setTextSize(text_size);
            paint.setColor(textColor);
            paint.setTextAlign(Paint.Align.CENTER);
            paint.getTextBounds(description, 0, description.length(), tmpRect1);
            canvas.drawText(description, 0, description.length(), frame.centerX(),
                    frame.centerY() + frame.height() / 2 + textMargin + tmpRect1.height(), paint);
            paint.reset();
        }
    }

    /**
     * 将dip或dp值转换为px值,保证尺寸大小不变
     */
    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * 将sp值转换为px值,保证文字大小不变
     */
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }


    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (mCameraZoomListener != null) {
            mCameraZoomListener.onZooming(false, false, false, detector.getScaleFactor());
        }
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {

    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        scaleGestureDetector.onTouchEvent(event);
        gestureDetector.onTouchEvent(event);
        return true;
    }

    public interface CameraZoomListener {
        /**
         * @param isDouble   是否双击
         * @param isMax      //是否移动到最大
         * @param scaleValue //缩小放大的值
         * @param isSingle   //是否单击
         */
        void onZooming(boolean isDouble, boolean isSingle, boolean isMax, float scaleValue);
    }

    private CameraZoomListener mCameraZoomListener;

    public void setCameraZoomListener(CameraZoomListener mCameraZoomListener) {
        this.mCameraZoomListener = mCameraZoomListener;
    }
}

如果生产环境的话,可以根据具体的产品修改界面
项目地址:https://github.com/ASCN-BJ/ZXingLibrary

你可能感兴趣的:(自定义控件)