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

Flutter中如何为AnimatedList的项重新排序添加动画?

Flutter AnimatedList 项移动至首位动画修复

问题分析

当前实现存在几个核心问题:

  • 选中项的移除动画是向左滑出视野,而非连贯移动到首位,视觉上呈现“消失再出现”的割裂效果
  • 插入首位的动画与移除动画无衔接,无法形成移动的连贯感
  • 未处理其他项向右偏移的动画逻辑,不符合需求中“其余项向右偏移”的要求
  • 移除项时hasDropdown的判断逻辑错误,误用了原位置index

解决方案

以下是修正后的代码及关键调整说明:

1. 新增状态变量记录选中项原位置

在State类中添加变量,用于计算动画偏移:

int? _lastSelectedIndex;
final double _itemWidth = 100.w; // 根据你的实际项宽调整

2. 修正AnimatedList实现

AnimatedList(
  shrinkWrap: true,
  key: listKey,
  padding: const EdgeInsets.only(left: 14).w,
  initialItemCount: _categories.length,
  scrollDirection: Axis.horizontal,
  itemBuilder: (_, index, animation) {
    final item = _categories[index];
    final isSelectedItem = selectedCategory.value == item;

    // 计算项的动画偏移
    Offset beginOffset = Offset.zero;
    Offset endOffset = Offset.zero;
    if (isSelectedItem && _lastSelectedIndex != null && _lastSelectedIndex! > 0) {
      // 选中项从原位置滑向首位
      beginOffset = Offset(_lastSelectedIndex! * _itemWidth, 0);
      endOffset = Offset.zero;
    } else if (_lastSelectedIndex != null && index < _lastSelectedIndex!) {
      // 原位置在选中项左侧的项,向右偏移腾位置
      beginOffset = Offset.zero;
      endOffset = Offset(_itemWidth, 0);
    }

    return GestureDetector(
      onTap: () {
        if (index == 0) return; // 已在首位,无需操作
        _lastSelectedIndex = index;
        selectedCategory.value = item;
        final movedItem = _categories.removeAt(index);

        // 移除项动画:滑向首位+抬起效果
        listKey.currentState!.removeItem(
          index,
          (context, anim) => SlideTransition(
            position: Tween<Offset>(
              begin: Offset.zero,
              end: Offset(-index * _itemWidth, 0),
            ).animate(CurvedAnimation(parent: anim, curve: Curves.easeInOut)),
            child: ScaleTransition(
              scale: Tween<double>(begin: 1, end: 1.1).animate(anim),
              child: _CourseCategoryItem(
                title: movedItem,
                hasDropdown: false,
              ),
            ),
          ),
          duration: const Duration(seconds: 1),
        );

        // 插入项动画:衔接移除动画,平滑显示在首位
        listKey.currentState!.insertItem(0, duration: const Duration(seconds: 1));
        _categories.insert(0, movedItem);
        setState(() {});
      },
      child: SlideTransition(
        position: Tween<Offset>(begin: beginOffset, end: endOffset).animate(animation),
        child: ScaleTransition(
          scale: isSelectedItem
              ? Tween<double>(begin: 1.1, end: 1).animate(animation)
              : Tween<double>(begin: 1, end: 1).animate(animation),
          child: _CourseCategoryItem(
            title: item,
            hasDropdown: index == 0,
            isSelected: selectedCategory.value == item,
          ),
        ),
      ),
    );
  },
)

关键调整点

  • 用_lastSelectedIndex追踪选中项的原位置,精准计算所有项的动画偏移量
  • 移除选中项时,通过SlideTransition让项滑向首位位置,同时用ScaleTransition实现“抬起”的缩放效果
  • 插入首位的动画与移除动画保持相同时长,保证视觉连贯性
  • 原位置在选中项左侧的项会向右偏移一个项宽,实现“腾位置”的效果
  • 修正hasDropdown逻辑,仅首位项显示下拉,移除过程中不显示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 18:05:02