'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 :
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 |