基本使用
添加依赖
compile 'com.journeyapps:zxing-android-embedded:3.5.0'
扫描二维码:
new IntentIntegrator(this).initiateScan();
仅仅添加这一行就可以开启扫描二维码了,非常简单。
看看内部做了什么操作把,initiateScan()这个方法就一行代码,咱瞅瞅
startActivityForResult(createScanIntent(), REQUEST_CODE);
这个就很熟悉了啊,传入Intent和requestcode,开启一个可以但会结果的界面。在瞅瞅createScanIntent这个方法。
public Intent createScanIntent() {
Intent intentScan = new Intent(activity, getCaptureActivity());
intentScan.setAction(Intents.Scan.ACTION);
// check which types of codes to scan for
//检查要扫码的二维码类型
if (desiredBarcodeFormats != null) {
// set the desired barcode types
StringBuilder joinedByComma = new StringBuilder();
for (String format : desiredBarcodeFormats) {
if (joinedByComma.length() > 0) {
joinedByComma.append(',');
}
joinedByComma.append(format);
}
intentScan.putExtra(Intents.Scan.FORMATS, joinedByComma.toString());
}
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
attachMoreExtras(intentScan);
return intentScan;
}
这个Intent需要传入一个activity,getCaptureActivity(),如果你设置了activity就是用你传递进来的activity,如果你没有设置就使用默认的activity,这个activity是CaptureActivity.class,那么在哪里设置自己的Activity呢。
new IntentIntegrator(this).setCaptureActivity(ToolbarCaptureActivity.class).initiateScan();
在开启扫描的时候设置咱自己的Activity,就可以修改咱们自己想要的界面了。
接着上面的intent说,desiredBarcodeFormats直接检查支持二维码支的格式有那些。
public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
public static final Collection<String> ONE_D_CODE_TYPES =
list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
"ITF", "RSS_14", "RSS_EXPANDED");
public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");
把设置的参数存入的Intent中。见方法
**private void attachMoreExtras(Intent intent) **
这时候就已经开启了二维码扫描界面了。
看看可以设置那些参数
IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setCaptureActivity(AnyOrientationCaptureActivity.class);
integrator.setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES);
integrator.setPrompt("Scan something");
integrator.setOrientationLocked(false);
integrator.setBeepEnabled(false);
integrator.setBarcodeImageEnabled(true)
integrator.initiateScan();
- setCaptureActivity
设置自定义的Activity - setDesiredBarcodeFormats
设置要扫描的所需条形码格式。 - setPrompt
设置扫面界面的提示语 - setOrientationLocked
- setBeepEnabled
设置扫描完成声音提示 false 表示没有提示音 - setBarcodeImageEnabled
表示二维码图片是否保存,true表示保存。 - setCameraId
设置前后摄像头滴 - setOrientationLocked()
是否旋转锁定
如果你的androidManifest文件扫描界面的屏幕旋转方向改成
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="fullSensor"
tools:replace="screenOrientation" />
然后
integrator.setOrientationLocked(false);
结果就是:调整手机方向时,扫描布局也会重新布置
获取返回的结果
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if(result != null) {
if(result.getContents() == null) {
Log.d("MainActivity", "Cancelled scan");
Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show();
} else {
Log.d("MainActivity", "Scanned");
Toast.makeText(this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
}
} else {
// This is important, otherwise the result will not be passed to the fragment
super.onActivityResult(requestCode, resultCode, data);
}
来看看这个是怎么解析的:
IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
上代码:
public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
String contents = intent.getStringExtra(Intents.Scan.RESULT);
String formatName = intent.getStringExtra(Intents.Scan.RESULT_FORMAT);
byte[] rawBytes = intent.getByteArrayExtra(Intents.Scan.RESULT_BYTES);
int intentOrientation = intent.getIntExtra(Intents.Scan.RESULT_ORIENTATION, Integer.MIN_VALUE);
Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
String errorCorrectionLevel = intent.getStringExtra(Intents.Scan.RESULT_ERROR_CORRECTION_LEVEL);
String barcodeImagePath = intent.getStringExtra(Intents.Scan.RESULT_BARCODE_IMAGE_PATH);
return new IntentResult(contents,
formatName,
rawBytes,
orientation,
errorCorrectionLevel,
barcodeImagePath);
}
return new IntentResult();
}
return null;
}
其实就是获取Intent中的信息,二维码的信息结果就在Intent中,当然IntentResult中有许多信息。
public final class IntentResult {
private final String contents;
private final String formatName;
private final byte[] rawBytes;
private final Integer orientation;
private final String errorCorrectionLevel;
private final String barcodeImagePath;
......
}
- contents
二维码扫描的信息 - formatName
扫描类型 - rawBytes
条形码内容的原始字节 - orientation
图片的旋转度数 - errorCorrectionLevel
纠错级别 - barcodeImagePath
二维码图片路径(我的是在缓存中)
/data/user/0/cn.projects.com.projectsdemo/cache/barcodeimage371491556.jpg
CaptureActivity
这个类加载自定义布局 :
xml文件
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!--
This Activity is typically full-screen. Therefore we can safely use centerCrop scaling with
a SurfaceView, without fear of weird artifacts. -->
<com.journeyapps.barcodescanner.DecoratedBarcodeView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/zxing_barcode_scanner"
app:zxing_preview_scaling_strategy="centerCrop"
app:zxing_use_texture_view="false"/>
</merge>
xml文件使用merge标签来减少布局的层次结果,不明白的自行度娘。
这里最重要的就是这个DecoratedBarcodeView自定义控件了,封装BarcodeView,ViewfinderView和状态文本。创建CaptureManger 管理CaptureActivity条形码扫描,从Intent初始化(通过IntentIntegrator)并开始解码capture.decode();(并不是真正意义上的解码)真正解码的事BarcodeView。
- BarcodeView
继承CameraPreview继承ViewGroup,用于扫描条形码的视图 - ViewfinderView
此view覆盖在相机预览的顶部。 它添加取景器矩形和部分透明度以外,以及激光扫描仪的动画和结果点(一条横线的那个玩意) - 状态文本
扫描时的提示信息
如果想要自定义界面,有两种方法:
- 创建一个类,复制CaptureActivity类的内容,修改initializeContent方法中的布局。
- 继承CaptureActivity类重写initializeContent方法。
例如:
public class SmallCaptureActivity extends CaptureActivity {
@Override
protected DecoratedBarcodeView initializeContent() {
setContentView(R.layout.capture_small);
return (DecoratedBarcodeView)findViewById(R.id.zxing_barcode_scanner);
}
}
xml文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.journeyapps.barcodescanner.DecoratedBarcodeView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/zxing_barcode_scanner"
app:zxing_preview_scaling_strategy="centerCrop"
app:zxing_scanner_layout = "@layout/custom_layout_zxing"
app:zxing_framing_rect_height = "200dp"
app:zxing_framing_rect_width = "200dp"
app:zxing_use_texture_view="false"/>
</LinearLayout>
DecoratedBarcodeView
自定义的Fragment,封装BarcodeView,ViewfinderView和状态文本。要自定义UI,请直接使用BarcodeView和ViewfinderView。
- initialize()
从xml中加载自定义属性,定义BarcodeView,ViewfinderView和statusView。
private void initialize(AttributeSet attrs) {
// Get attributes set on view
TypedArray attributes = getContext().obtainStyledAttributes(attrs, R.styleable.zxing_view);
int scannerLayout = attributes.getResourceId(
R.styleable.zxing_view_zxing_scanner_layout, R.layout.zxing_barcode_scanner);
attributes.recycle();
inflate(getContext(), scannerLayout, this);
barcodeView = (BarcodeView) findViewById(R.id.zxing_barcode_surface);
if (barcodeView == null) {
throw new IllegalArgumentException(
"There is no a com.journeyapps.barcodescanner.BarcodeView on provided layout " +
"with the id \"zxing_barcode_surface\".");
}
// Pass on any preview-related attributes
barcodeView.initializeAttributes(attrs);
viewFinder = (ViewfinderView) findViewById(R.id.zxing_viewfinder_view);
if (viewFinder == null) {
throw new IllegalArgumentException(
"There is no a com.journeyapps.barcodescanner.ViewfinderView on provided layout " +
"with the id \"zxing_viewfinder_view\".");
}
viewFinder.setCameraPreview(barcodeView);
// statusView is optional
statusView = (TextView) findViewById(R.id.zxing_status_view);
}
如果想要自定义BarcodeView,ViewfinderView。在自定义的
xml文件中
com.journeyapps.barcodescanner.DecoratedBarcodeView
加入一行
app:zxing_scanner_layout = "@layout/custom_layout_zxing"
来添加你自己的layout布局。
例如:
我自己定义的布局custom_layout_zxing
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.journeyapps.barcodescanner.BarcodeView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/zxing_barcode_surface"/>
<cn.projects.com.projectsdemo.zxing.CustomViewFindView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/zxing_viewfinder_view"/>
<TextView android:id="@+id/zxing_status_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="120dp"
android:background="@color/zxing_transparent"
android:text="@string/zxing_msg_default_status"
android:textColor="@color/zxing_status_text"/>
</FrameLayout>
这里我自己定义了ViewFindView,简单的绘制扫描先了四个角。
initializeFromIntent(Intent intent)
从Intent去除消息,初始化摄像机ID,解码格式和是否反转。onKeyDown
重写onKeyDown主要两个功能,1.按音量+开启闪光灯。2.按音量-关闭闪光灯。decodeSingle(BarcodeCallback callback)
解码单个的(就是一次)条码,然后停止解码。回调在住线程decodeContinuous(BarcodeCallback callback)
连续解码条形码。 相同的条形码可以每秒返回多次。回调在住线程pause
停止预览和解码pauseAndWait
暂停扫描和预览; 等待相机关闭。会阻塞住线程WrappedCallback
private class WrappedCallback implements BarcodeCallback {
private BarcodeCallback delegate;
public WrappedCallback(BarcodeCallback delegate) {
this.delegate = delegate;
}
@Override
public void barcodeResult(BarcodeResult result) {
delegate.barcodeResult(result);
}
@Override
public void possibleResultPoints(List<ResultPoint> resultPoints) {
for (ResultPoint point : resultPoints) {
viewFinder.addPossibleResultPoint(point);
}
delegate.possibleResultPoints(resultPoints);
}
WrappedCallback实现了BarcodeCallback,对解码的结果进行包装,实际上把结果传递到了BarcodeView的resultCallback中。这里只是对
possibleResultPoints进行处理,这个东西到底是啥呢,官方解释:
检测到结果点。 无论扫描是否成功,都可以进行调用。 这对于在扫描时向用户给出一些反馈主要是有用的。 不要依赖于在解码周期的任何特定点被调用。 @param resultPoints可能会识别条形码
这是啥意思呢,看看谁用到他了吧!
for (ResultPoint point : resultPoints) {
viewFinder.addPossibleResultPoint(point);
}
除了viewFinder没人使用它,看看viewFinder是怎么用的吧
if (!lastPossibleResultPoints.isEmpty()) {
paint.setAlpha(CURRENT_POINT_OPACITY / 2);
paint.setColor(resultPointColor);
float radius = POINT_SIZE / 2.0f;
for (final ResultPoint point : lastPossibleResultPoints) {
canvas.drawCircle(
frameLeft + (int) (point.getX() * scaleX),
frameTop + (int) (point.getY() * scaleY),
radius, paint
);
}
lastPossibleResultPoints.clear();
哦哦哦,原来激光线旁边的小点点就是可能的结果点啊!原来乳此!
CaptureManager
管理CaptureActivity条形码扫描。 这是针对专门用于捕获单个条形码并返回结果通过setResult()。以下由类程管理:
- 方向锁
- 静止计时器
- BeepManager
- 从Intent初始化(通过IntentIntegrator)
- 设置结果并完成扫描条形码的Activity
- 显示相机错误
initializeFromIntent(Intent intent, Bundle savedInstanceState)
根据Intent初始化参数
这里只主要是调用DecoratedBarcodeView的decodeSingle开始解码,
/**
* Start decoding.
*/
public void decode() {
barcodeView.decodeSingle(callback);
}
这个callback就是解码的结果的回调。
private BarcodeCallback callback = new BarcodeCallback() {
@Override
public void barcodeResult(final BarcodeResult result) {
barcodeView.pause();
beepManager.playBeepSoundAndVibrate();
handler.post(new Runnable() {
@Override
public void run() {
returnResult(result);
}
});
}
@Override
public void possibleResultPoints(List<ResultPoint> resultPoints) {
}
};
暂停预览并播放声音。
handler发送结果到 returnResult(result);
protected void returnResult(BarcodeResult rawResult) {
Intent intent = resultIntent(rawResult, getBarcodeImagePath(rawResult));
activity.setResult(Activity.RESULT_OK, intent);
closeAndFinish();
}
把结果返回到Activity并关闭,这个Activity,就是CaptureActivity或者自定义的Activity,就可以拿到结果了。
- InactivityTimer是对电池的状态进行监听,
final boolean onBatteryNow = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) <= 0;
这里应该是使用电池本身的电量(没有USB,或者充电)5分钟就关闭了Activity。
BarcodeView
BarcodeView是用于扫描条形码的视图。
使用decodeSingle()或decodeContinuous()开始解码。
停止使用stopDecoding()解码。
调用两个方法来管理状态:
- resume() 初始化相机并开始预览。 从Activity的onResume()调用。
- pause() 停止预览并释放任何资源。 从Activity的onPause()调用。
对外开放的开始解码代码
public void decodeSingle(BarcodeCallback callback) {
this.decodeMode = DecodeMode.SINGLE;
this.callback = callback;
startDecoderThread();
}
开启连续解码,相同的条形码可以每秒返回多次
public void decodeContinuous(BarcodeCallback callback) {
this.decodeMode = DecodeMode.CONTINUOUS;
this.callback = callback;
startDecoderThread();
}
开启解码
private void startDecoderThread() {
stopDecoderThread(); // To be safe
if (decodeMode != DecodeMode.NONE && isPreviewActive()) {
//启动线程条件:
// 1.请求解码
// 2.预览处于活动状态
decoderThread = new DecoderThread(getCameraInstance(), createDecoder(), resultHandler);
decoderThread.setCropRect(getPreviewFramingRect());
decoderThread.start();
}
}
保证线程安全,设置矩形区域,然后开启解码。
这里的handler(resultHandler)就是返回解码结果的回调。这个回调是在DecoderThread类发出的.
private final Handler.Callback resultCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
if (message.what == R.id.zxing_decode_succeeded) {
BarcodeResult result = (BarcodeResult) message.obj;
if (result != null) {
if (callback != null && decodeMode != DecodeMode.NONE) {
callback.barcodeResult(result);
if (decodeMode == DecodeMode.SINGLE) {
stopDecoding();
}
}
}
return true;
} else if (message.what == R.id.zxing_decode_failed) {
// Failed. Next preview is automatically tried.
//失败 下一个预览会自动尝试。
return true;
} else if (message.what == R.id.zxing_possible_result_points) {
//noinspection unchecked
List<ResultPoint> resultPoints = (List<ResultPoint>) message.obj;
if (callback != null && decodeMode != DecodeMode.NONE) {
callback.possibleResultPoints(resultPoints);
}
return true;
}
return false;
}
};
callback.barcodeResult(result);这一行代码中,Callback是CaptureManager类中callback。结果是一层一层向上传递的。
跟踪这个resultHandler。进入DecoderThread类。
DecoderThread
构造
public DecoderThread(CameraInstance cameraInstance, Decoder decoder, Handler resultHandler) {
Util.validateMainThread();
this.cameraInstance = cameraInstance;
this.decoder = decoder;
this.resultHandler = resultHandler;
}
直接解码呗
/**
* Start decoding.
*
* This must be called from the UI thread.
*/
public void start() {
Util.validateMainThread();
thread = new HandlerThread(TAG);
thread.start();
handler = new Handler(thread.getLooper(), callback);
running = true;
requestNextPreview();
}
这个callback返回的是图像信息,然后根据图像信息进行解码
private final Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
if (message.what == R.id.zxing_decode) {
decode((SourceData) message.obj);
} else if(message.what == R.id.zxing_preview_failed) {
// Error already logged. Try again.
requestNextPreview();
}
return true;
}
};
最关键的是
requestNextPreview();
这个方法.
private void requestNextPreview() {
if (cameraInstance.isOpen()) {
cameraInstance.requestPreview(previewCallback);
}
}
这里有个CameraInstance类,这个类使用后台线程管理Camera实例,必须调用在主线程。
public void requestPreview(final PreviewCallback callback) {
validateOpen();
cameraThread.enqueue(new Runnable() {
@Override
public void run() {
cameraManager.requestPreviewFrame(callback);
}
});
}
这里cameraThread.enqueue()其实就是handler.post发送调用在主线程。
public void requestPreviewFrame(PreviewCallback callback) {
Camera theCamera = camera;
if (theCamera != null && previewing) {
cameraPreviewCallback.setCallback(callback);
theCamera.setOneShotPreviewCallback(cameraPreviewCallback);
}
}
单次预览框将图像返回到回调。
private final class CameraPreviewCallback implements Camera.PreviewCallback {
private PreviewCallback callback;
private Size resolution;
public CameraPreviewCallback() {
}
public void setResolution(Size resolution) {
this.resolution = resolution;
}
public void setCallback(PreviewCallback callback) {
this.callback = callback;
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Size cameraResolution = resolution;
PreviewCallback callback = this.callback;
if (cameraResolution != null && callback != null) {
try {
if(data == null) {
throw new NullPointerException("No preview data received");
}
int format = camera.getParameters().getPreviewFormat();
SourceData source = new SourceData(data, cameraResolution.width, cameraResolution.height, format, getCameraRotation());
callback.onPreview(source);
} catch (RuntimeException e) {
// Could be:
// java.lang.RuntimeException: getParameters failed (empty parameters)
// IllegalArgumentException: Image data does not match the resolution
Log.e(TAG, "Camera preview failed", e);
callback.onPreviewError(e);
}
} else {
Log.d(TAG, "Got preview callback, but no handler or resolution available");
if(callback != null) {
// Should generally not happen
callback.onPreviewError(new Exception("No resolution available"));
}
}
}
}
callback.onPreview(source);
图像给回调了。
回调是在DecoderThread中实现的
private final PreviewCallback previewCallback = new PreviewCallback() {
@Override
public void onPreview(SourceData sourceData) {
// Only post if running, to prevent a warning like this:
// java.lang.RuntimeException: Handler (android.os.Handler) sending message to a Handler on a dead thread
// synchronize to handle cases where this is called concurrently with stop()
synchronized (LOCK) {
if (running) {
// Post to our thread.
handler.obtainMessage(R.id.zxing_decode, sourceData).sendToTarget();
}
}
}
@Override
public void onPreviewError(Exception e) {
synchronized (LOCK) {
if (running) {
// Post to our thread.
handler.obtainMessage(R.id.zxing_preview_failed).sendToTarget();
}
}
}
};
数据发送给了handler,在handler的Callback中
private final Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
if (message.what == R.id.zxing_decode) {
decode((SourceData) message.obj);
} else if(message.what == R.id.zxing_preview_failed) {
// Error already logged. Try again.
requestNextPreview();
}
return true;
}
};
看看decoder()方法。
private void decode(SourceData sourceData) {
long start = System.currentTimeMillis();
Result rawResult = null;
sourceData.setCropRect(cropRect);
LuminanceSource source = createSource(sourceData);
if(source != null) {
rawResult = decoder.decode(source);
}
if (rawResult != null) {
// Don't log the barcode contents for security.
long end = System.currentTimeMillis();
Log.d(TAG, "Found barcode in " + (end - start) + " ms");
if (resultHandler != null) {
BarcodeResult barcodeResult = new BarcodeResult(rawResult, sourceData);
Message message = Message.obtain(resultHandler, R.id.zxing_decode_succeeded, barcodeResult);
Bundle bundle = new Bundle();
message.setData(bundle);
message.sendToTarget();
}
} else {
if (resultHandler != null) {
Message message = Message.obtain(resultHandler, R.id.zxing_decode_failed);
message.sendToTarget();
}
}
if (resultHandler != null) {
List<ResultPoint> resultPoints = decoder.getPossibleResultPoints();
Message message = Message.obtain(resultHandler, R.id.zxing_possible_result_points, resultPoints);
message.sendToTarget();
}
requestNextPreview();
}
在decoder.decode(source)这里解码图像,并且把结果都分发出来,
现在比较清晰了,resultHandler就是结果的回调,网上跟踪就可以了。
最后又开启了下一次预览。解码二进制图像是在Decode类中,可以自己去看看
ViewfinderView
此视图覆盖在相机预览的顶部。 它添加取景器矩形和部分透明度以外,以及激光扫描仪的动画和结果点。
没啥说的啊。