import 'package:flutter/material.dart'; class ScannerOverlay extends ShapeBorder { const ScannerOverlay({ this.borderColor = Colors.red, this.borderWidth = 3.0, this.overlayColor = const Color.fromRGBO(0, 0, 0, 40), this.borderRadius = 0, this.borderLength = 40, this.cutOutSize = 250, }) : assert(borderLength <= cutOutSize / 2 + borderWidth * 2, "Border can't be larger than ${cutOutSize / 2 + borderWidth * 2}"); final Color borderColor; final double borderWidth; final Color overlayColor; final double borderRadius; final double borderLength; final double cutOutSize; @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 double width = rect.width; final double borderWidthSize = width / 2; final double height = rect.height; final double borderOffset = borderWidth / 2; final double newBorderLength = borderLength > cutOutSize / 2 + borderWidth * 2 ? borderWidthSize / 2 : borderLength; final double newCutOutSize = cutOutSize < width ? cutOutSize : width - borderOffset; final Paint backgroundPaint = Paint() ..color = overlayColor ..style = PaintingStyle.fill; final Paint borderPaint = Paint() ..color = borderColor ..style = PaintingStyle.stroke ..strokeWidth = borderWidth; final Paint boxPaint = Paint() ..color = borderColor ..style = PaintingStyle.fill ..blendMode = BlendMode.dstOut; final Rect cutOutRect = Rect.fromLTWH( rect.left + width / 2 - newCutOutSize / 2 + borderOffset, rect.top + height / 2 - newCutOutSize / 2 + borderOffset, newCutOutSize - borderOffset * 2, newCutOutSize - borderOffset * 2, ); canvas ..saveLayer( rect, backgroundPaint, ) ..drawRect( rect, backgroundPaint, ) // Draw top right corner ..drawRRect( RRect.fromLTRBAndCorners( cutOutRect.right - newBorderLength, cutOutRect.top, cutOutRect.right, cutOutRect.top + newBorderLength, topRight: Radius.circular(borderRadius), ), borderPaint, ) // Draw top left corner ..drawRRect( RRect.fromLTRBAndCorners( cutOutRect.left, cutOutRect.top, cutOutRect.left + newBorderLength, cutOutRect.top + newBorderLength, topLeft: Radius.circular(borderRadius), ), borderPaint, ) // Draw bottom right corner ..drawRRect( RRect.fromLTRBAndCorners( cutOutRect.right - newBorderLength, cutOutRect.bottom - newBorderLength, cutOutRect.right, cutOutRect.bottom, bottomRight: Radius.circular(borderRadius), ), borderPaint, ) // Draw bottom left corner ..drawRRect( RRect.fromLTRBAndCorners( cutOutRect.left, cutOutRect.bottom - newBorderLength, cutOutRect.left + newBorderLength, cutOutRect.bottom, bottomLeft: Radius.circular(borderRadius), ), borderPaint, ) ..drawRRect( RRect.fromRectAndRadius( cutOutRect, Radius.circular(borderRadius), ), boxPaint, ) ..restore(); } @override ShapeBorder scale(double t) { return ScannerOverlay( borderColor: borderColor, borderWidth: borderWidth, overlayColor: overlayColor, ); } }