Skip to content

Commit 7bf735c

Browse files
barcode widget added
1 parent 36f677b commit 7bf735c

File tree

8 files changed

+624
-3
lines changed

8 files changed

+624
-3
lines changed

android/src/main/java/com/amolg/flutterbarcodescanner/FlutterBarcodeScannerPlugin.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import androidx.lifecycle.Lifecycle;
1212
import androidx.lifecycle.LifecycleOwner;
1313

14+
import com.amolg.flutterbarcodescanner.widget.BarcodeViewFactory;
1415
import com.google.android.gms.common.api.CommonStatusCodes;
1516
import com.google.android.gms.vision.barcode.Barcode;
1617

@@ -220,6 +221,10 @@ public void run() {
220221
@Override
221222
public void onAttachedToEngine(FlutterPluginBinding binding) {
222223
pluginBinding = binding;
224+
binding.getPlatformViewRegistry().registerViewFactory(
225+
"plugins.codingwithtashi/barcode_scanner_view",
226+
new BarcodeViewFactory(binding.getBinaryMessenger())
227+
);
223228
}
224229

225230
@Override
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.amolg.flutterbarcodescanner.widget;
2+
3+
import android.content.Context;
4+
import io.flutter.plugin.common.BinaryMessenger;
5+
import io.flutter.plugin.platform.PlatformView;
6+
import io.flutter.plugin.platform.PlatformViewFactory;
7+
import io.flutter.plugin.common.StandardMessageCodec;
8+
9+
public class BarcodeViewFactory extends PlatformViewFactory {
10+
private final BinaryMessenger messenger;
11+
12+
public BarcodeViewFactory(BinaryMessenger messenger) {
13+
super(StandardMessageCodec.INSTANCE);
14+
this.messenger = messenger;
15+
}
16+
17+
@Override
18+
public PlatformView create(Context context, int id, Object args) {
19+
return new FlutterBarcodeView(context, messenger, id);
20+
}
21+
}
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
package com.amolg.flutterbarcodescanner.widget;
2+
3+
import android.content.Context;
4+
import android.hardware.Camera;
5+
import android.view.View;
6+
import android.util.Log;
7+
import android.view.SurfaceHolder;
8+
import android.view.SurfaceView;
9+
import android.os.Handler;
10+
import android.os.Looper;
11+
import android.view.ViewGroup;
12+
import android.widget.FrameLayout;
13+
import android.graphics.Canvas;
14+
import android.graphics.Paint;
15+
import android.graphics.Rect;
16+
import android.graphics.RectF;
17+
import android.view.animation.Animation;
18+
import android.view.animation.TranslateAnimation;
19+
import android.widget.ImageView;
20+
import android.graphics.Color;
21+
import androidx.annotation.NonNull;
22+
23+
import com.google.android.gms.vision.CameraSource;
24+
import com.google.android.gms.vision.Detector;
25+
import com.google.android.gms.vision.barcode.Barcode;
26+
import com.google.android.gms.vision.barcode.BarcodeDetector;
27+
28+
import io.flutter.plugin.common.BinaryMessenger;
29+
import io.flutter.plugin.common.MethodCall;
30+
import io.flutter.plugin.common.MethodChannel;
31+
import io.flutter.plugin.platform.PlatformView;
32+
33+
34+
import java.io.IOException;
35+
import java.lang.reflect.Field;
36+
37+
public class FlutterBarcodeView implements PlatformView{
38+
39+
40+
private static final String TAG = "FlutterBarcodeView";
41+
private final Context context;
42+
private final FrameLayout frameLayout;
43+
private final SurfaceView surfaceView;
44+
private final ScannerOverlay scannerOverlay;
45+
private final ImageView scanLine;
46+
private final MethodChannel methodChannel;
47+
private CameraSource cameraSource;
48+
private BarcodeDetector barcodeDetector;
49+
private boolean isDetecting = true;
50+
private boolean isFlashOn = false;
51+
private final Handler mainHandler;
52+
private final int SCAN_AREA_WIDTH = 800;
53+
private final int SCAN_AREA_HEIGHT = 800;
54+
55+
public FlutterBarcodeView(Context context, BinaryMessenger messenger, int id) {
56+
this.context = context;
57+
this.mainHandler = new Handler(Looper.getMainLooper());
58+
59+
// Create main container
60+
frameLayout = new FrameLayout(context);
61+
62+
// Create and add surface view
63+
surfaceView = new SurfaceView(context);
64+
frameLayout.addView(surfaceView, new FrameLayout.LayoutParams(
65+
ViewGroup.LayoutParams.MATCH_PARENT,
66+
ViewGroup.LayoutParams.MATCH_PARENT));
67+
68+
// Create and add scanner overlay
69+
scannerOverlay = new ScannerOverlay(context);
70+
frameLayout.addView(scannerOverlay, new FrameLayout.LayoutParams(
71+
ViewGroup.LayoutParams.MATCH_PARENT,
72+
ViewGroup.LayoutParams.MATCH_PARENT));
73+
74+
// Create and add scan line
75+
scanLine = new ImageView(context);
76+
scanLine.setBackgroundColor(Color.RED);
77+
FrameLayout.LayoutParams scanLineParams = new FrameLayout.LayoutParams(
78+
SCAN_AREA_WIDTH - 40, // Slightly smaller than scan area
79+
5); // Line thickness
80+
frameLayout.addView(scanLine, scanLineParams);
81+
82+
// Start scan line animation
83+
startScanLineAnimation();
84+
85+
methodChannel = new MethodChannel(messenger, "plugins.codingwithtashi/barcode_scanner_view_" + id);
86+
methodChannel.setMethodCallHandler(this::onMethodCall);
87+
88+
setupBarcodeDetector();
89+
setupCameraSource();
90+
setupSurfaceHolder();
91+
}
92+
93+
private void startScanLineAnimation() {
94+
TranslateAnimation animation = new TranslateAnimation(
95+
Animation.RELATIVE_TO_PARENT, 0f,
96+
Animation.RELATIVE_TO_PARENT, 0f,
97+
Animation.RELATIVE_TO_PARENT, 0f,
98+
Animation.RELATIVE_TO_PARENT, 0.8f);
99+
animation.setDuration(3000);
100+
animation.setRepeatCount(Animation.INFINITE);
101+
animation.setRepeatMode(Animation.REVERSE);
102+
scanLine.startAnimation(animation);
103+
}
104+
105+
private class ScannerOverlay extends View {
106+
private final Paint boxPaint;
107+
private final Paint transparentPaint;
108+
private final int boxCornerRadius = 20;
109+
110+
public ScannerOverlay(Context context) {
111+
super(context);
112+
boxPaint = new Paint();
113+
boxPaint.setColor(Color.WHITE);
114+
boxPaint.setStyle(Paint.Style.STROKE);
115+
boxPaint.setStrokeWidth(5f);
116+
117+
transparentPaint = new Paint();
118+
transparentPaint.setColor(Color.parseColor("#80000000")); // Semi-transparent black
119+
transparentPaint.setStyle(Paint.Style.FILL);
120+
}
121+
122+
@Override
123+
protected void onDraw(Canvas canvas) {
124+
super.onDraw(canvas);
125+
int width = getWidth();
126+
int height = getHeight();
127+
128+
// Calculate scanner box position (centered)
129+
int left = (width - SCAN_AREA_WIDTH) / 2;
130+
int top = (height - SCAN_AREA_HEIGHT) / 2;
131+
int right = left + SCAN_AREA_WIDTH;
132+
int bottom = top + SCAN_AREA_HEIGHT;
133+
134+
// Draw transparent overlay
135+
canvas.drawRect(0, 0, width, top, transparentPaint); // Top
136+
canvas.drawRect(0, top, left, bottom, transparentPaint); // Left
137+
canvas.drawRect(right, top, width, bottom, transparentPaint); // Right
138+
canvas.drawRect(0, bottom, width, height, transparentPaint); // Bottom
139+
140+
// Draw scanner box
141+
RectF boxRect = new RectF(left, top, right, bottom);
142+
canvas.drawRoundRect(boxRect, boxCornerRadius, boxCornerRadius, boxPaint);
143+
}
144+
}
145+
146+
private void setupBarcodeDetector() {
147+
barcodeDetector = new BarcodeDetector.Builder(context)
148+
.setBarcodeFormats(Barcode.ALL_FORMATS)
149+
.build();
150+
151+
barcodeDetector.setProcessor(new Detector.Processor<Barcode>() {
152+
private final Rect scanArea = new Rect();
153+
154+
@Override
155+
public void release() {}
156+
157+
@Override
158+
public void receiveDetections(@NonNull Detector.Detections<Barcode> detections) {
159+
if (!isDetecting) return;
160+
161+
// Calculate scan area boundaries
162+
int width = surfaceView.getWidth();
163+
int height = surfaceView.getHeight();
164+
int left = (width - SCAN_AREA_WIDTH) / 2;
165+
int top = (height - SCAN_AREA_HEIGHT) / 2;
166+
scanArea.set(left, top, left + SCAN_AREA_WIDTH, top + SCAN_AREA_HEIGHT);
167+
168+
final android.util.SparseArray<Barcode> barcodes = detections.getDetectedItems();
169+
if (barcodes.size() > 0) {
170+
final Barcode code = barcodes.valueAt(0);
171+
System.out.println("Barcode detected: " + code.rawValue);
172+
for (int i = 0; i < barcodes.size(); i++) {
173+
Barcode barcode = barcodes.valueAt(i);
174+
// Check if barcode is within scan area
175+
if (scanArea.contains(barcode.getBoundingBox())) {
176+
mainHandler.post(() -> {
177+
methodChannel.invokeMethod("onBarcodeDetected", barcode.rawValue);
178+
}
179+
);
180+
return;
181+
}
182+
}
183+
}
184+
185+
}
186+
});
187+
}
188+
189+
@Override
190+
public View getView() {
191+
return frameLayout;
192+
}
193+
private void setupCameraSource() {
194+
cameraSource = new CameraSource.Builder(context, barcodeDetector)
195+
.setAutoFocusEnabled(true)
196+
.setRequestedPreviewSize(1600, 1024)
197+
.build();
198+
}
199+
200+
private void setupSurfaceHolder() {
201+
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
202+
@Override
203+
public void surfaceCreated(SurfaceHolder holder) {
204+
try {
205+
cameraSource.start(surfaceView.getHolder());
206+
} catch (IOException e) {
207+
Log.e(TAG, "Error starting camera source: " + e.getMessage());
208+
methodChannel.invokeMethod("onError", "Failed to start camera: " + e.getMessage());
209+
} catch (SecurityException e) {
210+
Log.e(TAG, "Camera permission not granted: " + e.getMessage());
211+
methodChannel.invokeMethod("onError", "Camera permission not granted");
212+
}
213+
}
214+
215+
@Override
216+
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
217+
218+
@Override
219+
public void surfaceDestroyed(SurfaceHolder holder) {
220+
cameraSource.stop();
221+
}
222+
});
223+
}
224+
225+
private Camera getCamera() {
226+
try {
227+
Field[] declaredFields = CameraSource.class.getDeclaredFields();
228+
for (Field field : declaredFields) {
229+
if (field.getType() == Camera.class) {
230+
field.setAccessible(true);
231+
return (Camera) field.get(cameraSource);
232+
}
233+
}
234+
} catch (Exception e) {
235+
Log.e(TAG, "Error accessing camera: " + e.getMessage());
236+
}
237+
return null;
238+
}
239+
240+
private void toggleFlash(MethodChannel.Result result) {
241+
try {
242+
Camera camera = getCamera();
243+
if (camera != null) {
244+
Camera.Parameters parameters = camera.getParameters();
245+
if (!isFlashOn) {
246+
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
247+
isFlashOn = true;
248+
} else {
249+
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
250+
isFlashOn = false;
251+
}
252+
camera.setParameters(parameters);
253+
result.success(isFlashOn);
254+
} else {
255+
result.error("CAMERA_ERROR", "Camera not available", null);
256+
}
257+
} catch (Exception e) {
258+
result.error("FLASH_ERROR", "Error toggling flash: " + e.getMessage(), null);
259+
}
260+
}
261+
262+
263+
264+
private void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
265+
switch (call.method) {
266+
case "pauseScanning":
267+
isDetecting = false;
268+
result.success(null);
269+
break;
270+
case "resumeScanning":
271+
isDetecting = true;
272+
result.success(null);
273+
break;
274+
case "toggleFlash":
275+
toggleFlash(result);
276+
break;
277+
default:
278+
result.notImplemented();
279+
}
280+
}
281+
282+
@Override
283+
public void dispose() {
284+
if (cameraSource != null) {
285+
cameraSource.release();
286+
cameraSource = null;
287+
}
288+
if (barcodeDetector != null) {
289+
barcodeDetector.release();
290+
barcodeDetector = null;
291+
}
292+
scanLine.clearAnimation();
293+
}
294+
}

example/lib/main.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class HomePage extends StatefulWidget {
2929
}
3030

3131
class _HomePageState extends State<HomePage> {
32+
BarcodeViewController? controller;
3233
String result = '';
3334
@override
3435
Widget build(BuildContext context) {
@@ -82,6 +83,41 @@ class _HomePageState extends State<HomePage> {
8283
},
8384
child: const Text('Stream Barcode'),
8485
),
86+
SizedBox(
87+
height: 10,
88+
),
89+
SizedBox(
90+
width: 200,
91+
height: 200,
92+
child: SimpleBarcodeScanner(
93+
onScanned: (code) {
94+
setState(() {
95+
result = code;
96+
});
97+
},
98+
continuous: true,
99+
onBarcodeViewCreated: (BarcodeViewController controller) {
100+
this.controller = controller;
101+
},
102+
)),
103+
ElevatedButton(
104+
onPressed: () {
105+
controller?.toggleFlash();
106+
},
107+
child: Text("Switch"),
108+
),
109+
ElevatedButton(
110+
onPressed: () {
111+
controller?.pauseScanning();
112+
},
113+
child: Text("Pause"),
114+
),
115+
ElevatedButton(
116+
onPressed: () {
117+
controller?.resumeScanning();
118+
},
119+
child: Text("Resume"),
120+
),
85121
],
86122
),
87123
),

0 commit comments

Comments
 (0)