Showing
2 changed files
with
54 additions
and
123 deletions
| @@ -64,7 +64,9 @@ class MyHome extends StatelessWidget { | @@ -64,7 +64,9 @@ class MyHome extends StatelessWidget { | ||
| 64 | ), | 64 | ), |
| 65 | ); | 65 | ); |
| 66 | }, | 66 | }, |
| 67 | - child: const Text('MobileScanner with Controller (returning image)'), | 67 | + child: const Text( |
| 68 | + 'MobileScanner with Controller (returning image)', | ||
| 69 | + ), | ||
| 68 | ), | 70 | ), |
| 69 | ElevatedButton( | 71 | ElevatedButton( |
| 70 | onPressed: () { | 72 | onPressed: () { |
| 1 | import 'package:flutter/material.dart'; | 1 | import 'package:flutter/material.dart'; |
| 2 | import 'package:mobile_scanner/mobile_scanner.dart'; | 2 | import 'package:mobile_scanner/mobile_scanner.dart'; |
| 3 | +import 'package:mobile_scanner_example/scanned_barcode_label.dart'; | ||
| 4 | +import 'package:mobile_scanner_example/scanner_button_widgets.dart'; | ||
| 3 | import 'package:mobile_scanner_example/scanner_error_widget.dart'; | 5 | import 'package:mobile_scanner_example/scanner_error_widget.dart'; |
| 4 | 6 | ||
| 5 | class BarcodeScannerWithOverlay extends StatefulWidget { | 7 | class BarcodeScannerWithOverlay extends StatefulWidget { |
| 6 | @override | 8 | @override |
| 7 | - _BarcodeScannerWithOverlayState createState() => | ||
| 8 | - _BarcodeScannerWithOverlayState(); | 9 | + _BarcodeScannerWithOverlayState createState() => _BarcodeScannerWithOverlayState(); |
| 9 | } | 10 | } |
| 10 | 11 | ||
| 11 | class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> { | 12 | class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> { |
| 12 | - String overlayText = "Please scan QR Code"; | ||
| 13 | - bool camStarted = false; | ||
| 14 | - | ||
| 15 | final MobileScannerController controller = MobileScannerController( | 13 | final MobileScannerController controller = MobileScannerController( |
| 16 | formats: const [BarcodeFormat.qrCode], | 14 | formats: const [BarcodeFormat.qrCode], |
| 17 | - autoStart: false, | ||
| 18 | ); | 15 | ); |
| 19 | 16 | ||
| 20 | @override | 17 | @override |
| 21 | - void dispose() { | ||
| 22 | - controller.dispose(); | ||
| 23 | - super.dispose(); | ||
| 24 | - } | ||
| 25 | - | ||
| 26 | - void startCamera() { | ||
| 27 | - if (camStarted) { | ||
| 28 | - return; | ||
| 29 | - } | ||
| 30 | - | ||
| 31 | - controller.start().then((_) { | ||
| 32 | - if (mounted) { | ||
| 33 | - setState(() { | ||
| 34 | - camStarted = true; | ||
| 35 | - }); | ||
| 36 | - } | ||
| 37 | - }).catchError((Object error, StackTrace stackTrace) { | ||
| 38 | - if (mounted) { | ||
| 39 | - ScaffoldMessenger.of(context).showSnackBar( | ||
| 40 | - SnackBar( | ||
| 41 | - content: Text('Something went wrong! $error'), | ||
| 42 | - backgroundColor: Colors.red, | ||
| 43 | - ), | ||
| 44 | - ); | ||
| 45 | - } | ||
| 46 | - }); | ||
| 47 | - } | ||
| 48 | - | ||
| 49 | - void onBarcodeDetect(BarcodeCapture barcodeCapture) { | ||
| 50 | - final barcode = barcodeCapture.barcodes.last; | ||
| 51 | - setState(() { | ||
| 52 | - overlayText = barcodeCapture.barcodes.last.displayValue ?? | ||
| 53 | - barcode.rawValue ?? | ||
| 54 | - 'Barcode has no displayable value'; | ||
| 55 | - }); | 18 | + void initState() { |
| 19 | + super.initState(); | ||
| 20 | + controller.start(); | ||
| 56 | } | 21 | } |
| 57 | 22 | ||
| 58 | @override | 23 | @override |
| @@ -64,119 +29,84 @@ class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> { | @@ -64,119 +29,84 @@ class _BarcodeScannerWithOverlayState extends State<BarcodeScannerWithOverlay> { | ||
| 64 | ); | 29 | ); |
| 65 | 30 | ||
| 66 | return Scaffold( | 31 | return Scaffold( |
| 32 | + backgroundColor: Colors.black, | ||
| 67 | appBar: AppBar( | 33 | appBar: AppBar( |
| 68 | title: const Text('Scanner with Overlay Example app'), | 34 | title: const Text('Scanner with Overlay Example app'), |
| 69 | ), | 35 | ), |
| 70 | - body: Center( | ||
| 71 | - child: Column( | ||
| 72 | - mainAxisAlignment: MainAxisAlignment.center, | ||
| 73 | - children: <Widget>[ | ||
| 74 | - Expanded( | ||
| 75 | - child: camStarted | ||
| 76 | - ? Stack( | 36 | + body: Stack( |
| 77 | fit: StackFit.expand, | 37 | fit: StackFit.expand, |
| 78 | children: [ | 38 | children: [ |
| 79 | Center( | 39 | Center( |
| 80 | child: MobileScanner( | 40 | child: MobileScanner( |
| 81 | fit: BoxFit.contain, | 41 | fit: BoxFit.contain, |
| 82 | - onDetect: onBarcodeDetect, | ||
| 83 | - overlay: Padding( | ||
| 84 | - padding: const EdgeInsets.all(16.0), | ||
| 85 | - child: Align( | ||
| 86 | - alignment: Alignment.bottomCenter, | ||
| 87 | - child: Opacity( | ||
| 88 | - opacity: 0.7, | ||
| 89 | - child: Text( | ||
| 90 | - overlayText, | ||
| 91 | - style: const TextStyle( | ||
| 92 | - backgroundColor: Colors.black26, | ||
| 93 | - color: Colors.white, | ||
| 94 | - fontWeight: FontWeight.bold, | ||
| 95 | - fontSize: 24, | ||
| 96 | - overflow: TextOverflow.ellipsis, | ||
| 97 | - ), | ||
| 98 | - maxLines: 1, | ||
| 99 | - ), | ||
| 100 | - ), | ||
| 101 | - ), | ||
| 102 | - ), | ||
| 103 | controller: controller, | 42 | controller: controller, |
| 104 | scanWindow: scanWindow, | 43 | scanWindow: scanWindow, |
| 105 | errorBuilder: (context, error, child) { | 44 | errorBuilder: (context, error, child) { |
| 106 | return ScannerErrorWidget(error: error); | 45 | return ScannerErrorWidget(error: error); |
| 107 | }, | 46 | }, |
| 108 | - ), | ||
| 109 | - ), | ||
| 110 | - CustomPaint( | ||
| 111 | - painter: ScannerOverlay(scanWindow), | ||
| 112 | - ), | ||
| 113 | - Padding( | 47 | + overlayBuilder: (context, constraints) { |
| 48 | + return Padding( | ||
| 114 | padding: const EdgeInsets.all(16.0), | 49 | padding: const EdgeInsets.all(16.0), |
| 115 | child: Align( | 50 | child: Align( |
| 116 | alignment: Alignment.bottomCenter, | 51 | alignment: Alignment.bottomCenter, |
| 117 | - child: Row( | ||
| 118 | - mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||
| 119 | - children: [ | ||
| 120 | - ValueListenableBuilder<TorchState>( | ||
| 121 | - valueListenable: controller.torchState, | ||
| 122 | - builder: (context, value, child) { | ||
| 123 | - final Color iconColor; | ||
| 124 | - | ||
| 125 | - switch (value) { | ||
| 126 | - case TorchState.off: | ||
| 127 | - iconColor = Colors.black; | ||
| 128 | - case TorchState.on: | ||
| 129 | - iconColor = Colors.yellow; | ||
| 130 | - } | ||
| 131 | - | ||
| 132 | - return IconButton( | ||
| 133 | - onPressed: () => controller.toggleTorch(), | ||
| 134 | - icon: Icon( | ||
| 135 | - Icons.flashlight_on, | ||
| 136 | - color: iconColor, | 52 | + child: ScannedBarcodeLabel(barcodes: controller.barcodes), |
| 137 | ), | 53 | ), |
| 138 | ); | 54 | ); |
| 139 | }, | 55 | }, |
| 140 | ), | 56 | ), |
| 141 | - IconButton( | ||
| 142 | - onPressed: () => controller.switchCamera(), | ||
| 143 | - icon: const Icon( | ||
| 144 | - Icons.cameraswitch_rounded, | ||
| 145 | - color: Colors.white, | ||
| 146 | ), | 57 | ), |
| 58 | + ValueListenableBuilder( | ||
| 59 | + valueListenable: controller, | ||
| 60 | + builder: (context, value, child) { | ||
| 61 | + if (!value.isInitialized || !value.isRunning || value.error != null) { | ||
| 62 | + return const SizedBox(); | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + return CustomPaint( | ||
| 66 | + painter: ScannerOverlay(scanWindow: scanWindow), | ||
| 67 | + ); | ||
| 68 | + }, | ||
| 147 | ), | 69 | ), |
| 70 | + Align( | ||
| 71 | + alignment: Alignment.bottomCenter, | ||
| 72 | + child: Padding( | ||
| 73 | + padding: const EdgeInsets.all(16.0), | ||
| 74 | + child: Row( | ||
| 75 | + mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||
| 76 | + children: [ | ||
| 77 | + ToggleFlashlightButton(controller: controller), | ||
| 78 | + SwitchCameraButton(controller: controller), | ||
| 148 | ], | 79 | ], |
| 149 | ), | 80 | ), |
| 150 | ), | 81 | ), |
| 151 | ), | 82 | ), |
| 152 | ], | 83 | ], |
| 153 | - ) | ||
| 154 | - : const Center( | ||
| 155 | - child: Text("Tap on Camera to activate QR Scanner"), | ||
| 156 | - ), | ||
| 157 | - ), | ||
| 158 | - ], | ||
| 159 | - ), | ||
| 160 | - ), | ||
| 161 | - floatingActionButton: camStarted | ||
| 162 | - ? null | ||
| 163 | - : FloatingActionButton( | ||
| 164 | - onPressed: startCamera, | ||
| 165 | - child: const Icon(Icons.camera_alt), | ||
| 166 | ), | 84 | ), |
| 167 | ); | 85 | ); |
| 168 | } | 86 | } |
| 87 | + | ||
| 88 | + @override | ||
| 89 | + Future<void> dispose() async { | ||
| 90 | + await controller.dispose(); | ||
| 91 | + super.dispose(); | ||
| 92 | + } | ||
| 169 | } | 93 | } |
| 170 | 94 | ||
| 171 | class ScannerOverlay extends CustomPainter { | 95 | class ScannerOverlay extends CustomPainter { |
| 172 | - ScannerOverlay(this.scanWindow); | 96 | + const ScannerOverlay({ |
| 97 | + required this.scanWindow, | ||
| 98 | + this.borderRadius = 12.0, | ||
| 99 | + }); | ||
| 173 | 100 | ||
| 174 | final Rect scanWindow; | 101 | final Rect scanWindow; |
| 175 | - final double borderRadius = 12.0; | 102 | + final double borderRadius; |
| 176 | 103 | ||
| 177 | @override | 104 | @override |
| 178 | void paint(Canvas canvas, Size size) { | 105 | void paint(Canvas canvas, Size size) { |
| 106 | + // TODO: use `Offset.zero & size` instead of Rect.largest | ||
| 107 | + // we need to pass the size to the custom paint widget | ||
| 179 | final backgroundPath = Path()..addRect(Rect.largest); | 108 | final backgroundPath = Path()..addRect(Rect.largest); |
| 109 | + | ||
| 180 | final cutoutPath = Path() | 110 | final cutoutPath = Path() |
| 181 | ..addRRect( | 111 | ..addRRect( |
| 182 | RRect.fromRectAndCorners( | 112 | RRect.fromRectAndCorners( |
| @@ -199,14 +129,11 @@ class ScannerOverlay extends CustomPainter { | @@ -199,14 +129,11 @@ class ScannerOverlay extends CustomPainter { | ||
| 199 | cutoutPath, | 129 | cutoutPath, |
| 200 | ); | 130 | ); |
| 201 | 131 | ||
| 202 | - // Create a Paint object for the white border | ||
| 203 | final borderPaint = Paint() | 132 | final borderPaint = Paint() |
| 204 | ..color = Colors.white | 133 | ..color = Colors.white |
| 205 | ..style = PaintingStyle.stroke | 134 | ..style = PaintingStyle.stroke |
| 206 | - ..strokeWidth = 4.0; // Adjust the border width as needed | 135 | + ..strokeWidth = 4.0; |
| 207 | 136 | ||
| 208 | - // Calculate the border rectangle with rounded corners | ||
| 209 | -// Adjust the radius as needed | ||
| 210 | final borderRect = RRect.fromRectAndCorners( | 137 | final borderRect = RRect.fromRectAndCorners( |
| 211 | scanWindow, | 138 | scanWindow, |
| 212 | topLeft: Radius.circular(borderRadius), | 139 | topLeft: Radius.circular(borderRadius), |
| @@ -215,13 +142,15 @@ class ScannerOverlay extends CustomPainter { | @@ -215,13 +142,15 @@ class ScannerOverlay extends CustomPainter { | ||
| 215 | bottomRight: Radius.circular(borderRadius), | 142 | bottomRight: Radius.circular(borderRadius), |
| 216 | ); | 143 | ); |
| 217 | 144 | ||
| 218 | - // Draw the white border | 145 | + // First, draw the background, |
| 146 | + // with a cutout area that is a bit larger than the scan window. | ||
| 147 | + // Finally, draw the scan window itself. | ||
| 219 | canvas.drawPath(backgroundWithCutout, backgroundPaint); | 148 | canvas.drawPath(backgroundWithCutout, backgroundPaint); |
| 220 | canvas.drawRRect(borderRect, borderPaint); | 149 | canvas.drawRRect(borderRect, borderPaint); |
| 221 | } | 150 | } |
| 222 | 151 | ||
| 223 | @override | 152 | @override |
| 224 | - bool shouldRepaint(covariant CustomPainter oldDelegate) { | ||
| 225 | - return false; | 153 | + bool shouldRepaint(ScannerOverlay oldDelegate) { |
| 154 | + return scanWindow != oldDelegate.scanWindow || borderRadius != oldDelegate.borderRadius; | ||
| 226 | } | 155 | } |
| 227 | } | 156 | } |
-
Please register or login to post a comment