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

一、关于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;

    ...省略代码
}
  1. BarcodeView就是背景
  2. ViewfinderView就是扫描框
  3. TextView为下方提示文字

上个图:
第三方ZXing库zxing-android-embedded使用及自定义_第1张图片

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

  1. 新建一个Activity
  2. 把CaptureManager和DecoratedBarcodeView复制到我们自定义的Activity中
  3. 设置setCaptureActivity(CustomCaptureActivity.class)为我们自己的Activity
  4. 别忘了把自定义的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);
    }
}

自定义的布局:


<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();// 初始化扫码

这是我修改过后的界面:
第三方ZXing库zxing-android-embedded使用及自定义_第2张图片

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

四、扫码后不结束扫码界面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 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;
    }

    ... 省略代码
}

你可能感兴趣的:(安卓开发)