本文同步到CSDN
現(xiàn)在二維碼使用越來越廣泛了酸员,幾乎處處可見,并且 公司相關(guān)的項目中幾乎全部都和二維碼掃描有關(guān)讳嘱,所以總結(jié)一下自己的使用心路歷程幔嗦,總覺得要做點什么來記錄自己的成長,讓自己的成長有跡可循沥潭,如果恰好能夠幫助到你邀泉,我當(dāng)然會很開心啦,如果沒幫到钝鸽,請忽略汇恤。。
廢話結(jié)束拔恰,正文開始
小白之旅因谎,如有問題 望指正,萬分感謝 ????
首先推薦幾篇
Android中常用的就是 zxing 仁连,開源項目地址:https://github.com/zxing/zxing蓝角。首先我們下載項目到本地,然后加載到自己的工程中饭冬,可參考 我的上一片博文 AndroidStudio 導(dǎo)入 Zxing Android 項目使鹅,這個是作為庫文件導(dǎo)入的,當(dāng)然我們也可以單獨在工程中分出一個包昌抠,來實現(xiàn)掃描二維碼的功能患朱。
特別提醒: 如果作為 module 完整導(dǎo)入項目則配置好后就可以運行,如果單獨作為一個包炊苫,獨立出自己需要的內(nèi)容裁厅,需要復(fù)制 layout 文件冰沙,和資源文件 res-->values 里面的 ids.xml 和 res-->raw 里面的 beep.ogg 文件
一、了解 ZXing
ZXing 導(dǎo)入后执虹,所有的內(nèi)容如上圖所示拓挥,運行示例代碼,發(fā)現(xiàn)是橫屏用來掃描條形碼的袋励,包括識別相冊中的二維碼侥啤,掃描記錄,剪切板茬故,生成二維碼等功能盖灸,我們可以根據(jù)需要,分離出自己需要的那一部分磺芭,首先了解 ZXing 這個項目中各個部分的作用赁炎,然后開始 DELETE ??操作。
把 CaptureActivity 作為入口開始分析.....
(PS:不一定正確钾腺,是我自己的理解徙垫,不過 大概是這樣子的,如有失誤垮庐,后續(xù)會修正)
CaptureActivity: 打開相機并在后臺線程進行實際掃描松邪,繪制取景框,并進行圖像掃描反饋哨查。
CameraManager:相機管理類,調(diào)用相機 預(yù)覽剧辐,繪制掃描框的具體內(nèi)容都在這里寒亥。相關(guān)的相機的配置也在這里設(shè)置,例如前后攝像頭切換荧关,是否自動聚焦等溉奕。在 CaptureActivity 中,CameraManager 在 onResume() 中獲取對象忍啤,openDriver() 用來打開相機
CaptureActivityHandler: 處理所有的捕獲的狀態(tài)消息加勤,在 initCamera()中,打開相機后同波,創(chuàng)建該對象鳄梅,根據(jù)描述,應(yīng)該是傳遞消息的, 把需要解碼的內(nèi)容 傳遞給 然后 把結(jié)果返回到 CaptureActivity
DecodeThread: 處理最困難圖像解析工作未檩,包括解析和生成二維碼的內(nèi)容戴尸,和 DecodeHandler 搭檔
DecodeHandler: 把掃碼結(jié)果返回給 CaptureActivityHandler。
ViewfinderView:自定義的掃描界面冤狡,如果想繪制自己想要的掃描效果孙蒙,可以在這里動手??
大致是這樣子的项棠,從 CaptureActivity 入手,一點點看挎峦,就會明白是怎么回事香追,寫不明白的感覺。大家 也可以看 我這里實現(xiàn)的 DEMO 坦胶, 里面相應(yīng)的都給注釋了下透典。Github:
二、修改UI, 修改掃描頁面
大致的流程清楚后迁央,首先來最簡單的掷匠,把掃描的界面繪制成我們想要的樣子.
首先,把屏幕方向改為豎屏岖圈,相應(yīng)地 相機掃描方向也要旋轉(zhuǎn)讹语,
在 CameraManager 中 getFramingRectInPreview() 修改:
rect.left = rect.left * cameraResolution.y / screenResolution.x;
rect.right = rect.right * cameraResolution.y / screenResolution.x;
rect.top = rect.top * cameraResolution.x / screenResolution.y;
rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y;
然后,在 DecodeHandler 里面 decode() 中添加:
// 旋轉(zhuǎn)攝像頭掃描方向后 處理 可以掃描二維碼 也可以掃描條形碼
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++)
rotatedData[x * height + height - y - 1] = data[x + y * width];
}
int tmp = width;
width = height;
height = tmp;
data = rotatedData;
PlanarYUVLuminanceSource source = activity.getCameraManager().buildLuminanceSource(data, width, height);
我們想要的其實是一個正方形的掃描框蜂科,然后顽决,調(diào)整 ViewfinderView 繪制掃描框預(yù)覽界面,具體的可以在 ViewfinderView 的 onDraw() 方法中實現(xiàn)导匣,
Rect frame = cameraManager.getFramingRect();
想要繪制正方形才菠,getFramingRect() 獲取的預(yù)覽界面寬高,可以設(shè)置成一樣的贡定,均使用屏幕的寬來設(shè)置
int width = findDesiredDimensionInRange(screenResolution.x, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH);
int height = findDesiredDimensionInRange(screenResolution.x, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH);
// int height = findDesiredDimensionInRange(screenResolution.y, MIN_FRAME_HEIGHT, MAX_FRAME_HEIGHT);
繪制四個角
//畫掃描框邊上的角赋访,總共8個部分
paint.setColor(getResources().getColor(R.color.result_view));
canvas.drawRect(frame.left, frame.top, frame.left + ScreenRate, frame.top + CORNER_WIDTH, paint);
canvas.drawRect(frame.left, frame.top, frame.left + CORNER_WIDTH, frame.top + ScreenRate, paint);
canvas.drawRect(frame.right - ScreenRate, frame.top, frame.right, frame.top + CORNER_WIDTH, paint);
canvas.drawRect(frame.right - CORNER_WIDTH, frame.top, frame.right, frame.top + ScreenRate, paint);
canvas.drawRect(frame.left, frame.bottom - CORNER_WIDTH, frame.left + ScreenRate, frame.bottom, paint);
canvas.drawRect(frame.left, frame.bottom - ScreenRate, frame.left + CORNER_WIDTH, frame.bottom, paint);
canvas.drawRect(frame.right - ScreenRate, frame.bottom - CORNER_WIDTH, frame.right, frame.bottom, paint);
canvas.drawRect(frame.right - CORNER_WIDTH, frame.bottom - ScreenRate, frame.right, frame.bottom, paint);
//繪制中間的線,每次刷新界面,中間的線往下移動SPEEN_DISTANCE
slideTop += SPEEN_DISTANCE;
if (slideTop >= frame.bottom) {
slideTop = frame.top;
}
canvas.drawRect(frame.left + MIDDLE_LINE_PADDING, slideTop - MIDDLE_LINE_WIDTH / 2, frame.right - MIDDLE_LINE_PADDING, slideTop + MIDDLE_LINE_WIDTH / 2, paint);
//畫掃描框下面的字
paint.setColor(getResources().getColor(R.color.white));
paint.setTextSize(TEXT_SIZE * density);
paint.setAlpha(225);
paint.setTypeface(Typeface.DEFAULT);
String text = getResources().getString(R.string.msg_default_status);
float textWidth = paint.measureText(text);
canvas.drawText(text, (width - textWidth) / 2, (float) (frame.bottom + (float) TEXT_PADDING_TOP * density), paint);
實現(xiàn)效果圖:
三缓待、實現(xiàn)掃一掃
UI 繪制好后蚓耽,啟動 CaptureActivity 調(diào)用掃一掃,CaptureActivity 中的 handleDecode() 處理掃描后的結(jié)果旋炒,把處理好的結(jié)果返回步悠,
public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor) {
inactivityTimer.onActivity();
String result = rawResult.getText();
if (result.equals("")) {
Toast.makeText(CaptureActivity.this, "Scan Failed!", Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "掃描的結(jié)果" + result);
// 把掃描結(jié)果返回到掃描的頁面
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("result", result);
intent.putExtras(bundle);
setResult(RESULT_OK, intent);
}
CaptureActivity.this.finish();
}
四、添加切換前后攝像頭
掃描二維碼調(diào)用的相機是系統(tǒng)相機瘫镇,如果手機本身支持前后攝像頭的話(廢話鼎兽,現(xiàn)在還有不支持前置攝像頭的手機嗎,我要不能自拍的手機干嘛铣除。谚咬。。)通孽,應(yīng)該都沒有問題序宦,主要是切換下前后攝像頭就可以了。所以呢,主要看調(diào)用相機部分互捌,camera -> open 下 OpenCameraInterface 主要是用來處理相機相關(guān)的潘明,所以看下,發(fā)現(xiàn)
while (cameraId < numCameras) {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, cameraInfo);
if (CameraFacing.values()[cameraInfo.facing] == CameraFacing.BACK) {
break;
}
cameraId++;
}
CameraFacing.BACK秕噪,當(dāng)為后置攝像頭時返回了當(dāng)前相機钳降,也就是,默認掃一掃僅僅支持后置掃一掃腌巾,這里我們改為支持前后攝像頭遂填,根據(jù)攝像頭傳遞過來的參數(shù)進行修改,open() 打開相機的方法中添加一個參數(shù)澈蝙,用來判斷是前置攝像頭還是后置攝像頭
public static OpenCamera open(int cameraId, CameraFacing cf) {
int numCameras = Camera.getNumberOfCameras();
if (numCameras == 0) {
Log.w(TAG, "No cameras!");
return null;
}
if (cameraId >= numCameras) {
Log.w(TAG, "Requested camera does not exist: " + cameraId);
return null;
}
if (cameraId <= NO_REQUESTED_CAMERA) {
cameraId = 0;
while (cameraId < numCameras) {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, cameraInfo);
if(cf == CameraFacing.BACK){
if (CameraFacing.values()[cameraInfo.facing] == cf.BACK) {
break;
}
}
if(cf == CameraFacing.FRONT){
if (CameraFacing.values()[cameraInfo.facing] == cf.FRONT) {
break;
}
}
cameraId++;
}
if (cameraId == numCameras) {
Log.i(TAG, "No camera facing " + CameraFacing.BACK + "; returning camera #0");
cameraId = 0;
}
}
Log.i(TAG, "Opening camera #" + cameraId);
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, cameraInfo);
Camera camera = Camera.open(cameraId);
if (camera == null) {
return null;
}
return new OpenCamera(cameraId,
camera,
CameraFacing.values()[cameraInfo.facing],
cameraInfo.orientation);
}
然后發(fā)現(xiàn) 其實調(diào)用 OpenCameraInterface
里面 open()
方法的是 CameraManager
的 openDriver()
吓坚,也就是說需要在 openDriver()
里面?zhèn)鬟f參數(shù),再往外找灯荧,發(fā)現(xiàn)CaptureActivity
的initCamera()
是打開相機礁击,設(shè)置參數(shù)的地方,所以在這里把設(shè)置前后攝像頭的參數(shù)傳遞過去逗载,
private void initCamera(SurfaceHolder surfaceHolder) {
if (surfaceHolder == null) {
throw new IllegalStateException("No SurfaceHolder provide");
}
if (cameraManager.isOpen()) {
Log.w(TAG, "initCamera() while already open -- late SurfaceView callback?");
// 如果相機已經(jīng)打開 則關(guān)閉當(dāng)前相機 重建一個 切換攝像頭哆窿,,如果不需要切換前置攝像頭 則這里直接return
handler = null;
cameraManager.closeDriver();
// return;
}
try {
cameraManager.openDriver(surfaceHolder, cfbf);
// Creating the handler starts the preview, which can also throw a RuntimeException.
if (handler == null) {
handler = new CaptureActivityHandler(this, decodeFormats, decodeHints, characterSet, cameraManager);
}
// decodeOrStoreSavedBitmap(null, null);
} catch (IOException ioe) {
Log.w(TAG, ioe);
displayFrameworkBugMessageAndExit();
} catch (RuntimeException e) {
// Barcode Scanner has seen crashes in the wild of this variety:
// java.?lang.?RuntimeException: Fail to connect to camera service
Log.w(TAG, "Unexpected error initializing camera", e);
displayFrameworkBugMessageAndExit();
}
}
這樣就會調(diào)用相應(yīng)的前置或后置攝像頭厉斟。
BUT 修改后挚躯,發(fā)現(xiàn) 報錯了,前后攝像頭不能切換 直接卡死 2粱唷码荔!
Error:
Unexpected exception while focusing
Camera is being used after Camera.release() was called
看報錯信息大致是說,相機對象已經(jīng)被釋放了感挥,但是還在使用目胡,嗯,想想自己干了什么會這樣链快,對,cameraManager.closeDriver();
眉尸,在切換前后攝像頭時域蜗,當(dāng)相機是打開的時候就先釋放,重新創(chuàng)建一個對象噪猾,所以在釋放的時候霉祸,不能繼續(xù)使用相機,在closeDriver()
添加袱蜡,相機釋放之前丝蹭,先停止預(yù)覽。
camera.getCamera().setPreviewCallback(null);
camera.getCamera().lock();
stopPreview();
在capture.xml中添加一個組件坪蚁,用來切換相機前后攝像頭奔穿。
實現(xiàn)效果:
五镜沽、生成二維碼
生成二維碼部分的功能主要在QRCodeEncoder.java
里面,這里僅僅生成二維碼贱田,對于二維碼內(nèi)容的格式使用默認的缅茉,重寫構(gòu)造函數(shù)只傳入我們需要的參數(shù)
public QRCodeEncoder(Context activity, int dimension, String contnt) {
this.activity = activity;
this.dimension = dimension; // 生成二維碼圖片的尺寸
this.contents = contnt; // 生成的二維碼的內(nèi)容
}
public Bitmap encodeAsBitmap() throws WriterException {
Log.e("二維碼圖片參數(shù)",String.valueOf(dimension));
String contentsToEncode = contents;
if (contentsToEncode == null) {
return null;
}
Map<EncodeHintType,Object> hints = new HashMap<>();
// String encoding = guessAppropriateEncoding(contentsToEncode);
// if (encoding != null) {
// hints = new EnumMap<>(EncodeHintType.class);
// hints.put(EncodeHintType.CHARACTER_SET, encoding);
// }
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
BitMatrix result;
try {
format = BarcodeFormat.QR_CODE;
Log.e(TAG,"contentsToEncode == " + contentsToEncode);
result = new MultiFormatWriter().encode(contentsToEncode, format, dimension, dimension, hints);
int width = result.getWidth();
int height = result.getHeight();
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
} catch (IllegalArgumentException iae) {
Log.e(TAG,"Error == " + iae.toString());
// Unsupported format
return null;
}
}
調(diào)用encodeAsBitmap(),就可以生成Bitmap對象男摧。
實現(xiàn)效果:輸入內(nèi)容 點擊生成二維碼即可生成二維碼
這里僅僅把zxing項目中自己需要的功能提取出來蔬墩,主要還是要有耐心,看不懂就多看幾遍對我來說還是有效果的啊耗拓,哈哈哈拇颅,當(dāng)然,自己也去找了很多的資料幫助自己理解乔询,感謝你們樟插。
慣例,最后送給自己一句話:凡事往簡單處想哥谷,往認真處行