Customizing Dynamsoft Android Barcode Scanner API to Filter and Select Desired Results

Dynamsoft Barcode Scanner API is open source and free to customize. It currently provides two modes: Single and Multiple. In Single mode, the barcode scanner freezes the camera preview and waits for the user to select a barcode if multiple barcodes are detected. In Multiple mode, the scanner instantly returns all barcodes found in the camera preview. A common multi-code scanning scenario is when users want to filter and select only the desired barcode results. This article will guide you on how to merge the functionality of Single and Multiple modes to enable selective multiple barcode scanning.

Android Barcode Scanner Demo Video

Prerequisites

Steps to Customize Barcode Scanner API for Multi-Selection

The BarcodeScanner project created in the previous article will be used as the base. It consists of an application project and a Barcode Scanner API module.

Step 1: Modify the Android Application Project

In this new scenario, only one button is required to trigger the barcode scanning process.

  1. Update the layout file activity_main.xml to remove the Single mode button and rename the Multiple mode button.

     <?xml version="1.0" encoding="utf-8"?>
     <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
         xmlns:tools="http://schemas.android.com/tools"
         android:id="@+id/main"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:padding="16dp"
         tools:context=".MainActivity">
        
         <Button
             android:id="@+id/btn_multi"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/button_multi_scan"
             android:textColor="@android:color/white"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintEnd_toEndOf="parent" />
        
         <TextView
             android:id="@+id/tv_result"
             android:layout_width="0dp"
             android:layout_height="0dp"
             android:textSize="20sp"
             android:textIsSelectable="true"
             android:scrollbars="vertical"
             android:overScrollMode="always"
             android:padding="16dp"
             android:background="@android:color/white"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintBottom_toTopOf="@+id/btn_multi"
             android:layout_marginBottom="16dp" />
        
     </androidx.constraintlayout.widget.ConstraintLayout>
        
    

    Android UI update

  2. Update the handleButtonClick() method in MainActivity.java to start the barcode scanning process.

     private void handleButtonClick(View v) {
         config.setScanningMode(EnumScanningMode.SM_MULTIPLE);
         launcher.launch(config);
     }
    

Step 2: Modify the Barcode Scanner API Module

The workflow of the barcode scanning process is as follows:

  1. Start the camera preview and detect barcodes.
  2. When the user clicks the Capture button, the barcode scanner freezes the camera preview and displays the detected barcodes with overlay circles.
  3. Users can remove undesired barcodes by tapping the overlay circles.
  4. Click the checkmark button to confirm the selected barcodes and return the results to the application.

select desired barcode results from multiple options

Navigate to the dbrbundle module to modify the barcode scanner API.

1. Add a Checkmark Button to activity_scanner_barcode.xml

  1. Download the checkmark icon from Google Fonts and save it as done.xml in the res/drawable folder.
  2. Add the checkmark button to the top-right corner of the toolbar.

     <?xml version="1.0" encoding="utf-8"?>
     <RelativeLayout 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">
        
     	<androidx.appcompat.widget.Toolbar
     		android:id="@+id/toolbar"
     		android:layout_width="match_parent"
     		android:layout_height="?attr/actionBarSize"
     		android:background="@color/colorPrimary"
     		android:elevation="4dp"
     		android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
        
     		<RelativeLayout
     			android:layout_width="match_parent"
     			android:layout_height="match_parent">
        
     			<ImageView
     				android:id="@+id/iv_back"
     				android:layout_width="25dp"
     				android:layout_height="25dp"
     				android:layout_marginStart="16dp"
     				android:layout_marginTop="16dp"
     				android:src="@drawable/ic_arrow_left" />
        
     			<TextView
     				android:layout_width="wrap_content"
     				android:layout_height="wrap_content"
     				android:layout_centerInParent="true"
     				android:text="Barcode Scanner"
     				android:textColor="@android:color/white"
     				android:textSize="18sp"
     				android:textStyle="bold" />
        
     			<ImageView
     				android:id="@+id/btn_check"
     				android:layout_width="25dp"
     				android:layout_height="25dp"
     				android:layout_alignParentEnd="true"
     				android:layout_marginEnd="16dp"
     				android:layout_marginTop="16dp"
     				android:src="@drawable/done" />
        
     		</RelativeLayout>
     	</androidx.appcompat.widget.Toolbar>
        
     	<com.dynamsoft.dce.CameraView
     		android:id="@+id/camera_view"
     		android:layout_width="match_parent"
     		android:layout_height="match_parent" />
        
     	<View
     		android:id="@+id/touch_view"
     		android:layout_width="match_parent"
     		android:layout_height="match_parent" />
        
     	<LinearLayout
     		android:id="@+id/action_view"
     		android:layout_width="wrap_content"
     		android:layout_centerHorizontal="true"
     		android:layout_height="wrap_content"
     		android:layout_alignParentBottom="true"
     		android:gravity="center"
     		android:layout_marginBottom="90dp">
        
     		<android.widget.Button
     			android:id="@+id/btn_torch"
     			android:layout_width="40dp"
     			android:layout_height="40dp"
     			android:background="@drawable/icon_flash_off" />
        
     		<android.widget.Button
     			android:id="@+id/btn_toggle"
     			android:layout_marginStart="50dp"
     			android:layout_width="40dp"
     			android:layout_height="40dp"
     			android:background="@drawable/toggle_lens" />
     	</LinearLayout>
        
     	<Button
     		android:id="@+id/btn_capture"
     		android:layout_width="wrap_content"
     		android:layout_height="wrap_content"
     		android:layout_centerHorizontal="true"
     		android:layout_above="@id/action_view"
     		android:layout_marginBottom="16dp"
     		android:text="Capture"
     		android:src="@drawable/circle" />
     </RelativeLayout>
    

2. Add a Click Event for the Checkmark Button in BarcodeScannerActivity.java

Declare the checkmark button and implement the click event.

private ImageView btnSave;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    btnSave = findViewById(R.id.btn_check);
    ...
}

private void initCaptureButton() {
        ...

        btnSave.setVisibility(View.VISIBLE);
        btnSave.setOnClickListener(v -> {
            BarcodeResultItem[] resultArray = mapResultItem.values().toArray(new BarcodeResultItem[0]);
            if (resultArray.length > 0) {
                resultOK(BarcodeScanResult.EnumResultStatus.RS_FINISHED, resultArray);
                finish();
            }

        });
    }

mapResultItem is a HashMap that stores the detected barcode results. The resultOK() method is used to return the selected barcode results to the application.

3: Draw Overlay Circles for Barcode Selection

  1. Register the callback function to handle the barcode detection results:

     protected void onResume() {
         ...
         mRouter.addResultReceiver(new CapturedResultReceiver() {
             @Override
             public void onDecodedBarcodesReceived(@NonNull DecodedBarcodesResult result) {
                 if (isCaptureTriggered) {
                     processResult(result);
                 }
             }
         });
         ...
     }
    
  2. Freeze the camera preview and draw overlay circles for detected barcodes:

     private void processResult (DecodedBarcodesResult result) {
         if (result.getItems().length > 1) {
             if (configuration.isBeepEnabled()) {
                 beep();
             }
             mRouter.stopCapturing();
             try {
                 mCamera.setScanRegion(null);
                 mCamera.close();
             } catch (CameraEnhancerException e) {
                 throw new RuntimeException(e);
             }
             runOnUiThread(() -> {
                 btnCapture.setVisibility(View.GONE);
                 mCameraView.setScanLaserVisible(false);
                 btnToggle.setVisibility(View.GONE);
                 btnTorch.setVisibility(View.GONE);
             });
    
             drawSymbols(result);
         }
     }
    
     private void drawSymbols(DecodedBarcodesResult scanResult) {
         runOnUiThread(() -> {
             btnToggle.setVisibility(View.GONE);
             btnTorch.setVisibility(View.GONE);
         });
         int matchedStyle = DrawingStyleManager.createDrawingStyle(Color.WHITE, 3, Color.GREEN, Color.WHITE);
         DrawingLayer layer = mCameraView.getDrawingLayer(DrawingLayer.DBR_LAYER_ID);
         layer.setDefaultStyle(matchedStyle);
         ArrayList<DrawingItem> drawingItemArrayList = new ArrayList<>();
         mapResultItem = new HashMap<>();
         int offsetX = 0;
         int offsetY = 0;
         if (scanRegion != null) {
             if (scanRegion.measuredInPercentage) {
                 Size size = mCamera.getResolution();
                 offsetX = (int) (scanRegion.left * size.getHeight());
                 offsetY = (int) (scanRegion.top * size.getWidth());
             } else {
                 offsetX = (int) scanRegion.left;
                 offsetY = (int) scanRegion.top;
             }
         }
         BarcodeResultItem[] items = scanResult.getItems();
         for (int i = 0; i < items.length; i++) {
             BarcodeResultItem item = items[i];
             int arcCenterX = (item.getLocation().points[0].x + offsetX + item.getLocation().points[2].x + offsetX) / 2;
             int arcCenterY = (item.getLocation().points[0].y + offsetY + item.getLocation().points[2].y + offsetY) / 2;
             Point arcCenter = new Point(arcCenterX, arcCenterY);
             ArcDrawingItem drawingItem = new ArcDrawingItem(arcCenter, radius, EnumCoordinateBase.CB_IMAGE);
             drawingItem.addNote(new Note("index", i + ""), true);
             mapResultItem.put(i, item);
             drawingItemArrayList.add(drawingItem);
         }
         layer.setDrawingItems(drawingItemArrayList);
     }
    
  3. Remove undesired barcodes by tapping the overlay circles. Coordinate conversion is required to match the touch event with the barcode location:

     mTouchView.setOnTouchListener((v, e) -> {
         if (e.getAction() == MotionEvent.ACTION_DOWN) {
             ArrayList<DrawingItem> items = mCameraView.getDrawingLayer(DrawingLayer.DBR_LAYER_ID).getDrawingItems();
             if (items.size() > 1) {
                 for (int index = 0; index < items.size(); index++) {
                     DrawingItem item = items.get(index);
                     int centerX = ((ArcDrawingItem) item).getCentre().x;
                     int centerY = ((ArcDrawingItem) item).getCentre().y;
                     float touchX = e.getX();
                     float touchY = e.getY();
                     Point point = mCamera.convertPointToViewCoordinates(new Point(centerX, centerY));
                     float density = getResources().getDisplayMetrics().density;
                     if (isPointInCircle(touchX, touchY, point.x / density, point.y / density, 70)) {
                         items.remove(item);
                         mapResultItem.remove(index);
                         mCameraView.getDrawingLayer(DrawingLayer.DBR_LAYER_ID).setDrawingItems(items);
                         break;
                     }
                 }
             }
         }
         return false;
     });
    

Source Code

https://github.com/yushulx/android-camera-barcode-mrz-document-scanner/tree/main/examples/10.x/SelectMultiBarcodes