Reputation: 108
I want to make a QR Code Scanner in Flutter that has this type of border that helps users to focus on the scanner, has a border color (blue in this case) and a white background color and is running inside a container widget:-
How can I create such a QR Code Scanner in Flutter, so far, I have used flutter_barcode_scanner package and it looks like this:-
This one is pretty simple but it is a little tough to focus on the square scanner because of no border and it doesn't look that good. Here's how I implemented my QR Code Scanner:-
Future<void> scanQRCode() async {
try {
final qrcode = await FlutterBarcodeScanner.scanBarcode(
'#00B9F1',
'Cancel',
true,
ScanMode.QR,
);
if (!mounted) return;
setState(() {
this.qrcode = qrcode;
showDialog(
context: context,
builder: (_) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Container(
height: 300,
width: 250,
child: Text(qrcode),
),
);
},
);
});
} catch (error) {
print(error);
}
}
Upvotes: 5
Views: 16922
Reputation: 448
In case it helps, I have extracted from the qr_code_scanner QR scan library (now deprecated, use mobile_scanner instead) the overlay it offers. This way you can apply the overlay to whatever feature you want.
Desired Class
Stack(
children: [
...,
Positioned.fill(
child: Container(
decoration: ShapeDecoration(
shape: QrScannerOverlayShape(
borderColor: Colors.white,
borderRadius: 10,
borderLength: 20,
borderWidth: 5,
cutOutSize: scanArea,
),
),
),
),
...,
],
),
QrScannerOverlayShape Class
class QrScannerOverlayShape extends ShapeBorder {
QrScannerOverlayShape({
this.borderColor = Colors.red,
this.borderWidth = 3.0,
this.overlayColor = const Color.fromRGBO(0, 0, 0, 80),
this.borderRadius = 0,
this.borderLength = 40,
double? cutOutSize,
double? cutOutWidth,
double? cutOutHeight,
this.cutOutBottomOffset = 0,
}) : cutOutWidth = cutOutWidth ?? cutOutSize ?? 250,
cutOutHeight = cutOutHeight ?? cutOutSize ?? 250 {
assert(
borderLength <=
min(this.cutOutWidth, this.cutOutHeight) / 2 + borderWidth * 2,
"Border can't be larger than ${min(this.cutOutWidth, this.cutOutHeight) / 2 + borderWidth * 2}",
);
assert(
(cutOutWidth == null && cutOutHeight == null) ||
(cutOutSize == null && cutOutWidth != null && cutOutHeight != null),
'Use only cutOutWidth and cutOutHeight or only cutOutSize');
}
final Color borderColor;
final double borderWidth;
final Color overlayColor;
final double borderRadius;
final double borderLength;
final double cutOutWidth;
final double cutOutHeight;
final double cutOutBottomOffset;
@override
EdgeInsetsGeometry get dimensions => const EdgeInsets.all(10);
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
return Path()
..fillType = PathFillType.evenOdd
..addPath(getOuterPath(rect), Offset.zero);
}
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
Path getLeftTopPath(Rect rect) {
return Path()
..moveTo(rect.left, rect.bottom)
..lineTo(rect.left, rect.top)
..lineTo(rect.right, rect.top);
}
return getLeftTopPath(rect)
..lineTo(
rect.right,
rect.bottom,
)
..lineTo(
rect.left,
rect.bottom,
)
..lineTo(
rect.left,
rect.top,
);
}
@override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
final width = rect.width;
final borderWidthSize = width / 2;
final height = rect.height;
final borderOffset = borderWidth / 2;
final mBorderLength =
borderLength > min(cutOutHeight, cutOutHeight) / 2 + borderWidth * 2
? borderWidthSize / 2
: borderLength;
final mCutOutWidth =
cutOutWidth < width ? cutOutWidth : width - borderOffset;
final mCutOutHeight =
cutOutHeight < height ? cutOutHeight : height - borderOffset;
final backgroundPaint = Paint()
..color = overlayColor
..style = PaintingStyle.fill;
final borderPaint = Paint()
..color = borderColor
..style = PaintingStyle.stroke
..strokeWidth = borderWidth;
final boxPaint = Paint()
..color = borderColor
..style = PaintingStyle.fill
..blendMode = BlendMode.dstOut;
final cutOutRect = Rect.fromLTWH(
rect.left + width / 2 - mCutOutWidth / 2 + borderOffset,
-cutOutBottomOffset +
rect.top +
height / 2 -
mCutOutHeight / 2 +
borderOffset,
mCutOutWidth - borderOffset * 2,
mCutOutHeight - borderOffset * 2,
);
canvas
..saveLayer(
rect,
backgroundPaint,
)
..drawRect(
rect,
backgroundPaint,
)
// Draw top right corner
..drawRRect(
RRect.fromLTRBAndCorners(
cutOutRect.right - mBorderLength,
cutOutRect.top,
cutOutRect.right,
cutOutRect.top + mBorderLength,
topRight: Radius.circular(borderRadius),
),
borderPaint,
)
// Draw top left corner
..drawRRect(
RRect.fromLTRBAndCorners(
cutOutRect.left,
cutOutRect.top,
cutOutRect.left + mBorderLength,
cutOutRect.top + mBorderLength,
topLeft: Radius.circular(borderRadius),
),
borderPaint,
)
// Draw bottom right corner
..drawRRect(
RRect.fromLTRBAndCorners(
cutOutRect.right - mBorderLength,
cutOutRect.bottom - mBorderLength,
cutOutRect.right,
cutOutRect.bottom,
bottomRight: Radius.circular(borderRadius),
),
borderPaint,
)
// Draw bottom left corner
..drawRRect(
RRect.fromLTRBAndCorners(
cutOutRect.left,
cutOutRect.bottom - mBorderLength,
cutOutRect.left + mBorderLength,
cutOutRect.bottom,
bottomLeft: Radius.circular(borderRadius),
),
borderPaint,
)
..drawRRect(
RRect.fromRectAndRadius(
cutOutRect,
Radius.circular(borderRadius),
),
boxPaint,
)
..restore();
}
@override
ShapeBorder scale(double t) {
return QrScannerOverlayShape(
borderColor: borderColor,
borderWidth: borderWidth,
overlayColor: overlayColor,
);
}
}
How it looks (red color chosen)
Upvotes: 10
Reputation: 3067
You can use this qr_code_scanner to suit your needs
you can use it as a Widget
class _QRViewExampleState extends State<QRViewExample> {
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
Barcode result;
QRViewController controller;
// In order to get hot reload to work we need to pause the camera if the platform
// is android, or resume the camera if the platform is iOS.
@override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller.pauseCamera();
} else if (Platform.isIOS) {
controller.resumeCamera();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
flex: 5,
child: QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
),
),
Expanded(
flex: 1,
child: Center(
child: (result != null)
? Text(
'Barcode Type: ${describeEnum(result.format)} Data: ${result.code}')
: Text('Scan a code'),
),
)
],
),
);
}
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
setState(() {
result = scanData;
});
});
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
}
Upvotes: 3