import 'package:flutter/material.dart'; class ScanImageView extends StatefulWidget { final Widget child; final Color scanColor; final Rect scanRect; const ScanImageView({Key key, this.child, this.scanRect, this.scanColor}) : super(key: key); @override _ScanImageViewState createState() => _ScanImageViewState(); } class _ScanImageViewState extends State with TickerProviderStateMixin { AnimationController controller; @override void initState() { super.initState(); controller = AnimationController( vsync: this, duration: Duration(milliseconds: 1000)); Future.delayed(Duration(milliseconds: 600), () { controller.repeat(reverse: true); }); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: controller, builder: (BuildContext context, Widget child) => CustomPaint( foregroundPainter: _ScanPainter( controller.value, widget.scanColor, widget.scanRect), child: widget.child, willChange: false, )); } } class _ScanPainter extends CustomPainter { final double value; final Color scanColor; final Rect scanRect; _ScanPainter(this.value, this.scanColor, this.scanRect); Paint _paint; @override void paint(Canvas canvas, Size size) { if (_paint == null) { initPaint(); } double borderOffset = 3; double _borderLength = 20; final cutInnerRect = Rect.fromLTRB( scanRect.left + borderOffset / 2, scanRect.top + borderOffset / 2, scanRect.right - borderOffset / 2, scanRect.bottom - borderOffset / 2); final Path topLeft = Path() ..moveTo(cutInnerRect.left, cutInnerRect.top + _borderLength) ..lineTo(cutInnerRect.left, cutInnerRect.top) ..lineTo(cutInnerRect.left + _borderLength, cutInnerRect.top); final Path bottomLeft = Path() ..moveTo(cutInnerRect.left, cutInnerRect.bottom - _borderLength) ..lineTo(cutInnerRect.left, cutInnerRect.bottom) ..lineTo(cutInnerRect.left + _borderLength, cutInnerRect.bottom); final Path topRight = Path() ..moveTo(cutInnerRect.right, cutInnerRect.top + _borderLength) ..lineTo(cutInnerRect.right, cutInnerRect.top) ..lineTo(cutInnerRect.right - _borderLength, cutInnerRect.top); final Path bottomRight = Path() ..moveTo(cutInnerRect.right, cutInnerRect.bottom - _borderLength) ..lineTo(cutInnerRect.right, cutInnerRect.bottom) ..lineTo(cutInnerRect.right - _borderLength, cutInnerRect.bottom); final borderPaint = Paint() ..color = scanColor ..style = PaintingStyle.stroke ..strokeWidth = borderOffset; _paint.color = Colors.blueAccent; _paint.strokeWidth = 1; final scanLineRect = Rect.fromLTWH( scanRect.left + 10, scanRect.top + 10 + (value * (scanRect.height - 20)), scanRect.width - 20, 1); _paint.shader = LinearGradient(colors: [ Colors.lightBlue, Colors.blue, Colors.lightBlue, ], stops: [ 0.0, 0.5, 1, ]).createShader(scanLineRect); final backgroundPaint = Paint() ..color = const Color.fromRGBO(0, 0, 0, 50) ..style = PaintingStyle.fill; final boxPaint = Paint() ..color = scanColor ..style = PaintingStyle.fill ..blendMode = BlendMode.dstOut; canvas.saveLayer( Rect.fromLTWH(0, 0, size.width, size.height), backgroundPaint, ); canvas ..drawRect( Rect.fromLTWH(0, 0, size.width, size.height), backgroundPaint, ) ..drawRect(scanRect, boxPaint) ..drawPath(topLeft, borderPaint) ..drawPath(bottomLeft, borderPaint) ..drawPath(topRight, borderPaint) ..drawPath(bottomRight, borderPaint) ..drawRect(scanLineRect, _paint) ..restore(); } @override bool shouldRepaint(CustomPainter oldDelegate) { return true; } void initPaint() { _paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1 ..isAntiAlias = true ..strokeCap = StrokeCap.round ..strokeJoin = StrokeJoin.round; } }