Use and customization of third-party ZXing library zxing-android-embedded

Hits: 0

1. About ZXing

Now one-dimensional code and two-dimensional code are so widely used in our daily life, so the APP with code scanning function has become very common. An Android APP needs to use zxing for scanning code function. zxing is an open source software of Google. It is more convenient for developers to use the camera library, and our commonly used scan code function is one of them.

[github] address: https://github.com/zxing/zxing

However, because the function of zxing is too powerful, it contains many functions that we cannot use, so the scan code function is generally extracted and used alone. This extraction process is still a bit troublesome, but there are already many developers. After this process, let’s introduce a great third-party zxing library: zxing-android-embedded

Second, the third-party zxing library zxing-android-embedded

github address: https://github.com/journeyapps/zxing-android-embedded

The way to use is also very simple, first add dependencies in gradle

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

Then call it in Activity or fragment:

new IntentIntegrator(this)
        .setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES) ​​// Type of scan code, optional: 1D code, QR code, 1D/QR 
        code.setPrompt( "Please aim at the QR code" ) //Set the 
        prompt.setCameraId( 0 ) // Select the camera, you can use the front or rear. 
        setBeepEnabled( false ) // Whether to turn on the sound, there will be a "beep" after scanning the code. 
        setBarcodeImageEnabled( true ) // Generate a QR code after scanning the code image.initiateScan 
        (); // Initialize scan code

After scanning the code, the result will be called back in the onActivityResult method

@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);
    }
}

The above is the simple way to use

3. Custom interface

When we develop, we may want to customize the interface, so we need to modify the code. The custom interface can be achieved:
1. The default scan code is horizontal screen, after customization, you can scan the code vertically
2. Modify Scan code box layout, you can add other Views

In fact, when setting up IntentIntegrator, there will be a method: setCaptureActivity(Activity). This method is used to set the Activity of the code scanning interface. When it is not set, the CaptureActivity written by the library author will be called by default. We can take a look at this Activity together. What have you done:

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();
    }

    ...omitting the code
}

There are two important member variables here: CaptureManager and DecoratedBarcodeView, as can be seen from their names:
1. CaptureManager is a class used to pull up the scan code and process the scan code result
2. DecoratedBarcodeView is a display code scan interface Custom View

With this understanding, it is clear that we need to customize the code scanning interface. Let’s take a brief look at what DecoratedBarcodeView is:

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

    ...omitting the code
}

  1. BarcodeView is the background
  2. ViewfinderView is the scan frame
  3. TextView is the prompt text below

Last picture:

After understanding the role of these three Views, we can start our customization, and how to scan and parse these three Views is not the focus of our concern, so we skip it directly. Steps to customize the interface:

  1. Create a new Activity
  2. Copy CaptureManager and DecoratedBarcodeView to our custom Activity
  3. Set setCaptureActivity(CustomCaptureActivity.class) to our own Activity
  4. Don’t forget to add your custom Activity to AndroidManifest.xml to register

On the code, my custom 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); // custom layout

        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);
    }
}

Custom layout:

<?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 = "I'm a new button" />

</LinearLayout>

Call method:

new IntentIntegrator( this )
         // Custom Activity, the focus is on this line ----------------------------
        .setCaptureActivity(CustomCaptureActivity.class)
        .setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES) ​​// Type of scan code, optional: 1D code, QR code, 1D/QR 
        code.setPrompt( "Please aim at the QR code" ) //Set the 
        prompt.setCameraId( 0 ) // Select the camera, you can use the front or rear. 
        setBeepEnabled( false ) // Whether to turn on the sound, there will be a "beep" after scanning the code. 
        setBarcodeImageEnabled( true ) // Generate a QR code after scanning the code image.initiateScan 
        (); // Initialize scan code

Here is my modified interface:

You can customize the interface you want according to your needs.

4. Do not end the code scanning interface activity after scanning the code

When we use zxing-android-embedded, we will find one thing, that is, every time after scanning the code, the code scanning interface activity will be ended, and then the result of the code scanning will be obtained in the onActivityResult of the previous activity. Generally, such an operation is possible. It meets the needs of our project, but the author recently encountered a requirement during development. After scanning the code, he needs to pop up a Dialog without exiting the Activity, and then scan the code after the Dialog disappears. After searching around, I found that zxing-android-embedded does not provide such a method to satisfy the code scanning activity without ending the code scanning. There is no way to force me to look up the source code to see if I can start from the source code. Here is a summary of the requirements:
1. The scanning activity cannot be ended after the scanning is completed
. 2. The scanning results can be obtained in the scanning activity.
3. Obtained After the result, the Dialog will pop up. At this time, the scanning code should be in a suspended state, otherwise there will be unknown problems
. 4. After the Dialog disappears, the scanning code should be reactivated.

With the above requirements, we start from the place where the code scan is called:

new IntentIntegrator(this)
        .setCaptureActivity(CustomCaptureActivity.class) // Custom Activity 
        .setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES) ​​// Type of scan code, optional: one-dimensional code, two-dimensional code, one/two-dimensional 
        code.setPrompt( "Please aim at two Dimension code" ) // Set the prompt. 
        setCameraId( 0 ) // Select the camera, you can use the front or rear. 
        setBeepEnabled( false ) // Whether to enable the sound, there will be a "beep" after scanning the code. 
        setBarcodeImageEnabled( true ) // After scanning the code, generate a picture of the 
        QR code.initiateScan(); // Initialize the code scanning

In the construction method, the current Activity is set to the IntentIntegrator, and the author’s demo corresponds to MainActivity

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

Then the second key line, set the scan code Activity

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

Starting from the third line is the setting of some properties, these are not the focus of our concern, so we skip it. I haven’t mentioned the IntentIntegrator class for so long. This class is actually an entry for us to call the scan code. Various parameters are set in this class. Remember that our requirement is to not end the scan code after the code scan. Activity code? We focus on captureActivity, but the result is disappointing. There is no captureActivity-related operation in this class, but we can find some clues in the initiateScan() method.

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

There is only a simple line of code here, let’s first see what is done in 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;
}

In this code, we only need to care about the first line. From the first line, we can know that this method will return an Intent. This Intent jumps from activity to our custom captureActivity, and carries a lot of set parameters, so that I return to 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);
    }
}

Because our scan code may be called from the fragment, the author of the framework has done the processing of calling the scan code in the fragment and the activity respectively. Here we only care about the activity. Unfortunately, there is no IntentIntegrator we can start However, we learned that the operation of scanning the code is actually done in captureActivity, so let’s focus on captureActivity.

In fact, in the third section, we have already seen this Activity, but we only cared about the custom interface at that time, so we ignored an important class: CaptureManager, this class is actually the place where the scanning code is really processed, all settings and interfaces. , Activity calls will be completed in CaptureManager, let’s take a look at his use:

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

In the construction method, the current code scanning Activity and the code scanning View are set.

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);
}

The initializeFromIntent() method mainly takes out the various scan code parameters we set from the Intent, and the source code is not given here.

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

The last decode() method is where the code scanning is really started. We can see here that the barcodeView calls the decodeSingle method to start scanning the code, and also sets a callback to it. You can guess that this callback is the callback used to receive the code scanning result.

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) {
    }
};

In this callback, I see a returnResult(result), which is where the result is returned after scanning the code. We did not guess wrong.

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

BarcodeResult is our scan result. The first line of code is used to parse BarcodeResult and generate an Intent. This Intent is the key to carry the scan result back to onActivityResult. The name of the third line of code is obviously used to end Activity method, let’s follow up to see:

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

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

The culprit is the finish() method! ! That’s it, it ends our Activity, and we can start when we find the source. We can’t end our Activity by commenting it out. But now the Activity is not over, but we want to return the result, then we have to add a callback ourselves

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

private ResultCallBack mResultCallBack;

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

The modified returnResult method is as follows. Here I modified the returnResult method, but did not modify the closeAndFinish method above, because the closeAndFinish method is not only called when returning the scan code structure, so in order to prevent problems, I modified the returnResult method

/**
 * Modify this method to not return to the interface after scanning the code
 */
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(); comment out this line 
    } else {
        finishWhenClosed = true;
    }
    barcodeView.pause();
    inactivityTimer.cancel();
}

So far, our modifications are finished. In fact, the changes are very small. Just a few lines of code can realize the scanning activity without ending the code scanning, but the important thing is the process of thinking and the process of reading the source code. Just get those requirements and then I was also very panicked when I found that the third-party library could not be implemented. I also tried to modify the source code but it was unsuccessful. The last time I calmed down and slowly looked at the source code, I finally got it. This is also an improvement for myself. Thank you for reading The article, with the modified code attached at the end:

Modified 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); // custom layout

        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)  {
         // The code to pop up the dialog is slightly...

        // restart scan
        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);
    }
}

Modified CaptureManager:

public class CaptureManager{

    ... omit code


    /**
     * Modify this method to not return to the interface after scanning the code
     *
     * @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(); comment this line 
        } 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;
    }

    ... omit code
}

You may also like...

Leave a Reply

Your email address will not be published.