zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

Flutter 画笔绘制二维码扫描框

flutter 绘制 扫描 二维码 画笔
2023-09-11 14:18:54 时间


一、CustomPaint介绍

1. CustomPaint

CustomPaint 是一个 Widget 类,提供了绘制时所需的画布。

  const CustomPaint({
    Key? key,
    // CustomPainter  绘制背景
    this.painter,
    // CustomPainter  绘制前景
    this.foregroundPainter,
    // 尺寸,默认为0,如果有子widget则为子widget的大小
    this.size = Size.zero,
    this.isComplex = false,
    this.willChange = false,
    Widget? child,
  })

2. CustomPainter

CustomPainter 一个用于实现CustomPaint绘制的接口,需要实现此接口来进行自定义绘制。

class _ScanFramePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // 绘图的主要实现
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // 返回true 则会重新绘制,执行 paint函数,返回false 则不会重新绘制
    return true;
  }

}

3. Paint & Canvas

Paint 画笔
Canvas 画布
两者组合可绘制各种图形。

4. 示例(绘制文本背景)

1. 自定义 CustomPainter

class TextPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()..color = Colors.blue;
    // size是widget的尺寸,即CustomPaint的尺寸
    canvas.drawRect(Rect.fromLTRB(0, 0, size.width, size.height), paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

2. 显示CustomPaint以及Text

class CustomTextWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: CustomPaint(
          painter: TextPainter(),
          child: Text('Flutter 画笔绘制二维码扫描框'),
        ),
      ),
    );
  }
}

3. 最终效果
在这里插入图片描述

二、计算扫描框四个点坐标

先看效果图,然后根据 xy轴以及扫描框的大小计算坐标。

在这里插入图片描述
坐标计算代码如下,看注释。

class _ScanFramePainter extends CustomPainter {
  //默认定义扫描框为 260边长的正方形
  final Size frameSize = Size.square(260.0);

  @override
  void paint(Canvas canvas, Size size) {
    // 按扫描框居中来计算,全屏尺寸与扫描框尺寸的差集 除以 2 就是扫描框的位置
    Offset diff = size - frameSize;
    double leftTopX = diff.dx / 2;
    double leftTopY = diff.dy / 2;
    //根据左上角的坐标和扫描框的大小可得知扫描框矩形
    var rect =
        Rect.fromLTWH(leftTopX, leftTopY, frameSize.width, frameSize.height);
    // 4个点的坐标
    Offset leftTop = rect.topLeft;
    Offset leftBottom = rect.bottomLeft;
    Offset rightTop = rect.topRight;
    Offset rightBottom = rect.bottomRight;

    //定义画笔
    Paint paint = Paint()
      ..color = Colors.blue  //颜色
      ..strokeWidth = 4.0   //画笔线条宽度
      ..style = PaintingStyle.stroke; // 画笔的模式,填充还是只绘制边框
    
    //绘制正方形
    canvas.drawRect(rect, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

调整 CustomPaint 的尺寸后查看效果

  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomPaint(
        painter: _ScanFramePainter(),
        child: Container(),//全屏
      ),
    );
  }

绘制方框只是为了查看扫描框位置是否正确

在这里插入图片描述

三、绘制透明灰扫描背景

除扫描框以外,分成四个矩形组成在一起就是扫描框背景,在paint函数中绘制扫描背景

    //绘制罩层
    //画笔
    Paint paint = Paint()
      ..color = Color(0x40cccccc) //透明灰
      ..style = PaintingStyle.fill; // 画笔的模式,填充
    //左侧矩形
    canvas.drawRect(Rect.fromLTRB(0, 0, leftTopX, size.height), paint);
    //右侧矩形
    canvas.drawRect(
      Rect.fromLTRB(rightTop.dx, 0, size.width, size.height),
      paint,
    );
    //中上矩形
    canvas.drawRect(Rect.fromLTRB(leftTopX, 0, rightTop.dx, leftTopY), paint);
    //中下矩形
    canvas.drawRect(
      Rect.fromLTRB(leftBottom.dx, leftBottom.dy, rightBottom.dx, size.height),
      paint,
    );

四、绘制扫描框

定义边角的长度,和扫描线的移动高度

  final double cornerLength = 20.0;

在paint函数中继续绘制扫描框

  // 重新设置画笔
    paint
      ..color = Colors.blue
      ..strokeWidth = 4.0
      ..strokeCap = StrokeCap.square  // 解决因为线宽导致交界处不是直角的问题
      ..style = PaintingStyle.stroke;

    // 横向线条的坐标偏移
    Offset horizontalOffset = Offset(cornerLength, 0);
    // 纵向线条的坐标偏移
    Offset verticalOffset = Offset(0, cornerLength);
    // 左上角
    canvas.drawLine(leftTop, leftTop + horizontalOffset, paint);
    canvas.drawLine(leftTop, leftTop + verticalOffset, paint);
    // 左下角
    canvas.drawLine(leftBottom, leftBottom + horizontalOffset, paint);
    canvas.drawLine(leftBottom, leftBottom - verticalOffset, paint);
    // 右上角
    canvas.drawLine(rightTop, rightTop - horizontalOffset, paint);
    canvas.drawLine(rightTop, rightTop + verticalOffset, paint);
    // 右下角
    canvas.drawLine(rightBottom, rightBottom - horizontalOffset, paint);
    canvas.drawLine(rightBottom, rightBottom - verticalOffset, paint);

此时查看效果
在这里插入图片描述

五、绘制扫描线并添加动画

1. 绘制扫描线

首先要确定扫描线的Y坐标,由外部传值

  _ScanFramePainter({this.lineMoveValue}) : assert(lineMoveValue != null);

  // 百分比值,0 ~ 1,然后计算Y坐标
  final double lineMoveValue;

在paint函数中继续绘制扫描线

    //修改画笔线条宽度
    paint.strokeWidth = 2;
    // 扫描线的移动值
    var lineY = leftTopY + frameSize.height * lineMoveValue;
    // 10 为线条与方框之间的间距,绘制扫描线
    canvas.drawLine(
      Offset(leftTopX + 10.0, lineY),
      Offset(rightTop.dx - 10.0, lineY),
      paint,
    );

2. 使扫描线移动

使用 Animation 实现线条移动

class _ScanFrameState extends State<ScanFrame> with TickerProviderStateMixin {
  Animation<double> _animation;
  AnimationController _controller;

  //起始之间的线性插值器 从 0.05 到 0.95 百分比。
  final Tween<double> _rotationTween = Tween(begin: 0.05, end: 0.95);

  @override
  void initState() {
    super.initState();

    _controller = AnimationController(
      vsync: this,     //实现 TickerProviderStateMixin
      duration: Duration(seconds: 3), //动画时间 3s
    );

    _animation = _rotationTween.animate(_controller)
      ..addListener(() => setState(() {}))
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          _controller.repeat();
        } else if (status == AnimationStatus.dismissed) {
          _controller.forward();
        }
      });

    _controller.repeat();
  }

  @override
  void dispose() {
    // 释放动画资源
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: _ScanFramePainter(lineMoveValue: _animation.value),
      child: Container(),
    );
  }
}

六、Flutter 二维码扫描效果图

在这里插入图片描述