You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Flutter实现支持三值展示的任务进度条UI

任务进度UI参考效果

方案选型说明

你列的三个方向实际开发中优先级如下:

  • 轻量自定义组件(基于Stack+LayoutBuilder实现,比二次封装Slider/ProgressBar灵活度高太多,代码量也很小)
  • 现成pub包:仅适合完全不需要定制样式、快速堆功能的场景,后续要微调节点大小、颜色、标签位置的时候,改包源码的成本远高于自己实现
  • Container硬编码:完全不推荐,适配不同屏幕、动态进度值的时候要写大量冗余判断,扩展性极差
可直接复用的实现代码

下面的组件支持动态传入三个节点的阈值、标签、进度值、样式参数,自动适配父容器宽度,不需要写死尺寸:

import 'package:flutter/material.dart';

class ThreeNodeProgressBar extends StatelessWidget {
  /// 当前总进度,取值范围0-1
  final double progress;
  /// 三个节点对应的进度阈值,长度必须为3,取值范围0-1
  final List<double> nodeValues;
  /// 三个节点下方显示的标签文本,长度必须为3
  final List<String> nodeLabels;
  final Color activeTrackColor;
  final Color inactiveTrackColor;
  final Color activeNodeColor;
  final Color inactiveNodeColor;
  final double trackHeight;
  final double nodeRadius;

  const ThreeNodeProgressBar({
    super.key,
    required this.progress,
    required this.nodeValues,
    required this.nodeLabels,
    this.activeTrackColor = Colors.blue,
    this.inactiveTrackColor = Colors.grey,
    this.activeNodeColor = Colors.blue,
    this.inactiveNodeColor = Colors.white,
    this.trackHeight = 4,
    this.nodeRadius = 10,
  }) : assert(nodeValues.length == 3 && nodeLabels.length == 3, '仅支持3个节点配置'),
       assert(progress >=0 && progress <=1, '进度值范围必须在0-1之间');

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final totalWidth = constraints.maxWidth;
        // 进度轨道总长度,左右预留节点半径避免圆形节点被裁切
        final trackLength = totalWidth - 2 * nodeRadius;
        // 根据传入的节点进度比例,计算每个节点的x轴圆心坐标
        final nodePositions = nodeValues.map((p) => nodeRadius + trackLength * p).toList();

        return SizedBox(
          height: 50, // 预留标签显示空间,可根据字号自行调整
          width: totalWidth,
          child: Stack(
            clipBehavior: Clip.none,
            children: [
              // 底层未完成进度轨道
              Positioned(
                top: nodeRadius - trackHeight/2,
                left: nodeRadius,
                child: Container(
                  height: trackHeight,
                  width: trackLength,
                  decoration: BoxDecoration(
                    color: inactiveTrackColor,
                    borderRadius: BorderRadius.circular(trackHeight/2),
                  ),
                ),
              ),
              // 上层已完成进度轨道
              Positioned(
                top: nodeRadius - trackHeight/2,
                left: nodeRadius,
                child: Container(
                  height: trackHeight,
                  width: trackLength * progress.clamp(0, 1),
                  decoration: BoxDecoration(
                    color: activeTrackColor,
                    borderRadius: BorderRadius.circular(trackHeight/2),
                  ),
                ),
              ),
              // 三个节点+对应下方标签
              for(int i=0; i<3; i++)
                Positioned(
                  left: nodePositions[i] - nodeRadius,
                  top: 0,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      Container(
                        width: nodeRadius*2,
                        height: nodeRadius*2,
                        decoration: BoxDecoration(
                          shape: BoxShape.circle,
                          color: progress >= nodeValues[i] ? activeNodeColor : inactiveNodeColor,
                          border: Border.all(
                            color: activeNodeColor,
                            width: 2,
                          ),
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        nodeLabels[i],
                        style: TextStyle(
                          fontSize: 12,
                          color: progress >= nodeValues[i] ? activeNodeColor : Colors.grey,
                        ),
                      )
                    ],
                  ),
                )
            ],
          ),
        );
      },
    );
  }
}

组件调用示例:

ThreeNodeProgressBar(
  progress: 0.6, // 当前进度走到第二个节点
  nodeValues: const [0.2, 0.6, 1.0], // 三个节点分别在20%、60%、100%进度位置
  nodeLabels: const ['提交申请', '审核中', '完成'],
  activeTrackColor: Colors.green,
  activeNodeColor: Colors.green,
)
实用开发技巧
  • 进度动画扩展:如果要做进度切换的平滑效果,只需要把已选中进度条、节点的普通Container换成AnimatedContainer,传入duration参数就能自动实现补间动画,不需要额外写动画控制器
  • 交互扩展:如果需要支持点击节点跳转进度,直接给每个节点外层包GestureDetector,点击时更新progress值即可
  • 样式扩展:要做节点内对勾、进度条渐变、节点上方气泡提示,只需要在Stack里对应位置加子组件就行,整体布局逻辑不用改动
  • 避坑点:不要给进度条写固定宽度,用LayoutBuilder获取父容器约束做自适应,能避免90%的屏幕适配问题

内容的提问来源于stack exchange,提问作者Pranay Dhongade

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 13:15:47