DEV Community

Xiao Ling
Xiao Ling

Posted on • Originally published at dynamsoft.com

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

        });
    }
Enter fullscreen mode Exit fullscreen mode

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

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Sentry growth stunted Image

If you are wasting time trying to track down the cause of a crash, it’s time for a better solution. Get your crash rates to zero (or close to zero as possible) with less time and effort.

Try Sentry for more visibility into crashes, better workflow tools, and customizable alerts and reporting.

Switch Tools

👋 Kindness is contagious

If you found this post useful, please drop a ❤️ or leave a kind comment!

Okay