Flutter: create a QR Code Scanner with overlay effect
You must employ barcodes and QR codes if you want people to use your app to rapidly recognise data visually. They have been used for a long time to reliably and accurately optically identify data fragments.
There are still a lot of applications for barcodes nowadays. One of the most prevalent applications we've seen lately is at restaurants, where patrons may scan QR codes to select specific products from a menu.
Creating our Flutter project
flutter create qrcodescanner
Once the command completes, we can add mobile_scanner
to our project, which we can accomplish by writing the following code into the command line:
flutter pub get mobile_scanner
iOS platform configuration
Because we’re accessing a phone’s camera, the Apple App Store will see that we are making this request to access the camera and will want to know why we are making that request.
And adding the following into the Info.plist
file:
<key>io.flutter.embedded_views_preview</key> <true/> <key>NSCameraUsageDescription</key> <string>This app needs camera access to scan QR codes</string>
Now, when the user attempts to scan a QR code in the app with their camera, they will see a warning that lets them accept or reject the app from using their camera.
Open main.dart and Replace the Flutter default counter application in main.dart
with your own stateful widget.
You should have something like this:
import 'package:flutter/material.dart'; import 'package:qrcodescanner/screens/scanner/scanner.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scanner() ); } }
Before Create Scanner
stateful widget create scanner.dart file in home folder, then inside of it with the following code:
import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:qrcodescanner/screens/scanner/QRscannerOverlay.dart'; import 'package:qrcodescanner/screens/scanner/foundScreen.dart'; class Scanner extends StatefulWidget { const Scanner({Key? key}) : super(key: key); @override State<Scanner> createState() => _ScannerState(); } class _ScannerState extends State<Scanner> { MobileScannerController cameraController = MobileScannerController(); bool _screenOpened = false; @override void initState() { // TODO: implement initState this._screenWasClosed(); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black.withOpacity(0.5), appBar: AppBar( backgroundColor: Colors.pinkAccent, title: Text("Scanner", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), elevation: 0.0, ), body: Stack( children: [ MobileScanner( allowDuplicates: false, controller: cameraController, onDetect: _foundBarcode, ), QRScannerOverlay(overlayColour: Colors.black.withOpacity(0.5)) ], ) ); } void _foundBarcode(Barcode barcode, MobileScannerArguments? args){ print(barcode); if(!_screenOpened){ final String code = barcode.rawValue ?? "___"; _screenOpened = false; //here push navigation result page Navigator.push(context, MaterialPageRoute(builder: (context)=> FoundScreen(value: code, screenClose: _screenWasClosed))).then((value) => print(value)); // builder: builder) => FoundScreen(value: code, screenClose: _screenWasClosed)) } } void _screenWasClosed(){ _screenOpened = false; } }
Before Create QRScannerOverlay
stateful widget create qrscanneroverlay.dart file in home folder, then inside of it with the following code:
import 'package:flutter/material.dart'; class QRScannerOverlay extends StatelessWidget { const QRScannerOverlay({Key? key, required this.overlayColour}) : super(key: key); final Color overlayColour; @override Widget build(BuildContext context) { // // Changing the size of scanner cutout dependent on the device size. double scanArea = (MediaQuery.of(context).size.width < 400 || MediaQuery.of(context).size.height < 400) ? 200.0 : 330.0; return Stack(children: [ ColorFiltered( colorFilter: ColorFilter.mode( overlayColour, BlendMode.srcOut), // This one will create the magic child: Stack( children: [ Container( decoration: const BoxDecoration( color: Colors.red, backgroundBlendMode: BlendMode .dstOut), // This one will handle background + difference out ), Align( alignment: Alignment.center, child: Container( height: scanArea, width: scanArea, decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(20), ), ), ), ], ), ), Align( alignment: Alignment.center, child: CustomPaint( foregroundPainter: BorderPainter(), child: SizedBox( width: scanArea + 25, height: scanArea + 25, ), ), ), ]); } } // Creates the white borders class BorderPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { const width = 4.0; const radius = 20.0; const tRadius = 3 * radius; final rect = Rect.fromLTWH( width, width, size.width - 2 * width, size.height - 2 * width, ); final rrect = RRect.fromRectAndRadius(rect, const Radius.circular(radius)); const clippingRect0 = Rect.fromLTWH( 0, 0, tRadius, tRadius, ); final clippingRect1 = Rect.fromLTWH( size.width - tRadius, 0, tRadius, tRadius, ); final clippingRect2 = Rect.fromLTWH( 0, size.height - tRadius, tRadius, tRadius, ); final clippingRect3 = Rect.fromLTWH( size.width - tRadius, size.height - tRadius, tRadius, tRadius, ); final path = Path() ..addRect(clippingRect0) ..addRect(clippingRect1) ..addRect(clippingRect2) ..addRect(clippingRect3); canvas.clipPath(path); canvas.drawRRect( rrect, Paint() ..color = Colors.white ..style = PaintingStyle.stroke ..strokeWidth = width, ); } @override bool shouldRepaint(CustomPainter oldDelegate) { return false; } } class BarReaderSize { static double width = 200; static double height = 200; } class OverlayWithHolePainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint()..color = Colors.black54; canvas.drawPath( Path.combine( PathOperation.difference, Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height)), Path() ..addOval(Rect.fromCircle( center: Offset(size.width - 44, size.height - 44), radius: 40)) ..close(), ), paint); } @override bool shouldRepaint(CustomPainter oldDelegate) { return false; } } @override bool shouldRepaint(CustomPainter oldDelegate) { return false; }
Before Create foundScreen
stateful widget create foundscreen.dart file in home folder, then inside of it with the following code:
import 'package:flutter/material.dart'; class FoundScreen extends StatefulWidget { final String value; final Function() screenClose; const FoundScreen({Key? key, required this.value, required this.screenClose}) : super(key: key); @override State<FoundScreen> createState() => _FoundScreenState(); } class _FoundScreenState extends State<FoundScreen> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: Builder( builder: (BuildContext context){ return RotatedBox(quarterTurns: 0,child: IconButton( icon: Icon(Icons.arrow_back_rounded, color: Colors.white), onPressed: () => Navigator.pop(context, false), ),); }, ), title: Text("Result", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)), backgroundColor: Colors.pinkAccent, ), body: Center( child: Padding( padding: EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text("Result: ", style: TextStyle(fontSize: 20),), SizedBox(height: 20), Text(widget.value, style: TextStyle(fontSize: 16)) ], ), ), ), ); } }
Output: