Android之 Zxing二维码详解

一 简介

1.1 ZXing

目前Android扫描二维码,条形码主要用google官方的工具Zxing,支持扫码,相册解码,生成带logo的二维码等功能 

Zxing github 示例地址:https://github.com/zxing/zxing

1.2 ZBar

由于zxing是基于java编写的,扫码速度和解析上可能没那么快,但大部分场合足够用。也有基于c/c++的库zbar,需要编译通过才能用,下面是官网,有兴趣的可以编译试试:

ZBar官网:http://zbar.sourceforge.net/
ZBar GitHub地址:https://github.com/ZBar/ZBar

1.3  华为ScanKit

目前体验最好的华为统一扫码SDK,基本可以做到秒扫和快速识别,支持多码识别和二维码生成。但该服务必须在华为开发者联盟平台注册应用,配置包名和服务json

https://gitee.com/hms-core/hms-scan-demo华为官方demo示例 gitee地址:https://gitee.com/hms-core/hms-scan-demo

二 Zxing使用

2.1 依赖远程zxing库

dependencies {

  //zxing的core库
  implementation "com.google.zxing:core:3.5.1"
 
  //zxing
  implementation "com.google.zxing:zxing-parent:3.5.1"

  //zxing
  implementation 'com.journeyapps:zxing-android-embedded:4.1.0'

}

或直接使用下面库,目前识别比较快的Zxing库

implementation 'com.journeyapps:zxing-android-embedded:4.3.0'

2.2 添加权限

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FLASHLIGHT" />

2.3 调用SDK的扫码页面并返回结果

/**
 * 跳转到扫码界面扫码
 */
private void goScan(){
    Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
    startActivityForResult(intent, REQUEST_CODE_SCAN);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    // 扫描二维码/条码回传
    if (requestCode == REQUEST_CODE_SCAN && resultCode == RESULT_OK) {
        if (data != null) {
            //返回的文本内容
            String content = data.getStringExtra(DECODED_CONTENT_KEY);
            //返回的BitMap图像
            Bitmap bitmap = data.getParcelableExtra(DECODED_BITMAP_KEY);
        }
    }
}

三 自定义扫码页面

3.1 效果图

e3936b2bcaef40839606b0b9af31702c.png

3.2 activity_scan.xml

<?xml version="1.0" encoding="UTF-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:my_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <FrameLayout
        android:id="@+id/rim"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#C0C0C0">

        <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"/>
    </FrameLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:gravity="center_vertical">


        <TextView
            android:layout_marginStart="10sp"
            android:layout_toEndOf="@+id/back_img"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:text="扫码"
            android:textAllCaps="false"
            android:textColor="#FFFFFF"
            android:textSize="20sp"
            android:textStyle="bold" />

        <ImageView
            android:id="@+id/back_img"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:padding="12dp"
            android:layout_alignParentStart="true"
            android:layout_marginStart="12dp"
            android:layout_marginTop="4dp"
            android:gravity="center"
            android:src="@drawable/back" />

        <TextView
            android:id="@+id/tv_decode"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:text="解码器"
            android:textAllCaps="false"
            android:textColor="#FFFFFF"
            android:textSize="20sp"
            android:textStyle="bold"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="12dp"
            android:layout_marginTop="4dp" />
        <ImageView
            android:id="@+id/img_btn"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:padding="12dp"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="12dp"
            android:layout_marginTop="4dp"
            android:gravity="center"
            android:visibility="gone"
            android:src="@drawable/photo" />
    </RelativeLayout>

</FrameLayout>

3.3 ScanActivity.java

public class ScanActivity extends Activity {

    public static final int REQUEST_CODE_PHOTO = 0X1113;
    private static final String TAG = "ScanActivity";

    private Camera camera = null;
    private Camera.Parameters parameters = null;
    private boolean isPreview = false;
    private FrameCallback frameCallback = new FrameCallback();
    private int width = 1920;
    private int height = 1080;
    private double defaultZoom = 1.0;

    private SurfaceHolder surfaceHolder;
    private SurfaceCallBack surfaceCallBack;
    private CommonHandler handler;
    private boolean isShow;

    private FrameLayout rim;
    private SurfaceView surfaceView;
    private ImageView backImg;
    private TextView tvDecode;
    private ImageView imgBtn;


    private int shortSize;
    private int longSize;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Window window = getWindow();
        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_common);

        rim = (FrameLayout) findViewById(R.id.rim);
        surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        backImg = (ImageView) findViewById(R.id.back_img);
        tvDecode = (TextView) findViewById(R.id.tv_decode);
        imgBtn = (ImageView) findViewById(R.id.img_btn);


        checkCameraPermission();
    }

    //权限请求
    public final int REQUEST_CAMERA_PERMISSION = 1;
    private String cameraPermission = Manifest.permission.CAMERA;

    private void checkCameraPermission() {
        //检查是否有相机权限
        if (ContextCompat.checkSelfPermission(this, cameraPermission) != PackageManager.PERMISSION_GRANTED) {
            //没权限,请求权限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
                    REQUEST_CAMERA_PERMISSION);
        } else {
            //有权限
            createSurfaceView();
        }
    }

    //权限请求回调
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CAMERA_PERMISSION:
                if (grantResults != null && grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    //用户同意权限
                    createSurfaceView();
                } else {
                    // 权限被用户拒绝了,可以提示用户,关闭界面等等。
                    Toast.makeText(this, "拒绝权限,请去设置里面手动开启权限", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }


    /**
     * 创建预览
     */
    private void createSurfaceView() {
        surfaceCallBack = new SurfaceCallBack();

        adjustSurface(surfaceView);
        surfaceHolder = surfaceView.getHolder();
        isShow = false;
        setBackOperation();
        setPictureScanOperation();
        setDecodeSelectOperation();
    }

    private void adjustSurface(SurfaceView cameraPreview) {
        FrameLayout.LayoutParams paramSurface = (FrameLayout.LayoutParams) cameraPreview.getLayoutParams();
        if (getSystemService(Context.WINDOW_SERVICE) != null) {
            WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
            Display defaultDisplay = windowManager.getDefaultDisplay();
            Point outPoint = new Point();
            defaultDisplay.getRealSize(outPoint);
            int sceenWidth = outPoint.x;
            int sceenHeight = outPoint.y;

            shortSize = Math.min(sceenWidth, sceenHeight);
            longSize = shortSize;
            //横屏
            paramSurface.width = shortSize;
            paramSurface.height = shortSize;


//            float rate;
//            if (sceenWidth / (float) 1080 > sceenHeight / (float) 1920) {
//                rate = sceenWidth / (float) 1080;
//                int targetHeight = (int) (1920 * rate);
//                paramSurface.width = FrameLayout.LayoutParams.MATCH_PARENT;
//                paramSurface.height = targetHeight;
//                int topMargin = (int) (-(targetHeight - sceenHeight) / 2);
//                if (topMargin < 0) {
//                    paramSurface.topMargin = topMargin;
//                }
//            } else {
//                rate = sceenHeight / (float) 1920;
//                int targetWidth = (int) (1080 * rate);
//                paramSurface.width = targetWidth;
//                paramSurface.height = FrameLayout.LayoutParams.MATCH_PARENT;
//                int leftMargin = (int) (-(targetWidth - sceenWidth) / 2);
//                if (leftMargin < 0) {
//                    paramSurface.leftMargin = leftMargin;
//                }
//            }
        }
    }

    private void setBackOperation() {
        backImg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }


    private void setPictureScanOperation() {
        imgBtn = findViewById(R.id.img_btn);
        imgBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent pickIntent = new Intent(Intent.ACTION_PICK,
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
                ScanActivity.this.startActivityForResult(pickIntent, REQUEST_CODE_PHOTO);
            }
        });
    }

    private int decodeType = 1;
    private void setDecodeSelectOperation() {
        tvDecode.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (decodeType == 1) {
                    decodeType = 2;
                    tvDecode.setText("Sunmin");
                } else if (decodeType == 2) {
                    decodeType = 3;
                    tvDecode.setText("HuaweiHms");
                } else if (decodeType == 3) {
                    decodeType = 1;
                    tvDecode.setText("Zxing");
                }

            }
        });
        tvDecode.setText("Zxing");
    }


    @Override
    protected void onResume() {
        super.onResume();
        if (isShow) {
            initCamera();
        } else {
            surfaceHolder.addCallback(surfaceCallBack);
        }
    }

    @Override
    protected void onPause() {
        if (handler != null) {
            handler.quit();
            handler = null;
        }
        close();
        if (!isShow) {
            surfaceHolder.removeCallback(surfaceCallBack);
        }
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    private void initCamera() {
        open(surfaceHolder);
        if (handler == null) {
            handler = new CommonHandler();
        } else {
            startPreview();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
//        if (resultCode != RESULT_OK || data == null || requestCode != REQUEST_CODE_PHOTO) {
//            return;
//        }
//        try {
//            decodeMultiSyn(MediaStore.Images.Media.getBitmap(this.getContentResolver(), data.getData()));
//        } catch (Exception e) {
//            Log.e(TAG, Objects.requireNonNull(e.getMessage()));
//        }
    }


    class SurfaceCallBack implements SurfaceHolder.Callback {

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            if (!isShow) {
                isShow = true;
                initCamera();
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        }

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


    ///

    /**
     * Open up the camera.
     */
    public synchronized void open(SurfaceHolder holder) {
        try {
            camera = Camera.open(0);
            parameters = camera.getParameters();
            //获取合适的预览尺寸,保证不变形
            Camera.Size bestSize = getBestSize(parameters);
            //设置预览大小
            parameters.setPreviewSize(bestSize.width, bestSize.height);
            parameters.setPictureSize(width, height);
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            parameters.setPictureFormat(ImageFormat.NV21);
            camera.setPreviewDisplay(holder);
            camera.setDisplayOrientation(90);
            //camera.setDisplayOrientation(270);
            camera.setParameters(parameters);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取预览最佳尺寸
     */
    private Camera.Size getBestSize(Camera.Parameters parameters) {
        List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
        Camera.Size bestSize = null;
        float uiRatio = (float) longSize / shortSize;
        float minRatio = uiRatio;
        for (Camera.Size previewSize : sizes) {
            float cameraRatio = (float) previewSize.width / previewSize.height;

            //如果找不到比例相同的,找一个最近的,防止预览变形
            float offset = Math.abs(cameraRatio - minRatio);
            if (offset < minRatio) {
                minRatio = offset;
                bestSize = previewSize;
            }
            //比例相同
            if (uiRatio == cameraRatio) {
                bestSize = previewSize;
                break;
            }

        }
        return bestSize;
    }


    public synchronized void close() {
        if (camera != null) {
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    }

    public synchronized void startPreview() {
        if (camera != null && !isPreview) {
            camera.startPreview();
            isPreview = true;
        }
    }

    public synchronized void stopPreview() {
        if (camera != null && isPreview) {
            camera.stopPreview();
            frameCallback.setProperties(null);
            isPreview = false;
        }
    }

    public synchronized void callbackFrame(Handler handler, double zoomValue) {
        if (camera != null && isPreview) {
            frameCallback.setProperties(handler);
            if (camera.getParameters().isZoomSupported() && zoomValue != defaultZoom) {
                //Auto zoom.
                //parameters.setZoom(convertZoomInt(zoomValue));
                camera.setParameters(parameters);
            }
            camera.setOneShotPreviewCallback(frameCallback);
        }
    }

    public int convertZoomInt(double zoomValue) {
        List<Integer> allZoomRatios = parameters.getZoomRatios();
        float maxZoom = Math.round(allZoomRatios.get(allZoomRatios.size() - 1) / 100f);
        if (zoomValue >= maxZoom) {
            return allZoomRatios.size() - 1;
        }
        for (int i = 1; i < allZoomRatios.size(); i++) {
            if (allZoomRatios.get(i) >= (zoomValue * 100) && allZoomRatios.get(i - 1) <= (zoomValue * 100)) {
                return i;
            }
        }
        return -1;
    }


    class FrameCallback implements Camera.PreviewCallback {

        private Handler handler;

        public void setProperties(Handler handler) {
            this.handler = handler;

        }

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            if (handler != null) {
                Message message = handler.obtainMessage(0, camera.getParameters().getPreviewSize().width,
                        camera.getParameters().getPreviewSize().height, data);
                message.sendToTarget();
                handler = null;
            }
        }
    }

    //


    class CommonHandler extends Handler {

        private static final String TAG = "MainHandler";
        private static final double DEFAULT_ZOOM = 1.0;
        private HandlerThread decodeThread;
        private Handler decodeHandle;
        private BeepManager beepManager;
        private long preTime;
        public CommonHandler() {
            beepManager = new BeepManager(ScanActivity.this);

            decodeThread = new HandlerThread("DecodeThread");
            decodeThread.start();
            decodeHandle = new Handler(decodeThread.getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    if (msg == null) {
                        return;
                    }
                    long startTime=System.currentTimeMillis();
                    String result = decodeSyn(msg.arg1, msg.arg2, (byte[]) msg.obj);
                    long endTime=System.currentTimeMillis();
                    if (result == null) {
                        restart(DEFAULT_ZOOM);
                    } else {
                        Message message = new Message();
                        message.what = msg.what;
                        message.obj = result;
                        message.arg1= (int) (endTime-startTime);
                        CommonHandler.this.sendMessage(message);
                    }
                }
            };
            startPreview();
            restart(DEFAULT_ZOOM);
        }

        @Override
        public void handleMessage(Message message) {
            if (message.what == 0) {
                String result = (String) message.obj;
                Log.e(TAG, result);
                long currentTime = System.currentTimeMillis();
                if (currentTime - preTime > 200) {
                    preTime = currentTime;
                    beepManager.playBeepSoundAndVibrate();
                    restart(DEFAULT_ZOOM);
                    stopPreview();

                    Intent intent = new Intent(ScanActivity.this, ResultActivity.class);
                    intent.putExtra("result", result);
                    intent.putExtra("time", message.arg1);
                    startActivity(intent);

                }

            }
        }

        public void quit() {
            try {
                stopPreview();
                decodeHandle.getLooper().quit();
                decodeThread.join(500);
            } catch (InterruptedException e) {
                Log.w(TAG, e);
            }
        }

        public void restart(double zoomValue) {
            callbackFrame(decodeHandle, zoomValue);
        }

        /**
         * Call the MultiProcessor API in synchronous mode.
         */
        private String decodeSyn(int width, int height, byte[] data) {
            String result="";
            switch (decodeType){
                case 1://Zxing
                    result = ZxingUtils.decode(width, height, data);
                    break;
                case 2://商米
                    result = SunmiUtils.decode(width, height, data);
                    break;
                case 3://华为
                    result = HuaweiHmsUtils.decode(width, height, data, ScanActivity.this);
                    break;
            }
            return result;
        }
    }
}

3.4  解析工具类

public class ZxingUtils {
    public static String decode(int width, int height, byte[] data) {
        Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
        hints.put(DecodeHintType.CHARACTER_SET, "utf-8");

        Result rawResult = null;
        PlanarYUVLuminanceSource source = buildLuminanceSource(data, width, height);
        MultiFormatReader multiFormatReader = null;
        if (source != null) {
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
            try {
                multiFormatReader = new MultiFormatReader();
                multiFormatReader.setHints(hints);
                rawResult = multiFormatReader.decodeWithState(bitmap);
            } catch (ReaderException re) {
                // continue
            } finally {
                multiFormatReader.reset();
            }
        }
        if(rawResult==null){
            return null;
        }else {
            return rawResult.getText();
        }
    }



    /**
     * A factory method to build the appropriate LuminanceSource object based on the format
     * of the preview buffers, as described by Camera.Parameters.
     *
     * @param data   A preview frame.
     * @param width  The width of the image.
     * @param height The height of the image.
     * @return A PlanarYUVLuminanceSource instance.
     */
    public static PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
        Rect rect = new Rect(0,0,width,height);
        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);
    }
}

四 相册选区图片解析二维码

4.1 选择相册图片

private void setPictureScanOperation() {
        imgBtn = findViewById(R.id.img_btn);
        imgBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               Intent pickIntent = new Intent(Intent.ACTION_PICK,
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
               ZxingActivity.this.startActivityForResult(pickIntent, REQUEST_CODE_PHOTO);
            }
        });
    }

4.2 本地图片文件转换成可解码二维码的 Bitmap

/**
     * 将本地图片文件转换成可解码二维码的 Bitmap。为了避免图片太大,这里对图片进行了压缩。
     *
     * @param picturePath 本地图片文件路径
     */
    public static Bitmap getDecodeAbleBitmap(String picturePath) {
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(picturePath, options);
            int sampleSize = options.outHeight / 400;
            if (sampleSize <= 0) {
                sampleSize = 1;
            }
            options.inSampleSize = sampleSize;
            options.inJustDecodeBounds = false;

            return BitmapFactory.decodeFile(picturePath, options);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

4.3  解析二维码图片

 /**
     * 同步解析bitmap二维码。该方法是耗时操作,请在子线程中调用。
     *
     * @param bitmap 要解析的二维码图片
     * @return 返回二维码图片里的内容 或 null
     */
    public static String syncDecodeQRCode(Bitmap bitmap) {
        Result result;
        RGBLuminanceSource source = null;
        try {
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            int[] pixels = new int[width * height];
            bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
            source = new RGBLuminanceSource(width, height, pixels);
            result = new MultiFormatReader().decode(new BinaryBitmap(new HybridBinarizer(source)), new EnumMap<>(DecodeHintType.class));
            return result.getText();
        } catch (Exception e) {
            e.printStackTrace();
            if (source != null) {
                try {
                    result = new MultiFormatReader().decode(new BinaryBitmap(new GlobalHistogramBinarizer(source)), new EnumMap<>(DecodeHintType.class));
                    return result.getText();
                } catch (Throwable e2) {
                    e2.printStackTrace();
                }
            }
            return null;
        }
    }

五 生成二维码

5.1 支持修改边框颜色大小,二维码颜色大小,背景颜色,logo样式和圆角

5.2 activity_qrcode_style_xml

<FrameLayout
	android:layout_width="match_parent"
	android:layout_height="match_parent">

	<ImageView
		android:id="@+id/v_image"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:layout_gravity="center"
		android:layout_marginLeft="40dp"
		android:layout_marginRight="40dp" />
</FrameLayout>

 5.3 核心生成二维码源码,QRCodeEncoder.java

public class QRCodeEncoder {
    public static final Map<EncodeHintType, Object> HINTS = new EnumMap<>(EncodeHintType.class);

    static {
        HINTS.put(EncodeHintType.CHARACTER_SET, "utf-8");
        HINTS.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        HINTS.put(EncodeHintType.MARGIN, 0);
    }

    private QRCodeEncoder() {
    }

    /**
     * 同步创建黑色前景色、白色背景色的二维码图片。该方法是耗时操作,请在子线程中调用。
     *
     * @param content 要生成的二维码图片内容
     * @param size    图片宽高,单位为px
     */
    public static Bitmap syncEncodeQRCode(String content, int size) {
        return syncEncodeQRCode(content, size, Color.BLACK, Color.WHITE, null);
    }

    /**
     * 同步创建指定前景色、白色背景色的二维码图片。该方法是耗时操作,请在子线程中调用。
     *
     * @param content         要生成的二维码图片内容
     * @param size            图片宽高,单位为px
     * @param foregroundColor 二维码图片的前景色
     */
    public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor) {
        return syncEncodeQRCode(content, size, foregroundColor, Color.WHITE, null);
    }

    /**
     * 同步创建指定前景色、白色背景色、带logo的二维码图片。该方法是耗时操作,请在子线程中调用。
     *
     * @param content         要生成的二维码图片内容
     * @param size            图片宽高,单位为px
     * @param foregroundColor 二维码图片的前景色
     * @param logo            二维码图片的logo
     */
    public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor, Bitmap logo) {
        return syncEncodeQRCode(content, size, foregroundColor, Color.WHITE, logo);
    }

    /**
     * 同步创建指定前景色、指定背景色、带logo的二维码图片。该方法是耗时操作,请在子线程中调用。
     *
     * @param content         要生成的二维码图片内容
     * @param size            图片宽高,单位为px
     * @param foregroundColor 二维码图片的前景色
     * @param backgroundColor 二维码图片的背景色
     * @param logo            二维码图片的logo
     */
    public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor, int backgroundColor, Bitmap logo) {
        try {
            BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, size, size, HINTS);
            int[] pixels = new int[size * size];
            for (int y = 0; y < size; y++) {
                for (int x = 0; x < size; x++) {
                    if (matrix.get(x, y)) {
                        pixels[y * size + x] = foregroundColor;
                    } else {
                        pixels[y * size + x] = backgroundColor;
                    }
                }
            }
            Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
            bitmap.setPixels(pixels, 0, size, 0, 0, size, size);
            return addLogoToQRCode(bitmap, logo);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 同步创建指定前景色、指定背景色、带logo的二维码图片。该方法是耗时操作,请在子线程中调用。
     *
     * @param content         要生成的二维码图片内容
     * @param size            图片宽高,单位为px
     * @param foregroundColor 二维码图片的前景色
     * @param backgroundColor 二维码图片的背景色
     * @param logo            二维码图片的logo
     * @param borderColor     边框颜色
     */
    public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor, int backgroundColor, Bitmap logo ,int border , int borderColor) {
        int borderWidth= (int) dp2px(MyApp.getInstance(),4);
        size-=borderWidth*2;
        try {
            BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, size, size, HINTS);
            int[] pixels = new int[size * size];
            for (int y = 0; y < size; y++) {
                for (int x = 0; x < size; x++) {
                    if (matrix.get(x, y)) {
                        pixels[y * size + x] = foregroundColor;
                    } else {
                        pixels[y * size + x] = backgroundColor;
                    }
                }
            }
            Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
            bitmap.setPixels(pixels, 0, size, 0, 0, size, size);
            if(bitmap!=null){
                bitmap=addLogoToQRCode(bitmap, logo);
            }

            bitmap=addBorderToQRCode(bitmap,backgroundColor ,border,borderColor);

            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 添加logo到二维码图片上
     */
    private static Bitmap addBorderToQRCode(Bitmap src, int backgroundColor, int border , int borderColor) {
        if (src == null) {
            return src;
        }

        if(borderColor==0){
            borderColor=Color.parseColor("#63C99B");
        }

        int srcWidth = src.getWidth();
        int srcHeight = src.getHeight();


        int borderWidth= (int) dp2px(MyApp.getInstance(),4);
        Bitmap bitmap = Bitmap.createBitmap(srcWidth+borderWidth, srcHeight+borderWidth, Bitmap.Config.ARGB_8888);
        try {
            Canvas canvas = new Canvas(bitmap);
            canvas.drawColor(backgroundColor);
            if(border!=0){
                Paint paintRect = new Paint();
                paintRect.setColor(borderColor);
                paintRect.setStrokeWidth(borderWidth);
                if(border==1){
                    paintRect.setPathEffect(new DashPathEffect(new float[]{8, 8}, 0)); // 设置虚线样式
                }
                paintRect.setStyle(Paint.Style.STROKE);
                canvas.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), paintRect);
            }
            canvas.drawBitmap(src, borderWidth/2f, borderWidth/2f, null);

            canvas.save();
            canvas.restore();
        } catch (Exception e) {
            e.printStackTrace();
            bitmap = null;
        }
        return bitmap;
    }


    /**
     * 添加logo到二维码图片上
     */
    private static Bitmap addLogoToQRCode(Bitmap src, Bitmap logo) {
        if (src == null || logo == null) {
            return src;
        }

        int srcWidth = src.getWidth();
        int srcHeight = src.getHeight();
        int logoWidth = logo.getWidth();
        int logoHeight = logo.getHeight();

        float scaleFactor = srcWidth * 1.0f / 5 / logoWidth;
        Bitmap bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888);
        try {
            Canvas canvas = new Canvas(bitmap);
            canvas.drawBitmap(src, 0, 0, null);
            canvas.scale(scaleFactor, scaleFactor, srcWidth / 2, srcHeight / 2);
            canvas.drawBitmap(logo, (srcWidth - logoWidth) / 2, (srcHeight - logoHeight) / 2, null);
            canvas.save();
            canvas.restore();
        } catch (Exception e) {
            e.printStackTrace();
            bitmap = null;
        }
        return bitmap;
    }

    /**
     * 同步创建条形码图片
     *
     * @param content  要生成条形码包含的内容
     * @param width    条形码的宽度,单位px
     * @param height   条形码的高度,单位px
     * @param textSize 字体大小,单位px,如果等于0则不在底部绘制文字
     * @return 返回生成条形的位图
     */
    public static Bitmap syncEncodeBarcode(String content, int width, int height, int textSize) {
        if (TextUtils.isEmpty(content)) {
            return null;
        }
        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hints.put(EncodeHintType.MARGIN, 0);

        try {
            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.CODE_128, width, height, hints);
            int[] pixels = new int[width * height];
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    if (bitMatrix.get(x, y)) {
                        pixels[y * width + x] = 0xff000000;
                    } else {
                        pixels[y * width + x] = 0xffffffff;
                    }
                }
            }
            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
            if (textSize > 0) {
                bitmap = showContent(bitmap, content, textSize);
            }
            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 显示条形的内容
     *
     * @param barcodeBitmap 已生成的条形码的位图
     * @param content       条形码包含的内容
     * @param textSize      字体大小,单位px
     * @return 返回生成的新条形码位图
     */
    private static Bitmap showContent(Bitmap barcodeBitmap, String content, int textSize) {
        if (TextUtils.isEmpty(content) || null == barcodeBitmap) {
            return null;
        }
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        paint.setTextSize(textSize);
        paint.setTextAlign(Paint.Align.CENTER);
        int textWidth = (int) paint.measureText(content);
        Paint.FontMetrics fm = paint.getFontMetrics();
        int textHeight = (int) (fm.bottom - fm.top);
        float scaleRateX = barcodeBitmap.getWidth() * 1.0f / textWidth;
        if (scaleRateX < 1) {
            paint.setTextScaleX(scaleRateX);
        }
        int baseLine = barcodeBitmap.getHeight() + textHeight;
        Bitmap bitmap = Bitmap.createBitmap(barcodeBitmap.getWidth(), barcodeBitmap.getHeight() + 2 * textHeight, Bitmap.Config.ARGB_4444);
        Canvas canvas = new Canvas();
        canvas.drawColor(Color.WHITE);
        canvas.setBitmap(bitmap);
        canvas.drawBitmap(barcodeBitmap, 0, 0, null);
        canvas.drawText(content, barcodeBitmap.getWidth() / 2, baseLine, paint);
        canvas.save();
        canvas.restore();
        return bitmap;
    }

    public static float dp2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return dpValue * scale + 0.5f;
    }
}

5.4 异步调用QRCodeEncoder生成二维码

 private void updateQrcode(){
        new GenerateQrcodeTask().execute(content);
    }

    public class GenerateQrcodeTask extends AsyncTask<String, Void, Bitmap> {

        @Override
        protected Bitmap doInBackground(String... strings) {
            Bitmap logoBitmap=null;
            if(logo!=0){
                logoBitmap = BitmapFactory.decodeResource(getResources(), logo);
            }

            int borderColor22=TextUtils.isEmpty(borderColor)?0:Color.parseColor(borderColor);
            return QRCodeEncoder.syncEncodeQRCode(strings[0], (int) StatuesBarUtils.dp2px(mContext, 300f), Color.parseColor(qianColor),Color.parseColor(beinColor),logoBitmap, border , borderColor22);

        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            qrBitmap=bitmap;
            if (bitmap != null) {
                mDataBinding.vImage.setImageBitmap(qrBitmap);
            } else {
                Toast.makeText(mContext, "生成維碼失敗", Toast.LENGTH_SHORT).show();
            }
        }
    }

5.5 保存二维码,FileUtil.java工具类

public class FileUtil {

    public static String saveToImage(Bitmap bitmap) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            return MediaStore.Images.Media.insertImage(MyApp.getInstance().getContentResolver(), bitmap, "", "");
        } else {

            try {

                File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "qrcode");
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                File picFile = new File(dir, System.currentTimeMillis() + ".png");
                FileOutputStream fos = new FileOutputStream(picFile);
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
                fos.flush();
                fos.close();
                return picFile.getAbsolutePath();

            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        return null;
    }

 异步调用FileUtil,保存二维码

public class SaveBitmapTask extends AsyncTask<Bitmap, Void, String> {


        @Override
        protected String doInBackground(Bitmap... bitmaps) {
            return FileUtil.saveToImage(bitmaps[0]);
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            if (result != null) {
                Intent intent = new Intent(mContext, QrBarcodeEditResultActivity.class);
                intent.putExtra("filePath", result);
                startActivity(intent);
            } else {
                Toast.makeText(mContext, "保存二維碼失敗", Toast.LENGTH_SHORT).show();
            }
        }
    }

5.6 核心源码解析,我们知道bitmap是一个保存所有像素内容的容器,那二维码原理即是把内容解析到每一个像素里面。如下

  /**
 *
 * @param content         要生成的二维码图片内容
 * @param size            图片宽高,单位为px
 * @param foregroundColor 二维码图片的前景色
 * @param backgroundColor 二维码图片的背景色
 * @param logo            二维码图片的logo
 * @param borderColor     边框颜色
 */
public static Bitmap syncEncodeQRCode(String content, int size, int foregroundColor, int backgroundColor, Bitmap logo ,int border , int borderColor) {
	int borderWidth= (int) dp2px(MyApp.getInstance(),4);
	size-=borderWidth*2;
	try {
		//内容解析到字节矩阵
		BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, size, size, HINTS);
		//设置宽高像素数组
		int[] pixels = new int[size * size];
		//遍历记录图片每个像素信息
		for (int y = 0; y < size; y++) {
			for (int x = 0; x < size; x++) {
				if (matrix.get(x, y)) {
					pixels[y * size + x] = foregroundColor;
				} else {
					pixels[y * size + x] = backgroundColor;
				}
			}
		}
		//创建bitmap,设置像素数组
		Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
		bitmap.setPixels(pixels, 0, size, 0, 0, size, size);
		if(bitmap!=null){
			bitmap=addLogoToQRCode(bitmap, logo);
		}

		//添加logo
		bitmap=addBorderToQRCode(bitmap,backgroundColor ,border,borderColor);

		return bitmap;
	} catch (Exception e) {
		e.printStackTrace();
		return null;
	}
}