'How to draw an overlay on top of mobile vision barcode scanner?

I have been trying to get this done but with no succes for the last couple of days. I am trying to add a squarebox overlay ( wouldbe nice if it could contain 2 buttons as well) on top of the Mobile Vison Barcode scanner. I have tried different aproache and solution but with no success.

The final result should be something similar to : example

Thank you !

Update

My xml file is:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/topLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
android:orientation="vertical">


<ui.camera.CameraSourcePreview
    android:id="@+id/preview"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ui.camera.GraphicOverlay
        android:id="@+id/graphicOverlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</ui.camera.CameraSourcePreview>

</LinearLayout>

And the java class is:

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.CommonStatusCodes;

import data.model.Post;
import data.model.PostResponse;
import data.remote.APIService;
import data.remote.ApiUtils;
import ui.camera.CameraSource;
import ui.camera.CameraSourcePreview;

import ui.camera.GraphicOverlay;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

import com.google.android.gms.vision.MultiProcessor;
import com.google.android.gms.vision.barcode.Barcode;
import com.google.android.gms.vision.barcode.BarcodeDetector;

import java.io.IOException;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public final class BarcodeCaptureActivity extends AppCompatActivity                 
implements BarcodeGraphicTracker.BarcodeUpdateListener {
private static final String TAG = "Barcode-reader";

private static final int RC_HANDLE_GMS = 9001;

private static final int RC_HANDLE_CAMERA_PERM = 2;

public static final String AutoFocus = "AutoFocus";
public static final String UseFlash = "UseFlash";
public static final String BarcodeObject = "Barcode";

private CameraSource mCameraSource;
private CameraSourcePreview mPreview;
private GraphicOverlay<BarcodeGraphic> mGraphicOverlay;

private ScaleGestureDetector scaleGestureDetector;
private GestureDetector gestureDetector;

private APIService mAPIService;

private Boolean codeSent;

@Override
public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.barcode_capture);

    codeSent = false;

    mPreview = (CameraSourcePreview) findViewById(R.id.preview);
    mGraphicOverlay = (GraphicOverlay<BarcodeGraphic>) findViewById(R.id.graphicOverlay);

    boolean autoFocus = getIntent().getBooleanExtra(AutoFocus, false);
    boolean useFlash = getIntent().getBooleanExtra(UseFlash, false);

    int rc = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
    if (rc == PackageManager.PERMISSION_GRANTED) {
        createCameraSource(autoFocus, useFlash);
    } else {
        requestCameraPermission();
    }

    gestureDetector = new GestureDetector(this, new CaptureGestureListener());
    scaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());

    Snackbar.make(mGraphicOverlay, R.string.camera_info,
            Snackbar.LENGTH_LONG)
            .show();

    mAPIService = ApiUtils.getAPIService(((BaseApplication) this.getApplication()).getBaseUrl());
}

private void requestCameraPermission() {
    Log.w(TAG, "Camera permission is not granted. Requesting permission");

    final String[] permissions = new String[]{Manifest.permission.CAMERA};

    if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.CAMERA)) {
        ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM);
        return;
    }

    final Activity thisActivity = this;

    View.OnClickListener listener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            ActivityCompat.requestPermissions(thisActivity, permissions,
                    RC_HANDLE_CAMERA_PERM);
        }
    };

    findViewById(R.id.topLayout).setOnClickListener(listener);
    Snackbar.make(mGraphicOverlay, R.string.permission_camera_rationale,
            Snackbar.LENGTH_INDEFINITE)
            .setAction(R.string.ok, listener)
            .show();
}

@Override
public boolean onTouchEvent(MotionEvent e) {
    boolean b = scaleGestureDetector.onTouchEvent(e);

    boolean c = gestureDetector.onTouchEvent(e);

    return b || c || super.onTouchEvent(e);
}

@SuppressLint("InlinedApi")
private void createCameraSource(boolean autoFocus, boolean useFlash) {
    Context context = getApplicationContext();

    BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(context).build();
    BarcodeTrackerFactory barcodeFactory = new BarcodeTrackerFactory(mGraphicOverlay, this);
    barcodeDetector.setProcessor(
            new MultiProcessor.Builder<>(barcodeFactory).build());

    if (!barcodeDetector.isOperational()) {
        Log.w(TAG, "Detector dependencies are not yet available.");

        IntentFilter lowStorageFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
        boolean hasLowStorage = registerReceiver(null, lowStorageFilter) != null;

        if (hasLowStorage) {
            Toast.makeText(this, R.string.low_storage_error, Toast.LENGTH_LONG).show();
            Log.w(TAG, getString(R.string.low_storage_error));
        }
    }

    CameraSource.Builder builder = new CameraSource.Builder(getApplicationContext(), barcodeDetector)
            .setFacing(CameraSource.CAMERA_FACING_BACK)
            .setRequestedPreviewSize(1600, 1024)
            .setRequestedFps(15.0f);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        builder = builder.setFocusMode(
                autoFocus ? Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE : null);
    }


  /*  mCameraSource = builder
            .setFlashMode( Camera.Parameters.FLASH_MODE_AUTO)
            .build();*/

    mCameraSource = builder
            .setFlashMode(useFlash ? Camera.Parameters.FLASH_MODE_TORCH : null)
            .build();
}

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

@Override
protected void onPause() {
    super.onPause();
    if (mPreview != null) {
        mPreview.stop();
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mPreview != null) {
        mPreview.release();
    }
}

@Override
public void onRequestPermissionsResult(int requestCode,
                                       @NonNull String[] permissions,
                                       @NonNull int[] grantResults) {
    if (requestCode != RC_HANDLE_CAMERA_PERM) {
        Log.d(TAG, "Got unexpected permission result: " + requestCode);
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        return;
    }

    if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        Log.d(TAG, "Camera permission granted - initialize the camera source");
        // we have permission, so create the camerasource
        boolean autoFocus = getIntent().getBooleanExtra(AutoFocus, false);
        boolean useFlash = getIntent().getBooleanExtra(UseFlash, false);
        createCameraSource(autoFocus, useFlash);
        return;
    }

    Log.e(TAG, "Permission not granted: results len = " + grantResults.length +
            " Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));

    DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            finish();
        }
    };

    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle("Multitracker sample")
            .setMessage(R.string.no_camera_permission)
            .setPositiveButton(R.string.ok, listener)
            .show();
}

private void startCameraSource() throws SecurityException {
    // check that the device has play services available.
    int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
            getApplicationContext());
    if (code != ConnectionResult.SUCCESS) {
        Dialog dlg =
                GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
        dlg.show();
    }

    if (mCameraSource != null) {
        try {
            mPreview.start(mCameraSource, mGraphicOverlay);
        } catch (IOException e) {
            Log.e(TAG, "Unable to start camera source.", e);
            mCameraSource.release();
            mCameraSource = null;
        }
    }
}

private boolean onTap(float rawX, float rawY) {
    int[] location = new int[2];
    mGraphicOverlay.getLocationOnScreen(location);
    float x = (rawX - location[0]) / mGraphicOverlay.getWidthScaleFactor();
    float y = (rawY - location[1]) / mGraphicOverlay.getHeightScaleFactor();

    Barcode best = null;
    float bestDistance = Float.MAX_VALUE;
    for (BarcodeGraphic graphic : mGraphicOverlay.getGraphics()) {
        Barcode barcode = graphic.getBarcode();
        if (barcode.getBoundingBox().contains((int) x, (int) y)) {
            best = barcode;
            break;
        }
        float dx = x - barcode.getBoundingBox().centerX();
        float dy = y - barcode.getBoundingBox().centerY();
        float distance = (dx * dx) + (dy * dy);  // actually squared distance
        if (distance < bestDistance) {
            best = barcode;
            bestDistance = distance;
        }
    }

    if (best != null) {
        Intent data = new Intent();
        data.putExtra(BarcodeObject, best);
        setResult(CommonStatusCodes.SUCCESS, data);
        finish();
        return true;
    }
    return false;
}

private class CaptureGestureListener extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        return onTap(e.getRawX(), e.getRawY()) || super.onSingleTapConfirmed(e);
    }
}

private class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener {

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        return false;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        mCameraSource.doZoom(detector.getScaleFactor());
    }
}

@Override
public void onBarcodeDetected(Barcode barcode) {
    //do something with barcode data returned
    Date date = new Date();

    sendPost(barcode, formatDate(date));
}

private void sendPost(Barcode barcode, String date) {
    if (!codeSent) {
        codeSent = true;
        String urlApi = ((BaseApplication) this.getApplication()).getUrl() + ((BaseApplication) this.getApplication()).getApiKey();
        mAPIService.savePost(urlApi, new Post(barcode.displayValue, date)).enqueue(new Callback<PostResponse>() {
            @Override
            public void onResponse(Call<PostResponse> call, Response<PostResponse> response) {
                codeSent = false;
                if (response.isSuccessful()) {
                    Log.i(TAG, "post submitted to API: " + response.body().toString());
                    LaunchNewWin(response.body());
                }
            }

            @Override
            public void onFailure(Call<PostResponse> call, Throwable t) {
                Log.e(TAG, "Unable to submit post to API.", t);
                codeSent = false;
            }
        });
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
}

private static String formatDate(Date date) {
    DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    return dateFormat.format(date);
}

private void LaunchNewWin(PostResponse postResponse) {
    Intent intent = new Intent(this, ResultActivity.class);
    intent.putExtra("postResponse", postResponse.toString());
    startActivityForResult(intent, 1);
}
}


Solution 1:[1]

I've just seen your question whilst having to implement a QR code scanner myself.

What worked easily for me was to place everything in a RelativeLayout and simply stack views on top of the SurfaceView. By doing this you can obtain the required overlay effect.

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.activity.QRActivity">

<SurfaceView android:id="@+id/surfaceViewBarcode"
             android:layout_width="match_parent"
             android:layout_height="match_parent"/>

<View   android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:alpha="0.5"
        android:background="@android:color/black" />

<TextView android:text="@string/qr_scanText"
          android:gravity="center"
          android:textColor="@android:color/white"
          android:textStyle="bold"
          android:layout_centerInParent="true"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:layout_alignBottom="@+id/surfaceViewBarcode"
          android:layout_alignParentTop="true"/>

The only inconvenience is the fact that the bar code scanner will pickup the QR code even if it's not placed in the designated area.

After following this thread, I've found out that you have multiple options for achieving the required result. One would be by implementing a custom processor

Another way is by BoxDetector.

I think this example may come also come in handy

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1