首先我这里不会再介绍Camera的基本属性了解介绍了,也不会讲预览的基本设置。会直奔主题将怎么写二维码扫描的界面的自定义控件和扫描过程实现以及里面。至于Camera的基本介绍请看我之前的博客:Camera基本介绍,用Camera写一个自己的基本预览界面。这两篇博客是这个二维码扫描的基础篇。
(1)声明自己要解析那些类型的,必须解析二维码、解析条形码。
(2)开启一个线程去读取预览界面的指定区域的数据源(byte[])
(3)利用zXing的方法去先判断获取到的数据源里面的点是否是有效的,再在有效的基础上结合(1)声明的格式去解析这些有用的点,直到解析成功、失败、主动或被动停止为止。
(4)扫描过程从始至终都会有自定义控件和动画跟随着,所以这个(4)是贯穿整个过程并且受到(3)的控制。
(5)回调(3)执行结果来控制(4);
下面我对上面的四点进行分别解析:
(1)声明解析类型:
import android.content.Intent;
import android.net.Uri;
import com.google.zxing.BarcodeFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.regex.Pattern;
public class DecodeFormatManager {
private static final Pattern COMMA_PATTERN = Pattern.compile(",");
static final Vector<BarcodeFormat> PRODUCT_FORMATS;
static final Vector<BarcodeFormat> ONE_D_FORMATS;
static final Vector<BarcodeFormat> QR_CODE_FORMATS;
static final Vector<BarcodeFormat> DATA_MATRIX_FORMATS;
static {
PRODUCT_FORMATS = new Vector<>(5);
PRODUCT_FORMATS.add(BarcodeFormat.UPC_A);
PRODUCT_FORMATS.add(BarcodeFormat.UPC_E);
PRODUCT_FORMATS.add(BarcodeFormat.EAN_13);
PRODUCT_FORMATS.add(BarcodeFormat.EAN_8);
ONE_D_FORMATS = new Vector<>(PRODUCT_FORMATS.size() + 4);
ONE_D_FORMATS.addAll(PRODUCT_FORMATS);
ONE_D_FORMATS.add(BarcodeFormat.CODE_39);
ONE_D_FORMATS.add(BarcodeFormat.CODE_93);
ONE_D_FORMATS.add(BarcodeFormat.CODE_128);
ONE_D_FORMATS.add(BarcodeFormat.ITF);
QR_CODE_FORMATS = new Vector<>(1);
QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE);
DATA_MATRIX_FORMATS = new Vector<>(1);
DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX);
}
private DecodeFormatManager() {
}
private static final class Holder {
private static final DecodeFormatManager DECODE_FORMAT_MANAGER = new DecodeFormatManager();
}
public static DecodeFormatManager getInstance() {
return Holder.DECODE_FORMAT_MANAGER;
}
static Vector<BarcodeFormat> parseDecodeFormats(Intent intent) {
List<String> scanFormats = null;
String scanFormatsString = intent.getStringExtra(Intents.Scan.SCAN_FORMATS);
if (scanFormatsString != null) {
scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString));
}
return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE));
}
static Vector<BarcodeFormat> parseDecodeFormats(Uri inputUri) {
List<String> formats = inputUri.getQueryParameters(Intents.Scan.SCAN_FORMATS);
if (formats != null && formats.size() == 1 && formats.get(0) != null) {
formats = Arrays.asList(COMMA_PATTERN.split(formats.get(0)));
}
return parseDecodeFormats(formats, inputUri.getQueryParameter(Intents.Scan.MODE));
}
private static Vector<BarcodeFormat> parseDecodeFormats(Iterable<String> scanFormats,
String decodeMode) {
if (scanFormats != null) {
Vector<BarcodeFormat> formats = new Vector<BarcodeFormat>();
try {
for (String format : scanFormats) {
formats.add(BarcodeFormat.valueOf(format));
}
return formats;
} catch (IllegalArgumentException iae) {
// ignore it then
}
}
if (decodeMode != null) {
if (Intents.Scan.PRODUCT_MODE.equals(decodeMode)) {
return PRODUCT_FORMATS;
}
if (Intents.Scan.QR_CODE_MODE.equals(decodeMode)) {
return QR_CODE_FORMATS;
}
if (Intents.Scan.DATA_MATRIX_MODE.equals(decodeMode)) {
return DATA_MATRIX_FORMATS;
}
if (Intents.Scan.ONE_D_MODE.equals(decodeMode)) {
return ONE_D_FORMATS;
}
}
return null;
}
}
(2和3)开启线程去读取预览界面的指定区域的数据源,然后在线程里面使用handler来不断的读取和解析数据:
import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ResultPointCallback;
import java.util.Hashtable;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
public class DecodeThread extends Thread {
private Activity activity;
private final CountDownLatch handlerInitLatch;
private DecodeHandler handler;
private final Hashtable<DecodeHintType, Object> hints;
public static final String BARCODE_BITMAP = "barcode_bitmap";
public DecodeThread(Activity activity, Vector<BarcodeFormat> decodeFormats,
String characterSet, ResultPointCallback resultPointCallback) {
this.activity = activity;
handlerInitLatch = new CountDownLatch(1);
//Decode Hint Type 解码提示类型
hints = new Hashtable<>(3);//哈希表集合
if (decodeFormats == null || decodeFormats.isEmpty()) {
decodeFormats = new Vector<>();
decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);
decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);
decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
}
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
if (characterSet != null) {
hints.put(DecodeHintType.CHARACTER_SET, characterSet);
}
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
}
Handler getHandler() {
try {
handlerInitLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return handler;
}
@Override
public void run() {
super.run();
Looper.prepare();
handler = new DecodeHandler(activity, hints);
handlerInitLatch.countDown();
Looper.loop();
}
}
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import java.util.Hashtable;
import shen.da.ye.myscandemo.R;
import shen.da.ye.myscandemo.ScanActivity;
import shen.da.ye.myscandemo.camera.CameraManager;
import shen.da.ye.myscandemo.camera.PlanarYUVLuminanceSource;
public class DecodeHandler extends Handler {
private static final String TAG = "cy==DecodeHandler";
private final MultiFormatReader MULITFORMATREADER;
private Activity activity;
public DecodeHandler(Activity activity, Hashtable<DecodeHintType, Object> hints) {
MULITFORMATREADER = new MultiFormatReader();
MULITFORMATREADER.setHints(hints);
this.activity = activity;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case R.id.decode:
decode(((byte[]) msg.obj), msg.arg1, msg.arg2);
break;
case R.id.quit:
Looper.myLooper().quit();
default:
break;
}
}
/**
* 解码取景器矩形中的数据,以及花费多长时间。 为了效率,将相同的读取对象从一个解码重用到下一个解码。
*
* @param data The YUV preview frame.
* @param width The width of the preview frame.
* @param height The height of the preview frame.
*/
private void decode(byte[] data, int width, int height) {
//解析从预览界面的矩形中的数据
long start = System.currentTimeMillis();
Result rawResult = null;
byte[] rotateData = new byte[data.length];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
rotateData[x * height + height - y - 1] = data[x + y * width];
}
}
int tmp = width;
width = height;
height = tmp;
//二维码扫描核心处理代码是在这里调用zXing
PlanarYUVLuminanceSource planarYUVLuminanceSource = CameraManager.getInstance().buildLuminanceSource(rotateData, width, height);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(planarYUVLuminanceSource));
try {
rawResult = MULITFORMATREADER.decodeWithState(bitmap);
} catch (NotFoundException e) {
e.printStackTrace();
} finally {
MULITFORMATREADER.reset();
}
if (rawResult != null) {
long end = System.currentTimeMillis();
Message message = Message.obtain(((ScanActivity) activity).getHandler(), R.id.decode_succeeded, rawResult);
Bundle bundle = new Bundle();
bundle.putParcelable(DecodeThread.BARCODE_BITMAP, planarYUVLuminanceSource.renderCroppedGreyScaleBitmap());
message.setData(bundle);
message.sendToTarget();
} else {
Message message = Message.obtain(((ScanActivity) activity).getHandler(), R.id.decode_failed);
message.sendToTarget();
}
}
}
(5)再用一个hander来解决线程里面读取数据的信息回调:
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import java.util.Vector;
import shen.da.ye.myscandemo.R;
import shen.da.ye.myscandemo.ScanActivity;
import shen.da.ye.myscandemo.camera.CameraManager;
public class CaptureActivityHandler extends Handler {
private static final String TAG = "cy====CaptureHandler";
private Activity activity;
private State mState;
private final DecodeThread decodeThread;
private enum State {
PREVIEW,
SUCCESS,
DONE
}
/*Vector:向量,Barcode Format:条码格式*/
public CaptureActivityHandler(Activity activity, Vector<BarcodeFormat> barcodeFormats,
String characterSet) {
this.activity = activity;
decodeThread = new DecodeThread(activity, barcodeFormats, characterSet,
new ViewfinderResultPointCallback(((ScanActivity) activity).getViewfinderView()));
decodeThread.start();
mState = State.SUCCESS;
//(1)开始预览
CameraManager.getInstance().startPreview();
//(2)开始解码
restartDecode();
}
private void restartDecode() {
if (mState == State.SUCCESS) {
mState = State.PREVIEW;
CameraManager.getInstance().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
CameraManager.getInstance().requestAutoFocus(this, R.id.auto_focus);
((ScanActivity) activity).drawViewfinder();
}
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case R.id.auto_focus:
//接收到自动定焦msg
if (mState == State.PREVIEW) {
CameraManager.getInstance().requestAutoFocus(this, R.id.auto_focus);
Log.i(TAG, "自动定焦");
}
break;
case R.id.restart_preview:
//接收到重新启动预览的msg,是在扫描走了onPause之后再走onResume的时候调用这个方法
restartDecode();//TODO 看一下这个需不需要再重新开始预览,加一个跳转界面测试一下
break;
case R.id.decode_succeeded:
//接收到解码成功的msg
mState = State.SUCCESS;
Bundle bundle = msg.getData();
Bitmap barcode = bundle == null ? null : (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);
((ScanActivity) activity).handleDecode((Result) msg.obj, barcode);
Log.i(TAG, "解析扫描到捕捉的点成功");
break;
case R.id.decode_failed:
//接收到扫描失败的msg,这个扫描失败是捕捉到的东西不是一个正常的条形码和二维码,那么继续捕捉。还有一个扫描时失败是捕捉到的点是二维码,但是从里面解析到的数据失败
mState = State.PREVIEW;
CameraManager.getInstance().requestPreviewFrame(decodeThread.getHandler(), R.id.decode);
Log.i(TAG, "捕捉的点经过解析是无效的,重新再捕捉");
break;
case R.id.return_scan_result:
//接收到
activity.setResult(Activity.RESULT_OK, ((Intent) msg.obj));
activity.finish();
break;
case R.id.launch_product_query:
//接收到
String url = (String) msg.obj;
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
activity.startActivity(intent);
break;
default:
break;
}
}
//onPause的时候停止扫描
public void quitSynchronously() {
mState = State.DONE;
CameraManager.getInstance().stopPreview();
Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit);
quit.sendToTarget();
try {
decodeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//移除到正在发送的信息,保证现在没有任何排队的信息
removeMessages(R.id.decode_succeeded);
removeMessages(R.id.decode_failed);
}
}
源代码