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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 09:07:37