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+ }
0 commit comments