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: