如何使用Flutter实现支持三值展示的任务进度条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
相关产品推荐
相关产品推荐

