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

如何获取动画曲线上点的近似时间,同步按钮颜色变化?

Flutter进度条动画与选项颜色同步解决方案

针对非曲线下进度条与选项颜色不同步的问题,以下是两种精准同步的实现方案:

方案一:通过曲线逆运算计算精准延迟时间

Flutter的Curve类通过transform(t)方法将0-1的时间进度t映射为0-1的动画进度值。要实现颜色变化与进度条经过时机同步,需要反向计算:当动画进度达到选项位置时,对应的时间t是多少,再以此设置Future.delayed的延迟时长。

由于多数曲线没有解析逆函数,可用二分法近似求解对应时间值:

// 计算动画进度达到targetProgress时对应的时间比例t(0-1)
double findTimeForProgress(Curve curve, double targetProgress, {int iterations = 10}) {
  double low = 0.0;
  double high = 1.0;
  // 迭代10次足以满足精度要求
  for (int i = 0; i < iterations; i++) {
    double mid = (low + high) / 2;
    double currentProgress = curve.transform(mid);
    if (currentProgress < targetProgress) {
      low = mid;
    } else {
      high = mid;
    }
  }
  return (low + high) / 2;
}

修改alteraCorItemStepper函数,替换原有均分时间逻辑:

alteraCorItemStepper(int indiceItem){
  const totalDurationMs = 2000;
  const curve = Curves.slowMiddle;
  int totalItems = listaProdutos.length;
  
  if(ultimoIndicePressionado < indiceItem){
    // 目标选项的进度阈值(适配spaceBetween布局)
    double targetProgress = indiceItem / (totalItems - 1);
    double targetTimeRatio = findTimeForProgress(curve, targetProgress);
    int targetDelay = (totalDurationMs * targetTimeRatio).round();
    
    Future.delayed(Duration(milliseconds: targetDelay), () {
      listaProdutos[indiceItem].corWidget = res.azul_claro;
      listaProdutos.refresh();
    });
    
    for(int i = 1; i <= indiceItem - ultimoIndicePressionado; i++){
      int indexAlterar = ultimoIndicePressionado + i;
      double itemProgress = indexAlterar / (totalItems - 1);
      double timeRatio = findTimeForProgress(curve, itemProgress);
      int delay = (totalDurationMs * timeRatio).round();
      
      Future.delayed(Duration(milliseconds: delay), () {
        if(indexAlterar <= indiceItem){
          listaProdutos[indexAlterar].corWidget = res.azul_claro;
          listaProdutos.refresh();
        }
      });
    }
  }
  
  if(ultimoIndicePressionado > indiceItem){
    listaProdutos[ultimoIndicePressionado].corWidget = Colors.white;
    listaProdutos.refresh();
    
    for(int i = 1; i <= ultimoIndicePressionado - indiceItem; i++){
      int indexAlterar = ultimoIndicePressionado - i;
      double itemProgress = indexAlterar / (totalItems - 1);
      double timeRatio = findTimeForProgress(curve, itemProgress);
      int delay = (totalDurationMs * timeRatio).round();
      
      Future.delayed(Duration(milliseconds: delay), () {
        if(indexAlterar > indiceItem){
          listaProdutos[indexAlterar].corWidget = Colors.white;
          listaProdutos.refresh();
        }
      });
    }
  }
}

方案二:监听动画帧实时同步颜色(推荐)

放弃Future.delayed,改用AnimationController+AnimatedBuilder监听动画每一帧进度,实时判断进度条是否覆盖选项,同步更新颜色。这种方式完全避免时间计算误差,还能响应动画中途的中断(比如用户重复点击按钮)。

1. 在Controller中配置动画控制器

class YourController extends GetxController with SingleTickerProviderStateMixin {
  late AnimationController _progressController;
  late Animation<double> _progressAnimation;
  final RxList<Produto> listaProdutos = <Produto>[].obs;
  int ultimoIndicePressionado = 0;
  
  @override
  void onInit() {
    super.onInit();
    _progressController = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    
    // 绑定动画曲线
    _progressAnimation = CurvedAnimation(
      parent: _progressController,
      curve: Curves.slowMiddle,
    );
    
    // 监听动画帧更新颜色
    _progressAnimation.addListener(() {
      _syncItemColors(_progressAnimation.value);
    });
  }
  
  // 根据当前进度同步选项颜色
  void _syncItemColors(double currentProgress) {
    int totalItems = listaProdutos.length;
    double itemWidthRatio = 1 / (totalItems - 1); // spaceBetween布局的选项进度阈值
    
    for (int i = 0; i < totalItems; i++) {
      double itemThreshold = i * itemWidthRatio;
      bool isCovered = currentProgress >= itemThreshold;
      
      if (ultimoIndicePressionado < widget.indiceItem) {
        // 正向移动:进度条覆盖的选项变蓝
        listaProdutos[i].corWidget = isCovered ? res.azul_claro : Colors.white;
      } else {
        // 反向移动:进度条未覆盖的选项变白
        listaProdutos[i].corWidget = isCovered ? Colors.white : res.azul_claro;
      }
    }
    listaProdutos.refresh();
  }
  
  // 启动动画到目标进度
  void startProgressAnimation(double targetProgress, int targetIndex) {
    ultimoIndicePressionado = targetIndex;
    _progressController.animateTo(targetProgress);
  }
  
  @override
  void onClose() {
    _progressController.dispose();
    super.onClose();
  }
}

2. 修改按钮点击事件

onTap: (){
  controller.startProgressAnimation(widget.valorProgress, widget.indiceItem);
}

3. 替换AnimatedContainer为AnimatedBuilder

Obx(() => AnimatedBuilder(
  animation: controller._progressAnimation,
  builder: (context, child) {
    return Container(
      height: largura_total/28,
      child: Align(
        alignment: Alignment.centerLeft,
        child: Container(
          height: (largura_total/28)/3,
          width: (largura - 32) * controller._progressAnimation.value,
          decoration: BoxDecoration(
            color: res.azul_claro,
            borderRadius: BorderRadius.circular(14),
          ),
        ),
      ),
    );
  },
))

方案对比

  • 方案一:基于时间延迟,适用于无法重构动画逻辑的场景,但仍可能存在微小误差,且无法处理动画中途中断的情况。
  • 方案二:实时监听动画进度,同步精度最高,逻辑更清晰,能处理所有动画状态变化,是最优解。

内容的提问来源于stack exchange,提问作者Roger Walter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 11:44:53