Android Zxing 二维码与相册选取图片扫描

Zxing 是谷歌的一个开源库,网上大多数二维码扫描功能都是基于该库实现功能的,现在来动手简单实现二维码扫描与相册图片读取二维码。
在写这篇文章时,参考了几位大佬的博客相关知识:https://blog.csdn.net/qq_34902522/article/details/78384661 ,他已经实现了封装二维码相册读取图片的功能的库YZxing
还有本次所依赖的大佬的所封装的扫描库
https://blog.csdn.net/qq_23547831/article/details/52037710
老规矩先上图:

二维码扫描录制.gif

由于申请权限代码过于繁琐,除了依赖Zxing ,这里还引入了郭霖大神的PermissionX

    implementation 'cn.yipianfengye.android:zxing-library:2.1'
    implementation 'com.permissionx.guolindev:permissionx:1.2.2'

在Mainifest中声明我们需要的的摄像头权限和Zxing需要的权限:振动器(扫描后解析结果会震动)

 
  

点击二维码扫描按钮后调用方法checkCamera()
代码如下:

  private void checkCamera() {
        PermissionX.init(this).permissions(CAMERA).onExplainRequestReason(new ExplainReasonCallback() {
            @Override
            public void onExplainReason(ExplainScope scope, List deniedList) {
                scope.showRequestReasonDialog(deniedList, "扫描二维码需要开启摄像头", "允许");
            }
        }).onForwardToSettings(new ForwardToSettingsCallback() {
            @Override
            public void onForwardToSettings(ForwardScope scope, List deniedList) {
                scope.showForwardToSettingsDialog(deniedList, "需要在应用程序设置中手动开启", "OK");
            }
        }).request(new RequestCallback() {
            @Override
            public void onResult(boolean allGranted, List grantedList, List deniedList) {
                if (allGranted) {
                    startActivityForResult(
                            new Intent(MainActivity.this, ScanQRCodeActivity.class)
                            , SCAN_RESULT);
                } else {
                    Toast.makeText(MainActivity.this, "权限已拒绝", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

在权限检查通过后(PermissionX具体用法参考上面贴出的链接),启动意图跳转到ScanQRCodeActivity二维码扫描界面:

public class ScanQRCodeActivity extends AppCompatActivity {
    private FrameLayout fl_my_container;
    public static final int SCAN_SUCCESS=1111;//扫描成功
    public static final int SCAN_FAIL=1112;//扫描失败

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scan_q_r_code);
        fl_my_container=findViewById(R.id.fl_my_container);
        initScan();
    }

    private void initScan() {
        try {
            /**
             * 二维码解析回调函数
             */
            CodeUtils.AnalyzeCallback analyzeCallback = new CodeUtils.AnalyzeCallback() {
                @Override
                public void onAnalyzeSuccess(Bitmap mBitmap, String result) {
                    if (!TextUtils.isEmpty(result)) {
                        setResult(SCAN_SUCCESS,getIntent().putExtra("success_result",result));
                        finish();
                    }
                }

                @Override
                public void onAnalyzeFailed() {
                    setResult(SCAN_FAIL,getIntent().putExtra("fail_result","扫描失败"));
                    finish();
                }
            };

            /**
             * 执行扫面Fragment的初始化操作
             */
            CaptureFragment captureFragment = new CaptureFragment();
            // 为二维码扫描界面设置定制化界面
            CodeUtils.setFragmentArgs(captureFragment, R.layout.my_camera);
            captureFragment.setAnalyzeCallback(analyzeCallback);
            /**
             * 替换我们的扫描控件
             */
            getSupportFragmentManager().beginTransaction().replace(R.id.fl_my_container, captureFragment).commit();
        } catch (Exception e) {

        }
    }
}

CodeUtils 是二维码扫描工具类Zxing下的包,根据回调结果响应返回数据给上一个Activity
其中布局activity_scan_q_r_code,my_camera 截图如下:


activity_scan_q_r_code.png
my_camera .png

扫描结果返回到MainActivity 中的 onActivityResult方法,将结果显示在控件TextView 上

  @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (resultCode){
            case ScanQRCodeActivity.SCAN_SUCCESS:
                tv_content.setText(data.getStringExtra("success_result"));
                break;

            case ScanQRCodeActivity.SCAN_FAIL:
                tv_content.setText(data.getStringExtra("fail_result"));
                break;
        }
    }

实现了扫描二维码后,接着来实现通过相册识别二维码
截图如下:


相册识别二维码.gif

由于相册图片是存储在SD卡上的,首先添加WRITE_EXTERNAL_STORAGE,授予APP对系统的读写权限

 

checkStorage()点击调用系统相册,动态申请权限

 private void checkStorage() {
        PermissionX.init(this).permissions(WRITE_STORAGE).onExplainRequestReason(new ExplainReasonCallback() {
            @Override
            public void onExplainReason(ExplainScope scope, List deniedList) {
                scope.showRequestReasonDialog(deniedList, "读取相册需要该权限", "允许");
            }
        })
                .onForwardToSettings(new ForwardToSettingsCallback() {
                    @Override
                    public void onForwardToSettings(ForwardScope scope, List deniedList) {
                        scope.showForwardToSettingsDialog(deniedList, "需要在应用程序设置中手动开启", "OK");
                    }
                })
                .request(new RequestCallback() {
                    @Override
                    public void onResult(boolean allGranted, List grantedList, List deniedList) {
                        if (allGranted) {
                            Intent intent = new Intent(Intent.ACTION_PICK,
                                    android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                            startActivityForResult(intent, DEVICE_PHOTO_REQUEST);
                        } else {
                            Toast.makeText(MainActivity.this, "权限已拒绝", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
    }

相关的请求码和常量如下:

public static final String WRITE_STORAGE = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public final static int DEVICE_PHOTO_REQUEST = 1234;

在onActivityResult 方法中对回调的结果进行操作

@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //系统相册返回请求
        switch (requestCode) {
            case DEVICE_PHOTO_REQUEST:
                if (data != null) {
                    Uri uri = data.getData();
                    String imagePath = BitMapUtil.getPicturePathFromUri(MainActivity.this, uri);
                    //对获取到的二维码照片进行压缩
                    Bitmap bitmap = BitMapUtil.compressPicture(imagePath);
                    Result result = setZxingResult(bitmap);
                    if (result == null) {
                        tv_content.setText("识别失败,请试试其它二维码");
                    } else {
                        tv_content.setText(result.getText());
                    }
                }
                break;
        }
    }

通过返回的Uri 获取其图片,优化的话可以对其压缩裁剪,使得Zxing识别更加快速,BitMapUtil已经分开处理判断7.0 uri的异常问题和Bitmap的压缩
工具类代码如下

public class BitMapUtil {
    public static String getPicturePathFromUri(Context context, Uri uri) {
        int sdkVersion = Build.VERSION.SDK_INT;
        if (sdkVersion >= 19) {
            return getPicturePathFromUriAboveApi19(context, uri);
        } else {
            return getPicturePathFromUriBelowAPI19(context, uri);
        }
    }


    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    private static String getPicturePathFromUriAboveApi19(Context context, Uri uri) {
        String filePath = null;
        if (DocumentsContract.isDocumentUri(context, uri)) {
            // 如果是document类型的 uri, 则通过document id来进行处理
            String documentId = DocumentsContract.getDocumentId(uri);
            if (isMediaDocument(uri)) { // MediaProvider
                // 使用':'分割
                String id = documentId.split(":")[1];

                String selection = MediaStore.Images.Media._ID + "=?";
                String[] selectionArgs = {id};
                filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);
            } else if (isDownloadsDocument(uri)) { // DownloadsProvider
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId));
                filePath = getDataColumn(context, contentUri, null, null);
            }
        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
            // 如果是 content 类型的 Uri
            filePath = getDataColumn(context, uri, null, null);
        } else if ("file".equals(uri.getScheme())) {
            // 如果是 file 类型的 Uri,直接获取图片对应的路径
            filePath = uri.getPath();
        }
        return filePath;
    }

    private static String getPicturePathFromUriBelowAPI19(Context context, Uri uri) {
        return getDataColumn(context, uri, null, null);
    }


    private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        String path = null;

        String[] projection = new String[]{MediaStore.Images.Media.DATA};
        Cursor cursor = null;
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                int columnIndex = cursor.getColumnIndexOrThrow(projection[0]);
                path = cursor.getString(columnIndex);
            }
        } catch (Exception e) {
            if (cursor != null) {
                cursor.close();
            }
        }
        return path;
    }


    private static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    private static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }


    /**
     *
     * 对图片压缩
     *
     * */

    public static Bitmap compressPicture(String imgPath) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imgPath, options);
        Log.e( "log","未压缩之前图片的宽:" + options.outWidth + "--未压缩之前图片的高:"
                + options.outHeight + "--未压缩之前图片大小:" + options.outWidth * options.outHeight * 4 / 1024 / 1024 + "M");

        options.inSampleSize = calculateInSampleSize(options, 100, 100);
        Log.e( "log" ," inSampleSize:" + options.inSampleSize);
        options.inJustDecodeBounds = false;
        Bitmap afterCompressBm = BitmapFactory.decodeFile(imgPath, options);
//      //默认的图片格式是Bitmap.Config.ARGB_8888
        Log.e("log" ," 图片的宽:" + afterCompressBm.getWidth() + "--图片的高:"
                + afterCompressBm.getHeight() + "--图片大小:" + afterCompressBm.getWidth() * afterCompressBm.getHeight() * 4 / 1024 / 1024 + "M");
        return afterCompressBm;
    }


    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

}

这个方法就是我们这次最主要的功能通过 setZxingResult()方法对优化完后的图片调用二维码库里的类来解析读取,并且返回结果展示

 private static Result setZxingResult(Bitmap bitmap) {
        if (bitmap == null) return null;
        int picWidth = bitmap.getWidth();
        int picHeight = bitmap.getHeight();
        int[] pix = new int[picWidth * picHeight];
        //Log.e(TAG, "decodeFromPicture:图片大小: " + bitmap.getByteCount() / 1024 / 1024 + "M");
        bitmap.getPixels(pix, 0, picWidth, 0, 0, picWidth, picHeight);
        //构造LuminanceSource对象
        RGBLuminanceSource rgbLuminanceSource = new RGBLuminanceSource(picWidth
                , picHeight, pix);
        BinaryBitmap bb = new BinaryBitmap(new HybridBinarizer(rgbLuminanceSource));
        //因为解析的条码类型是二维码,所以这边用QRCodeReader最合适。
        QRCodeReader qrCodeReader = new QRCodeReader();
        Map hints = new EnumMap<>(DecodeHintType.class);
        hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
        hints.put(DecodeHintType.TRY_HARDER, true);
        Result result;
        try {
            result = qrCodeReader.decode(bb, hints);
            return result;
        } catch (NotFoundException | ChecksumException | FormatException e) {
            e.printStackTrace();
            return null;
        }
    }

至此,功能已经实现完毕,希望能对各位起到一点作用
在最后附上本项目的GitHub链接

你可能感兴趣的:(Android Zxing 二维码与相册选取图片扫描)