Zxing 是谷歌的一个开源库,网上大多数二维码扫描功能都是基于该库实现功能的,现在来动手简单实现二维码扫描与相册图片读取二维码。
在写这篇文章时,参考了几位大佬的博客相关知识:https://blog.csdn.net/qq_34902522/article/details/78384661 ,他已经实现了封装二维码相册读取图片的功能的库YZxing
还有本次所依赖的大佬的所封装的扫描库
https://blog.csdn.net/qq_23547831/article/details/52037710
老规矩先上图:
由于申请权限代码过于繁琐,除了依赖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 截图如下:
扫描结果返回到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;
}
}
实现了扫描二维码后,接着来实现通过相册识别二维码
截图如下:
由于相册图片是存储在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链接