您的位置:首页 > 移动开发 > Android开发

第三方ZXing库zxing-android-embedded使用及自定义

2017-09-08 09:55 906 查看

一、关于ZXing

现在一维码二维码在我们的日常生活中使用如此的广泛,所以拥有扫码功能的APP变得非常普遍,一个安卓APP需要扫码功能就要用到zxing了,zxing是谷歌开源的让开发者更方便使用摄像头的库,而我们常用的扫码功能就是其中之一。

github地址:https://github.com/zxing/zxing

但是因为zxing的功能太强大了,包含了很多我们用不上的功能,所以一般都会抽取其中的扫码功能单独使用,这个抽取的过程还是有点麻烦的,但是已经有很多开发者为我们省去了这个过程,现在就来介绍一个很棒的第三方zxing库:zxing-android-embedded

二、第三方zxing库zxing-android-embedded

github地址:https://github.com/journeyapps/zxing-android-embedded

使用方式也很简单,首先在gradle中添加依赖

compile 'com.journeyapps:zxing-android-embedded:3.5.0'


然后在Activity或fragment中调用即可:

new IntentIntegrator(this)
.setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES)// 扫码的类型,可选:一维码,二维码,一/二维码
.setPrompt("请对准二维码")// 设置提示语
.setCameraId(0)// 选择摄像头,可使用前置或者后置
.setBeepEnabled(false)// 是否开启声音,扫完码之后会"哔"的一声
.setBarcodeImageEnabled(true)// 扫完码之后生成二维码的图片
.initiateScan();// 初始化扫码


扫完码之后会在onActivityResult方法中回调结果

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if(result != null) {
if(result.getContents() == null) {
Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}


以上就是简单的使用方式了

三、自定义界面

我们开发的时候可能会想要自定义界面,那么就需要我们修改代码了,自定义界面可以实现的:

1. 默认的的扫码是横屏的,自定义后可以竖屏扫码

2. 修改扫码框布局,可以添加其他View

其实在设置IntentIntegrator时会有一个方法:setCaptureActivity(Activity),这个方法就是用来设置扫码界面的Activity,当不设置的时候会默认调用库作者自己写的CaptureActivity,我们可以一起看一下这个Activity里面做了什么:

public class CaptureActivity extends Activity {
private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

barcodeScannerView = initializeContent();

capture = new CaptureManager(this, barcodeScannerView);
capture.initializeFromIntent(getIntent(), savedInstanceState);
capture.decode();
}

...省略代码
}


这里有2个很重要的成员变量:CaptureManager和DecoratedBarcodeView,从他们的名字可以看出:

1. CaptureManager是用来拉起扫码和处理扫码结果的类

2. DecoratedBarcodeView则是一个显示扫码界面的自定义View

有了这个了解之后我们要自定义扫码界面就很明了了,再一起简单看一下DecoratedBarcodeView是个啥:

public class DecoratedBarcodeView extends FrameLayout {
private BarcodeView barcodeView;
private ViewfinderView viewFinder;
private TextView statusView;

...省略代码
}


BarcodeView就是背景

ViewfinderView就是扫描框

TextView为下方提示文字

上个图:



了解了这三个View的作用之后我们就可以开始我们的自定义了,而这三个View具体怎么去扫码与解析并不是我们关心的重点我们直接跳过。自定义界面的步骤:

新建一个Activity

把CaptureManager和DecoratedBarcodeView复制到我们自定义的Activity中

设置setCaptureActivity(CustomCaptureActivity.class)为我们自己的Activity

别忘了把自定义的Activity加入到AndroidManifest.xml中注册

上代码,我自定义的activity:

public class CustomCaptureActivity extends Activity {
private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_custom_capture);// 自定义布局

barcodeScannerView = (DecoratedBarcodeView) findViewById(R.id.dbv_custom);

capture = new CaptureManager(this, barcodeScannerView);
capture.initializeFromIntent(getIntent(), savedInstanceState);
capture.decode();
}

@Override
protected void onResume() {
super.onResume();
capture.onResume();
}

@Override
protected void onPause() {
super.onPause();
capture.onPause();
}

@Override
protected void onDestroy() {
super.onDestroy();
capture.onDestroy();
}

@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
capture.onSaveInstanceState(outState);
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
capture.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
}
}


自定义的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">

<com.journeyapps.barcodescanner.DecoratedBarcodeView
android:id="@+id/dbv_custom"
android:layout_width="match_parent"
android:layout_height="235dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="50dp"
app:zxing_preview_scaling_strategy="fitXY" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="我是新添加的按钮啊" />

</LinearLayout>


调用方式:

new IntentIntegrator(this)
// 自定义Activity,重点是这行----------------------------
.setCaptureActivity(CustomCaptureActivity.class)
.setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES)// 扫码的类型,可选:一维码,二维码,一/二维码
.setPrompt("请对准二维码")// 设置提示语
.setCameraId(0)// 选择摄像头,可使用前置或者后置
.setBeepEnabled(false)// 是否开启声音,扫完码之后会"哔"的一声
.setBarcodeImageEnabled(true)// 扫完码之后生成二维码的图片
.initiateScan();// 初始化扫码


这是我修改过后的界面:



大家可以根据自己的需要定制自己想要的界面。

四、扫码后不结束扫码界面Activity

我们在使用zxing-android-embedded时会发现一点,那就是每次扫完码之后都会结束掉扫码界面Activity,然后在上一个Activity的onActivityResult获取扫码的结果,一般情况下这样的操作是可以满足我们项目中的需求,但是作者最近在开发的时候遇到了一个需求,需要在扫完码之后在不退出Activity的情况下弹出一个Dialog,然后在Dialog消失之后再扫码。找了一圈发现zxing-android-embedded没有提供这样的方法可以满足扫码后不结束扫码Activity。没办法逼得我去查阅源码看能否从源码下手,这里总结一下需求:

1. 首先扫码结束后不能结束扫码Activity

2. 在扫码Activity中能获取到扫码的结果

3. 获取到结果之后弹出Dialog,此时扫码应该是处于暂停状态,否则会出现未知问题

4. Dialog消失之后应该重新激活扫码

有了以上的需求,我们就从调用扫码的地方开始看起:

new IntentIntegrator(this)
.setCaptureActivity(CustomCaptureActivity.class)// 自定义Activity
.setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES)// 扫码的类型,可选:一维码,二维码,一/二维码
.setPrompt("请对准二维码")// 设置提示语
.setCameraId(0)// 选择摄像头,可使用前置或者后置
.setBeepEnabled(false)// 是否开启声音,扫完码之后会"哔"的一声
.setBarcodeImageEnabled(true)// 扫完码之后生成二维码的图片
.initiateScan();// 初始化扫码


构造方法中把当前的Activity设置到IntentIntegrator中,作者demo中对应的是MainActivity

public IntentIntegrator(Activity activity) {
this.activity = activity;
}


然后关键的第二行,设置扫码Activity

public IntentIntegrator setCaptureActivity(Class<?> captureActivity) {
this.captureActivity = captureActivity;
return this;
}


从第三行开始就是对一些属性的设置了,这些不是我们关心的重点,所以我们跳过。说了这么久还没有提到过IntentIntegrator这个类,这个类其实是我们调用扫码的一个入口,各种参数都是在这个类中设置,还记得我们的需求是扫完码之后不结束扫码的Activity码?我们把重心放到captureActivity上,但是结果却是让人失落的,这个类中并没有captureActivity相关的操作,不过在initiateScan()方法中我们可以找到一些蛛丝马迹。

public final void initiateScan() {
startActivityForResult(createScanIntent(), REQUEST_CODE);
}


这里只有简单的一行代码,让我们先看一下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,这个Intent就是从activity跳转到我们自定义的captureActivity,并且携带了很多设置的参数,让我返回到startActivityForResult()中:

protected void startActivityForResult(Intent intent, int code) {
if (fragment != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
fragment.startActivityForResult(intent, code);
}
} else if (supportFragment != null) {
supportFragment.startActivityForResult(intent, code);
} else {
activity.startActivityForResult(intent, code);
}
}


因为我们的扫码有可能是从fragment中调用的,所以框架的作者在这里分别做了对fragment和activity中调用扫码的处理,这里我们关心activity即可,很可惜,IntentIntegrator中没有我们能下手的地方,但是我们了解到扫码的操作其实都是在captureActivity做的,让我们把重点放到captureActivity中。

其实在第三节中我们我们已经看过这个Activity了,但是我们当时只关心自定义界面,所以忽略了一个重要的类:CaptureManager,这个类其实就是真正处理扫码的地方,所有的设置、界面、Activity的调用都会在CaptureManager完成,我们先看一下他的使用:

capture = new CaptureManager(this, barcodeScannerView);
capture.initializeFromIntent(getIntent(), savedInstanceState);
capture.decode();


构造方法中会把当前扫码Activity和扫码的View设置进去。

public CaptureManager(Activity activity, DecoratedBarcodeView barcodeView) {
this.activity = activity;
this.barcodeView = barcodeView;
barcodeView.getBarcodeView().addStateListener(stateListener);
handler = new Handler();
inactivityTimer = new InactivityTimer(activity, new Runnable() {
@Override
public void run() {
Log.d(TAG, "Finishing due to inactivity");
finish();
}
});
beepManager = new BeepManager(activity);
}


而initializeFromIntent()方法主要是从Intent中取出我们所设置的各种扫码的参数,这里就不给出源码了。

/**
* Start decoding.
*/
public void decode() {
barcodeView.decodeSingle(callback);
}


最后的decode()方法就是真正开始扫码的地方,我们可以看到这里barcodeView调用了decodeSingle方法开启扫码,还设置了一个回调进去,可以猜到这个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) {
}
};


在这个回调中看到一个returnResult(result),这个就是扫码之后返回结果的地方了,我们果然没有猜错

protected void returnResult(BarcodeResult rawResult) {
Intent intent = resultIntent(rawResult, getBarcodeImagePath(rawResult));
activity.setResult(Activity.RESULT_OK, intent);
closeAndFinish();
}


BarcodeResult就是我们的扫码结果了,第一行代码用来解析BarcodeResult并生成一个Intent,这个Intent就是用来携带扫码结果返回到onActivityResult的关键了,第三行代码的名字很明显就是用来结束Activity的方法了吧,跟进去看:

protected void closeAndFinish() {
if(barcodeView.getBarcodeView().isCameraClosed()) {
finish();
} else {
finishWhenClosed = true;
}
barcodeView.pause();
inactivityTimer.cancel();
}


private void finish() {
activity.finish();
}


罪魁祸首就是这个finish()方法了!!就是它,它把我们的Activity结束掉了,找到源头了就可以下手了。我们把它给注释掉就结束不了我们的Activity了。但是现在Activity不结束了,但是我们要把结果给返回回去啊,那我们自己得加个回调了

public interface ResultCallBack {
void callBack(int requestCode, int resultCode, Intent intent)
}

private ResultCallBack mResultCallBack;

public void setResultCallBack(ResultCallBack resultCallBack) {
this.mResultCallBack = resultCallBack;
}


修改后的returnResult方法为下,这里我修改的是returnResult方法,而并没有修改上面的closeAndFinish方法,因为closeAndFinish方法不只是在返回扫码结构的时候调用了,所以为了防止出问题就修改是returnResult方法

/**
* 修改此方法,实现扫码之后不返回界面
*/
protected void returnResult(BarcodeResult rawResult) {
Intent intent = resultIntent(rawResult, getBarcodeImagePath(rawResult));
activity.setResult(Activity.RESULT_OK, intent);
if (barcodeView.getBarcodeView().isCameraClosed()) {
if (null != mResultCallBack) {
mResultCallBack.callBack(IntentIntegrator.REQUEST_CODE, Activity.RESULT_OK, intent);
}
// activity.finish(); 注释掉这行
} else {
finishWhenClosed = true;
}
barcodeView.pause();
inactivityTimer.cancel();
}


到这里为止我们的修改就完了,其实改动非常小,就几行代码就可以实现扫完码之后不结束扫码Activity,但是重要的是思考的过程和阅读源码的过程,刚拿到那些需求然后发现第三方库实现不了的时候我也是非常慌的,也尝试过修改源码但是没有成功,最后一次静下心来慢慢翻看源码终于搞定了,这也是对我自己的一次提升吧,谢谢大家阅读文章,最后附上修改后的代码:

修改后的CustomCaptureActivity:

public class CustomCaptureActivity extends Activity {
private CaptureManager capture;
private DecoratedBarcodeView barcodeScannerView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_custom_capture);// 自定义布局

barcodeScannerView = (DecoratedBarcodeView) findViewById(R.id.dbv_custom);

capture = new CaptureManager(this, barcodeScannerView);
capture.initializeFromIntent(getIntent(), savedInstanceState);
capture.setResultCallBack(new CaptureManager.ResultCallBack() {
@Override
public void callBack(int requestCode, int resultCode, Intent intent) {
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
if (null != result && null != result.getContents()) {
showDialog(result.getContents());
}
}
});
capture.decode();
}

public void showDialog(String result) {
// 弹出dialog的代码略...

// 重新拉起扫描
capture.onResume();
capture.decode();
}

@Override
protected void onResume() {
super.onResume();
capture.onResume();
}

@Override
protected void onPause() {
super.onPause();
capture.onPause();
}

@Override
protected void onDestroy() {
super.onDestroy();
capture.onDestroy();
}

@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
capture.onSaveInstanceState(outState);
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
capture.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return barcodeScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
}
}


修改后的CaptureManager:

public class CaptureManager{

... 省略代码

/**
* 修改此方法,实现扫码之后不返回界面
*
* @author mark.liu
* created at 2017-9-5
*/
protected void returnResult(BarcodeResult rawResult) {
Intent intent = resultIntent(rawResult, getBarcodeImagePath(rawResult));
activity.setResult(Activity.RESULT_OK, intent);
if (barcodeView.getBarcodeView().isCameraClosed()) {
if (null != mResultCallBack) {
mResultCallBack.callBack(IntentIntegrator.REQUEST_CODE, Activity.RESULT_OK, intent);
}
// activity.finish(); 注释这一行
} else {
finishWhenClosed = true;
}
barcodeView.pause();
inactivityTimer.cancel();
}

public interface ResultCallBack {
void callBack(int requestCode, int resultCode, Intent intent);
}

private ResultCallBack mResultCallBack;

public void setResultCallBack(ResultCallBack resultCallBack) {
this.mResultCallBack = resultCallBack;
}

... 省略代码
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  二维码 摄像头