Flutter 椭圆弧型(弓型)进度条

1,794 阅读2分钟

1d8526c1836b873a6b2d58e94ceb1ee2_v2-d1763db7484207bbed4c0a0d71c6233d_1440w.webp

首先我们定义一个椭圆弧型进度条组件。

/// 圆弧进度条
class CurveProgressIndicator extends StatelessWidget {
  const CurveProgressIndicator({
    super.key,
    this.size = Size.zero,
    required this.progress,
  });

  /// 监听器
  final ValueNotifier<double> progress;

  /// 尺寸
  final Size size;

  @override
  Widget build(BuildContext context) {
    debugPrint("重绘:底部进度条");
    return RepaintBoundary(
      child: CustomPaint(
        size: size,
        painter: CurveProgressIndicatorPainter(progress),
      ),
    );
  }
}

接着我们定义一个自定义画刷。

/// 圆弧进度条的绘制器
class CurveProgressIndicatorPainter extends CustomPainter {
  const CurveProgressIndicatorPainter(this.progress) : super(repaint: progress);

  /// 监听器
  final ValueNotifier<double> progress;

  @override
  void paint(Canvas canvas, Size size) {
    if (progress.value == 0) {
      //没有进度不绘制
      return;
    }
    debugPrint("重绘:底部进度条-画刷");

    // 贝塞尔曲率
    double bezier = size.height / 4;

    // 绘制弧线
    Path line = Path()
      ..moveTo(0, bezier)
      ..quadraticBezierTo(size.width / 2, -bezier, size.width, bezier);

    // 定义画刷
    var gradient = Gradient.linear(
      Offset.zero,
      Offset(size.width, 0),
      [
        const Color(0xFFB7EAFF),
        const Color(0xFF7ED9FF),
        const Color(0xFF0F7BAA),
        // Colors.red, Colors.blue, Colors.yellow,
      ],
      [0, 0.6, 1],
    );

    // 绘制扇形进度条
    var paint = Paint()
      //..color = progressColor
      ..shader = gradient
      ..strokeWidth = 2.5
      ..strokeCap = StrokeCap.round
      ..style = PaintingStyle.stroke;

    //计算百分比进度条
    PathMetrics pathMetrics = line.computeMetrics();
    // 获取第一小节信息(猜测可能有多个Path叠加?)
    PathMetric pathMetric = pathMetrics.first;
    // 整个Path的长度
    double length = pathMetric.length;
    // 当前进度
    double value = length * progress.value;
    Path extractPath = pathMetric.extractPath(0, value, startWithMoveTo: true);
    canvas.drawPath(extractPath, paint);
  }

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

详细解析

弧线的路径定义:

定义一个高度为Size容器 4分之1的高度 (我这里是将Size设置成100,因为我的案例中播放器底部有一个圆弧形的背景, 可以根据实际情况设置参数),

将起点放置于(0,bezier),绘制弧线点(x1,y1) = (宽度/2,-bezier) ,(x2,y3) = (宽度,bezier)。

image.png

关于百分比进度的绘制方法:

需要根据PathMetrics获取到line的总长度,再通过 double value = length * progress.value 计算出当前的路径,最后绘制 渐变色 就完成了。

//计算百分比进度条
    PathMetrics pathMetrics = line.computeMetrics();
    // 获取第一小节信息(猜测可能有多个Path叠加?)
    PathMetric pathMetric = pathMetrics.first;
    // 整个Path的长度
    double length = pathMetric.length;
    // 当前进度
    double value = length * progress.value;
    Path extractPath = pathMetric.extractPath(0, value, startWithMoveTo: true);
    canvas.drawPath(extractPath, paint);

看看成品:

MUSIC-银色@4x.png

MUSIC-深空黑色@4x.png

整个app的源码地址:

github.com/944095635/d…

在线体验效果(服务器带宽比较低):

music.dmskin.com/


2024-11-18 更新: 1.优化 路径绘制方案,修改为Slider方式,可以支持点击和拖拽。 2.增加几首歌单 3.播放列表切换歌曲

Path line = Path()
        ..moveTo(0, size.height)
        ..quadraticBezierTo(
          size.width / 2,
          -size.height,
          size.width,
          size.height,
        );

      //计算百分比进度条
      ui.PathMetrics pathMetrics = line.computeMetrics();
      // 获取第一小节信息(猜测可能有多个Path叠加?)
      ui.PathMetric pathMetric = pathMetrics.first;
      // 整个Path的长度
      double length = pathMetric.length;
      // 当前进度
      double value = length * controllerValue;

      // 左侧路径
      Path extractLeftPath =
          pathMetric.extractPath(0, value, startWithMoveTo: true);

      //绘制第1段颜色
      context.canvas.drawPath(extractLeftPath, activePaint);
      
      // 获取滑块绘制的位置
      ui.Tangent? tangent = pathMetric.getTangentForOffset(value);

      /// 滑块在路径上
      thumbCenter = tangent!.position;

image.png