Zxing 是谷歌的一個開源庫满败,網(wǎng)上大多數(shù)二維碼掃描功能都是基于該庫實現(xiàn)功能的楔壤,現(xiàn)在來動手簡單實現(xiàn)二維碼掃描與相冊圖片讀取二維碼拷况。
在寫這篇文章時,參考了幾位大佬的博客相關(guān)知識:https://blog.csdn.net/qq_34902522/article/details/78384661 梗顺,他已經(jīng)實現(xiàn)了封裝二維碼相冊讀取圖片的功能的庫YZxing
還有本次所依賴的大佬的所封裝的掃描庫
https://blog.csdn.net/qq_23547831/article/details/52037710
老規(guī)矩先上圖:
由于申請權(quán)限代碼過于繁瑣泡孩,除了依賴Zxing ,這里還引入了郭霖大神的PermissionX
implementation 'cn.yipianfengye.android:zxing-library:2.1'
implementation 'com.permissionx.guolindev:permissionx:1.2.2'
在Mainifest中聲明我們需要的的攝像頭權(quán)限和Zxing需要的權(quán)限:振動器(掃描后解析結(jié)果會震動)
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.VIBRATE" />
點擊二維碼掃描按鈕后調(diào)用方法checkCamera()
代碼如下:
private void checkCamera() {
PermissionX.init(this).permissions(CAMERA).onExplainRequestReason(new ExplainReasonCallback() {
@Override
public void onExplainReason(ExplainScope scope, List<String> deniedList) {
scope.showRequestReasonDialog(deniedList, "掃描二維碼需要開啟攝像頭", "允許");
}
}).onForwardToSettings(new ForwardToSettingsCallback() {
@Override
public void onForwardToSettings(ForwardScope scope, List<String> deniedList) {
scope.showForwardToSettingsDialog(deniedList, "需要在應(yīng)用程序設(shè)置中手動開啟", "OK");
}
}).request(new RequestCallback() {
@Override
public void onResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {
if (allGranted) {
startActivityForResult(
new Intent(MainActivity.this, ScanQRCodeActivity.class)
, SCAN_RESULT);
} else {
Toast.makeText(MainActivity.this, "權(quán)限已拒絕", Toast.LENGTH_SHORT).show();
}
}
});
}
在權(quán)限檢查通過后(PermissionX具體用法參考上面貼出的鏈接)寺谤,啟動意圖跳轉(zhuǎn)到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 {
/**
* 二維碼解析回調(diào)函數(shù)
*/
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();
}
};
/**
* 執(zhí)行掃面Fragment的初始化操作
*/
CaptureFragment captureFragment = new CaptureFragment();
// 為二維碼掃描界面設(shè)置定制化界面
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下的包仑鸥,根據(jù)回調(diào)結(jié)果響應(yīng)返回數(shù)據(jù)給上一個Activity
其中布局activity_scan_q_r_code,my_camera 截圖如下:
掃描結(jié)果返回到MainActivity 中的 onActivityResult方法变屁,將結(jié)果顯示在控件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;
}
}
實現(xiàn)了掃描二維碼后眼俊,接著來實現(xiàn)通過相冊識別二維碼
截圖如下:
由于相冊圖片是存儲在SD卡上的,首先添加WRITE_EXTERNAL_STORAGE粟关,授予APP對系統(tǒng)的讀寫權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
checkStorage()點擊調(diào)用系統(tǒng)相冊,動態(tài)申請權(quán)限
private void checkStorage() {
PermissionX.init(this).permissions(WRITE_STORAGE).onExplainRequestReason(new ExplainReasonCallback() {
@Override
public void onExplainReason(ExplainScope scope, List<String> deniedList) {
scope.showRequestReasonDialog(deniedList, "讀取相冊需要該權(quán)限", "允許");
}
})
.onForwardToSettings(new ForwardToSettingsCallback() {
@Override
public void onForwardToSettings(ForwardScope scope, List<String> deniedList) {
scope.showForwardToSettingsDialog(deniedList, "需要在應(yīng)用程序設(shè)置中手動開啟", "OK");
}
})
.request(new RequestCallback() {
@Override
public void onResult(boolean allGranted, List<String> grantedList, List<String> 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, "權(quán)限已拒絕", Toast.LENGTH_SHORT).show();
}
}
});
}
相關(guān)的請求碼和常量如下:
public static final String WRITE_STORAGE = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public final static int DEVICE_PHOTO_REQUEST = 1234;
在onActivityResult 方法中對回調(diào)的結(jié)果進行操作
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//系統(tǒng)相冊返回請求
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 獲取其圖片,優(yōu)化的話可以對其壓縮裁剪,使得Zxing識別更加快速,BitMapUtil已經(jīng)分開處理判斷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,直接獲取圖片對應(yīng)的路徑
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);
// //默認(rèn)的圖片格式是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()方法對優(yōu)化完后的圖片調(diào)用二維碼庫里的類來解析讀取澎灸,并且返回結(jié)果展示
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);
//構(gòu)造LuminanceSource對象
RGBLuminanceSource rgbLuminanceSource = new RGBLuminanceSource(picWidth
, picHeight, pix);
BinaryBitmap bb = new BinaryBitmap(new HybridBinarizer(rgbLuminanceSource));
//因為解析的條碼類型是二維碼,所以這邊用QRCodeReader最合適性昭。
QRCodeReader qrCodeReader = new QRCodeReader();
Map<DecodeHintType, Object> 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;
}
}
至此拦止,功能已經(jīng)實現(xiàn)完畢,希望能對各位起到一點作用
在最后附上本項目的GitHub鏈接