如何在Flutter中实现支持动态阈值的自定义时间线组件?
动态阈值时间线组件实现方案
核心思路
- 基于传入的动态阈值列表和达成值,自动计算每个节点、线段的位置与颜色
- 区分已达成(≤达成值)和未达成(>达成值)的节点、线段样式
- 自动处理达成值落在两个阈值之间的分段线条
完整实现代码
class DynamicTimeline extends StatelessWidget { final List<int> thresholds; final int achievedValue; final Color achievedColor; final Color unachievedColor; final double nodeSize; final double lineThickness; final double totalWidth; const DynamicTimeline({ super.key, required this.thresholds, required this.achievedValue, this.achievedColor = Colors.green, this.unachievedColor = Colors.grey, this.nodeSize = 12, this.lineThickness = 4, this.totalWidth = 300, }) : assert(thresholds.isNotEmpty && thresholds.length >= 2, "阈值列表至少需要2个元素"); @override Widget build(BuildContext context) { // 确保阈值列表是升序排列 final sortedThresholds = List<int>.from(thresholds)..sort(); final minThreshold = sortedThresholds.first; final maxThreshold = sortedThresholds.last; final totalRange = maxThreshold - minThreshold; return Center( child: Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.center, children: _buildTimelineChildren(sortedThresholds, totalRange), ), ); } List<Widget> _buildTimelineChildren(List<int> sortedThresholds, int totalRange) { final children = <Widget>[]; for (int i = 0; i < sortedThresholds.length; i++) { final currentThreshold = sortedThresholds[i]; // 构建节点(包含数值文本) children.add( Column( mainAxisSize: MainAxisSize.min, children: [ Text( currentThreshold.toString(), style: TextStyle( color: currentThreshold <= achievedValue ? achievedColor : unachievedColor, fontSize: 12, ), ), const SizedBox(height: 4), Container( width: nodeSize, height: nodeSize, decoration: BoxDecoration( shape: BoxShape.circle, color: currentThreshold <= achievedValue ? achievedColor : unachievedColor, ), ), ], ), ); // 最后一个节点后不需要线条 if (i == sortedThresholds.length - 1) break; final nextThreshold = sortedThresholds[i + 1]; final segmentRange = nextThreshold - currentThreshold; final segmentWidth = (segmentRange / totalRange) * totalWidth; // 判断当前区间是否完全在达成值内 if (nextThreshold <= achievedValue) { // 整个线段都是达成色 children.add( SizedBox( width: segmentWidth, child: Divider( thickness: lineThickness, color: achievedColor, height: lineThickness, ), ), ); } else if (currentThreshold > achievedValue) { // 整个线段都是未达成色 children.add( SizedBox( width: segmentWidth, child: Divider( thickness: lineThickness, color: unachievedColor, height: lineThickness, ), ), ); } else { // 达成值落在当前区间内,拆分线段 final achievedSegmentRange = achievedValue - currentThreshold; final achievedWidth = (achievedSegmentRange / totalRange) * totalWidth; final unachievedWidth = segmentWidth - achievedWidth; children.add( SizedBox( width: achievedWidth, child: Divider( thickness: lineThickness, color: achievedColor, height: lineThickness, ), ), ); children.add( SizedBox( width: unachievedWidth, child: Divider( thickness: lineThickness, color: unachievedColor, height: lineThickness, ), ), ); } } return children; } } // 使用示例 class TimelineDemo extends StatelessWidget { const TimelineDemo({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.all(20), child: DynamicTimeline( thresholds: [50, 100, 250, 300], achievedValue: 175, achievedColor: Colors.green, unachievedColor: Colors.grey.shade400, totalWidth: 350, ), ), ); } }
关键特性说明
- 动态适配任意阈值列表:支持后端返回的任意长度升序/降序阈值(内部自动排序)
- 智能颜色区分:节点和线段自动根据达成值判断颜色,达成值落在区间中间时自动拆分线段
- 可配置样式:支持自定义节点大小、线条粗细、总宽度、颜色方案
- 响应式布局:基于总宽度按比例分配各线段长度,适配不同屏幕
内容的提问来源于stack exchange,提问作者Arnab Ghosh
相关产品推荐
相关产品推荐

