zxing開源庫的基本使用
如果你的專案中有模組跟二維碼相關的話,那你一定聽過或者用過大名鼎鼎的zxing開源庫。
什麼是zxing?
ZXing是一個開源的,用Java實現的多種格式的1D/2D條碼影象處理庫,它包含了聯絡到其他語言的埠。zxing可以實現使用手機的內建的攝像頭完成條形碼的掃描及解碼。
本篇文章就來學習zxing的基本使用,學習了以下幾個內容就能滿足大部分專案中的二維碼相關需求:
- 通過攝像頭掃描二維碼圖片,讀取圖片內容
- 從相簿中選取二維碼圖片,讀取圖片內容
- 自己輸入字串內容,生成二維碼圖片
- 長按識別自己生成的二維碼圖片
如何依賴zxing到專案中?
如果你還在使用zxing的jar包、或者你是把zxing的程式碼複製到專案中,使用這兩種方式依賴的話那就out了,現在Android Studio可支援zxing線上依賴,目前最新版本是3.3.3。線上依賴的好處我就不多說了,相信大家都懂。
新建專案,在app/build.gradle檔案中線上依賴:
implementation 'com.google.zxing:core:3.3.3'
加入許可權
因為掃描二維碼需要攝像頭許可權,把圖片儲存到本地需要sdcard許可權,所以需要在AndroidManifest.xml中加入相應的許可權
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />
當然現在市面上的手機大部分都是6.0以上的作業系統了,所以還得在MainActivity的onCreate方法中動態申請以上這兩個許可權。
//6.0版本或以上需請求許可權 String[] permissions=new String[]{Manifest.permission. WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { requestPermissions(permissions,PERMS_REQUEST_CODE); }
掃描二維碼
專案依賴進來了,許可權也有了,開始用程式碼實現第一個功能。點選掃描二維碼按鈕,開啟一個ScanActivity,這個Activity是我之前封裝好的,裡面處理了掃描二維碼的整個流程,掃描成功後會把掃描結果返回。ScanActivity類的程式碼有點多,就不貼出來了,有興趣的自己看原始碼。
Intent intent = new Intent(MainActivity.this,ScanActivity.class);
startActivityForResult(intent,SCAN_REQUEST_CODE);
重寫onActivityResult方法,監聽掃描結果。
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == SCAN_REQUEST_CODE && resultCode == RESULT_OK) {
String input = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
showToast("掃描結果:"+input);
}
}
從相簿中選擇二維碼圖片進行識別
首先啟動系統相簿,從相簿中選擇一張圖片。
Intent innerIntent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
Intent wrapperIntent = Intent.createChooser(innerIntent, "選擇二維碼圖片");
startActivityForResult(wrapperIntent, SELECT_IMAGE_REQUEST_CODE);
然後在onActivityResult中獲取選擇圖片路徑,呼叫BitmapUtil.parseQRcode方法解析二維碼圖片。
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if(requestCode==SELECT_IMAGE_REQUEST_CODE){//從相簿選擇圖片
String[] proj = {MediaStore.Images.Media.DATA};
// 獲取選中圖片的路徑
Cursor cursor = this.getContentResolver().query(intent.getData(),proj, null, null, null);
if (cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
String photoPath = cursor.getString(columnIndex);
String result= BitmapUtil.parseQRcode(photoPath);
if (!TextUtils.isEmpty(result)) {
showToast("從相簿選擇的圖片識別結果:"+result);
} else {
showToast("從相簿選擇的圖片不是二維碼圖片");
}
}
cursor.close();
}
}
接下來看parseQRcode方法,
/**
* 解析二維碼圖片
* @param bitmapPath 檔案路徑
* @return
*/
public static String parseQRcode(String bitmapPath){
Bitmap bitmap = BitmapFactory.decodeFile(bitmapPath, null);
String result=parseQRcode(bitmap);
return result;
}
從上面的方法中看到直接把檔案路徑讀取成Bitmap,繼續呼叫parseQRcode方法把Bitmap物件傳進去,這裡用到了方法過載。
public static String parseQRcode(Bitmap bmp){
bmp=comp(bmp);//bitmap壓縮 如果不壓縮的話在低配置的手機上解碼很慢
int width = bmp.getWidth();
int height = bmp.getHeight();
int[] pixels = new int[width * height];
bmp.getPixels(pixels, 0, width, 0, 0, width, height);
QRCodeReader reader = new QRCodeReader();
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);//優化精度
hints.put(DecodeHintType.CHARACTER_SET,"utf-8");//解碼設定編碼方式為:utf-8
try {
Result result = reader.decode(new BinaryBitmap(
new HybridBinarizer(new RGBLuminanceSource(width, height, pixels))), hints);
return result.getText();
} catch (NotFoundException e) {
Log.i("ansen",""+e.toString());
e.printStackTrace();
} catch (ChecksumException e) {
e.printStackTrace();
} catch (FormatException e) {
e.printStackTrace();
}
return null;
}
如果傳入的是一個Bitmap物件,先呼叫comp方法對Bitmap進行壓縮(壓縮程式碼這裡不貼出),獲取圖片寬高,把影象的每個畫素顏色轉為int值,存入pixels陣列。
然後初始化QRCodeReader物件,呼叫decode方法進行解碼,這個方法有兩個引數,引數1是一個BinaryBitmap物件,第二個引數是一個Map型別,key的值是DecodeHintType列舉型別,這裡我們put了兩個值,優化精度跟設定編碼方式為。這個方法還會返回一個Result物件,最後呼叫result.getText()方法獲取二維碼內容。
生成二維碼圖片
生成二維碼圖片呼叫CreateQRBitmp.createQRCodeBitmap方法生成,這個方法是我們自己封裝的,需要傳入兩個引數,引數1:圖片內容、引數2:二維碼圖片最中間顯示的logo(Bitmap物件)。
String contentString = etInput.getText().toString().trim();
if(TextUtils.isEmpty(contentString)){
showToast("請輸入二維碼內容");
return ;
}
Log.i("ansen","輸入的內容:"+contentString);
Bitmap portrait = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
//兩個方法,一個不傳大小,使用預設
qrCodeBitmap = CreateQRBitmp.createQRCodeBitmap(contentString, portrait);
ivQrImage.setImageBitmap(qrCodeBitmap);
createQRCodeBitmap原始碼如下:
public static Bitmap createQRCodeBitmap(String content,Bitmap portrait) {
// 用於設定QR二維碼引數
Hashtable<EncodeHintType, Object> qrParam = new Hashtable<>();
// 設定QR二維碼的糾錯級別——這裡選擇最高H級別
qrParam.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
// 設定編碼方式
qrParam.put(EncodeHintType.CHARACTER_SET, "UTF-8");
// 生成QR二維碼資料——這裡只是得到一個由true和false組成的陣列
// 引數順序分別為:編碼內容,編碼型別,生成圖片寬度,生成圖片高度,設定引數
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(content,
BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, qrParam);
// 開始利用二維碼資料建立Bitmap圖片,分別設為黑白兩色
int w = bitMatrix.getWidth();
int h = bitMatrix.getHeight();
int[] data = new int[w * h];
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
if (bitMatrix.get(x, y))
data[y * w + x] = 0xff000000;// 黑色
else
data[y * w + x] = 0x00ffffff;// -1 相當於0xffffffff 白色
}
}
// 建立一張bitmap圖片,採用最高的圖片效果ARGB_8888
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
// 將上面的二維碼顏色陣列傳入,生成圖片顏色
bitmap.setPixels(data, 0, w, 0, 0, w, h);
if(portrait!=null){
createQRCodeBitmapWithPortrait(bitmap,initProtrait(portrait));
}
return bitmap;
} catch (WriterException e) {
e.printStackTrace();
}
return null;
}
大部分程式碼都有註釋,首先就是呼叫MultiFormatWriter物件的encode方法生成BitMatrix物件,這裡我們傳入5個引數,引數1:內容、引數2:二維碼格式、引數3:圖片寬、引數4:圖片高、引數5:二維碼生成的引數(例如編碼方法以及糾錯級別)。
拿到BitMatrix物件後開始利用二維碼資料建立Bitmap圖片,分別設為黑白兩色,建立一個寬高一樣的Bitmap物件,呼叫setPixels方法把上面的二維碼顏色陣列傳入,生成圖片顏色。如果中間需要新增logo呼叫createQRCodeBitmapWithPortrait方法。最後把Bitmap物件返回。
長按識別二維碼以及儲存圖片
識別二維碼跟從相簿中選擇圖片進行識別功能上很相似,所以就不在做重複介紹了,就介紹一下儲存圖片功能。
從下面原始碼中看到,首先獲取rootView,從rootView中獲取根佈局的Bitmap,然後呼叫ImageUtil.savePicToLocal方法儲存圖片。
View view = getWindow().getDecorView().getRootView();//找到當前頁面的根佈局
view.setDrawingCacheEnabled(true);//禁用繪圖快取
view.buildDrawingCache();
Bitmap temBitmap = view.getDrawingCache();
ImageUtil.savePicToLocal(temBitmap,MainActivity.this);
//禁用DrawingCahce否則會影響效能 ,而且不禁止會導致每次截圖到儲存的是快取的點陣圖
view.setDrawingCacheEnabled(false);//識別完成之後開啟繪圖快取
showToast("儲存圖片到本地成功");
ImageUtil.savePicToLocal方法也比較簡單,就是把一個Bitmap儲存到本地Sdcard上。需要注意的是記得傳送一個廣播,不然需要重啟手機才能在系統相簿中看到這個圖片。
public static void savePicToLocal(Bitmap bitmap, Context context) {
String filePath=Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/screen"+File.separator + System.currentTimeMillis() + ".png";
if (bitmap != null) {
try {
// 圖片檔案路徑
Log.i("ansen", "filePath:" + filePath);
File file = new File(filePath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
FileOutputStream os = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri uri = Uri.fromFile(new File(filePath));
intent.setData(uri);
context.sendBroadcast(intent);
os.flush();
os.close();
} catch (Exception e) {
}
}
}
程式碼終於寫完了,接下來看看效果,由於模擬器沒有攝像頭,而真機又不能錄製Gif圖片,所以攝像頭掃描二維碼就不演示啦,大家自己下載原始碼執行檢視效果。
當然少不了原始碼,下載地址如下:
https://github.com/ansen666/ZxingTest
如果你想第一時間看我的後期文章,掃碼關注公眾號,長期推送Android開發文章、最新動態、開源專案,讓你各種漲姿勢。
Android開發666 - 安卓開發技術分享
掃描二維碼加關注