Flutter 之自定义view-横向进度条

324 阅读3分钟

自定义view-横向进度条

image.png

写作目的:QQ群有个兄弟在问这个控件如何实现,刚好前段时间学了自定义,现学现用写了个demo,也方便兄弟们可以学习一波自定义。这用到了绘制矩形,绘制圆形,手势。废话不多说。开干!

1.绘制矩形,并且展示圆弧角度

///设置画笔,颜色,风格
final backgroundPaint = Paint()
  ..color = color2
  ..style = PaintingStyle.fill;
canvas.drawRRect(
  RRect.fromRectAndRadius(
    Rect.fromLTWH(0, 0, size.width, size.height),
    Radius.circular(size.height / 2),
  ),
  backgroundPaint,
);
  1. 继承CustomPainter,在paint 方法内设置第一个背景的画笔,颜色,风格。PaintingStyle.fill表示填充。
  2. 可以使用drawRRect方法来绘制圆角矩形(RRect), 参数说明:
  • rrect: 要绘制的圆角矩形对象,使用RRect类创建。
  • paint: 绘制的样式和属性,使用Paint类创建。
  • 然后使用 RRect.fromRectAndRadius 方法创建了一个圆角矩形 rrect
  • Rect.fromLTWH是创建矩形。

2.绘制第二个已完成的矩形,并且展示圆弧角度

final background2Paint = Paint()
  ..color = color
  ..style = PaintingStyle.fill;
canvas.drawRRect(
  RRect.fromRectAndRadius(
    Rect.fromLTWH(0, 0, size.width * progress, size.height),
    Radius.circular(size.height / 2),
  ),
  background2Paint,
);

方法基本一样,只不过矩形区域的Rect.fromLTWH(0, 0, size.width * progress, size.height)第三个参数是动态获取的,progess表示总进度比例。

3.绘制进度条上的圆圈

final strokeWidth = 5.0;
final strokePaint = Paint()
  ..color = color
  ..strokeWidth = strokeWidth;
final centerX = size.width * progress;
final centerY = size.height / 2;
final radius = size.height / 2;
canvas.drawCircle(
  Offset(centerX, centerY),
  radius,
  strokePaint..style = PaintingStyle.stroke,
);
canvas.drawCircle(
  Offset(centerX, centerY),
  radius,
  strokePaint
    ..style = PaintingStyle.fill
    ..color = color2,
);

我实现的方式直接绘制了2个圆圈,内圆和外圆。

  1. 先初始化画笔的宽度
  2. 设置圆圈的半径,中心点的x,y点。
  3. 使用drawCircle方法绘制圆弧
  4. 分别设置 PaintingStyle.strokePaintingStyle.fill

到目前为止 绘制的核心代码已经完毕。

如果有手势控制需求的。可以直接加上手势方法GestureDetector,我们只需要监听横向事件的话增加onHorizontalDragUpdate。方法内部需要动态的修改进度值,所以进度值需要做一些修改。这时候就可以讲一讲 CustomPainter与监听器。

CustomPainter 与监听器

这一点非常重要,我是在看CupertinoActivityIndicator组件源码时发现的这一点。因为 CustomPainter 的本身是一个Listenable 子类,可以传入一个Listenable对象, 这个对象进行更新时,会触发通知让 CustomPainter 进行重绘。就不需要使用组件状态的 setState 来完成画布的刷新。这点在 CustomPainter 的源码中也有明确指出:

image-20201109184913000

最高效地触发画板重绘的方式是: 

[1]. 继承自 CustomPainter,在构造函数中对父类 repaint属性 进行赋值,repaint是一个可监听对象,当对象变化时会触发画布的重绘。 [2]. 继承自 Listenable 实现 CustomPainter,让该类自己执行对自己的更新。

经过修改之后的源码:

import 'package:flutter/material.dart';

class CustomProgressBar extends StatefulWidget {
  final double progress;
  final Color color;
  final Color color2;
  final bool enableGesture;
  final double width;
  final double height;

  CustomProgressBar({
    required this.progress,
    required this.color,
    required this.color2,
    this.enableGesture = false,
    this.width = 200,
    this.height = 20,
  });

  @override
  _CustomProgressBarState createState() => _CustomProgressBarState();
}

class _CustomProgressBarState extends State<CustomProgressBar> {
  ValueNotifier<double> _currentProgress = ValueNotifier(0.0);
  double progress = 0;

  @override
  void initState() {
    super.initState();
    _currentProgress.value = widget.progress;
  }

  void _updateProgress(DragUpdateDetails details) {
    progress += details.delta.dx / context.size!.width;
    progress = progress.clamp(0.0, 1.0);
    _currentProgress.value = progress;
  }

  @override
  Widget build(BuildContext context) {
    return
      Container(
        height: widget.height,
        width: widget.width,
        child: widget.enableGesture
            ? GestureDetector(
          onHorizontalDragUpdate: _updateProgress,
          child: _buildProgressBar(),
        )
            : _buildProgressBar(),
      );
  }

  Widget _buildProgressBar() {
    return CustomPaint(
      painter: ProgressBarPainter(
        progress: _currentProgress,
        color: widget.color,
        color2: widget.color2,
      ),
    );
  }
}

class ProgressBarPainter extends CustomPainter {
  final  ValueNotifier<double> progress;// <--- 定义成员变量
  final Color color;
  final Color color2;

  ProgressBarPainter({
    required this.progress,
    required this.color,
    required this.color2,
  }):super(repaint: progress);// <--- 传入 Listenable 可监听对象

  @override
  void paint(Canvas canvas, Size size) {
    ///设置画笔,颜色,风格
    final backgroundPaint = Paint()
      ..color = color2
      ..style = PaintingStyle.fill;
    canvas.drawRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(0, 0, size.width, size.height),
        Radius.circular(size.height / 2),
      ),
      backgroundPaint,
    );

    final background2Paint = Paint()
      ..color = color
      ..style = PaintingStyle.fill;
    canvas.drawRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(0, 0, size.width * progress.value, size.height),
        Radius.circular(size.height / 2),
      ),
      background2Paint,
    );

    final strokeWidth = 5.0;
    final strokePaint = Paint()
      ..color = color
      ..strokeWidth = strokeWidth;
    final centerX = size.width * progress.value;
    final centerY = size.height / 2;
    final radius = size.height / 2;
    canvas.drawCircle(
      Offset(centerX, centerY),
      radius,
      strokePaint..style = PaintingStyle.stroke,
    );
    canvas.drawCircle(
      Offset(centerX, centerY),
      radius,
      strokePaint
        ..style = PaintingStyle.fill
        ..color = color2,
    );
  }

  @override
  bool shouldRepaint(ProgressBarPainter oldDelegate) {
    return oldDelegate.progress != progress ||
        oldDelegate.color != color ||
        oldDelegate.color2 != color2;
  }
}

使用:

CustomProgressBar(progress: 0.7,color: Colors.white,color2:Colors.blue.shade200,enableGesture: true,)

这这是抛砖引玉,自己还可以根据上面的代码扩展完善,增加渐变,动画,刻度。。。。 自定义系统学习传送门:s.juejin.cn/ds/imEMWF7/

感兴趣可以加qq群882974689交流flutter。