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

Flutter列表视图选中项对应位置指示器的动效实现方案咨询

Flutter 实现列表选中时指示器动画移动效果

核心实现思路

  • 维护当前选中项的索引,点击列表项时更新索引
  • 通过全局Key获取每个列表项的屏幕位置,计算指示器的目标位置
  • 用AnimatedPositioned实现指示器的平滑移动动画,无需手动管理复杂的动画控制器

完整实现代码

class AnimatedIndicatorRow extends StatefulWidget {
  const AnimatedIndicatorRow({super.key});

  @override
  State<AnimatedIndicatorRow> createState() => _AnimatedIndicatorRowState();
}

class _AnimatedIndicatorRowState extends State<AnimatedIndicatorRow> {
  // 当前选中项索引
  int _selectedIndex = 0;
  // 存储每个列表项的GlobalKey,用于获取控件位置
  final List<GlobalKey> _itemKeys = List.generate(6, (index) => GlobalKey());
  // 指示器的top偏移值
  double _indicatorTop = 0;

  @override
  void initState() {
    super.initState();
    // 等待UI渲染完成后,初始化指示器到第一个item的位置
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _updateIndicatorPosition(_selectedIndex);
    });
  }

  // 根据选中索引更新指示器位置
  void _updateIndicatorPosition(int index) {
    final key = _itemKeys[index];
    final renderBox = key.currentContext?.findRenderObject() as RenderBox?;
    if (renderBox != null) {
      // 获取item在全局坐标系中的位置
      final position = renderBox.localToGlobal(Offset.zero);
      // 计算指示器top值:让指示器垂直居中对齐选中项
      setState(() {
        _indicatorTop = position.dy + (renderBox.size.height / 2) - 2;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        ListView.builder(
          shrinkWrap: true,
          // 不需要滚动的话可以禁用滚动 physics,避免位置计算错位
          physics: const NeverScrollableScrollPhysics(),
          itemCount: 6,
          itemBuilder: (context, index) {
            return GestureDetector(
              key: _itemKeys[index],
              onTap: () {
                setState(() {
                  _selectedIndex = index;
                });
                _updateIndicatorPosition(index);
              },
              child: Padding(
                padding: const EdgeInsets.all(13),
                child: Text(
                  'Item$index',
                  // 选中项高亮,提升交互反馈
                  style: TextStyle(
                    color: _selectedIndex == index ? const Color(0xffAA16CF) : Colors.black,
                    fontWeight: _selectedIndex == index ? FontWeight.bold : FontWeight.normal,
                  ),
                ),
              ),
            );
          },
        ),
        Stack(
          children: [
            SvgPicture.asset(
              'assets/images/scale_img.svg',
            ),
            AnimatedPositioned(
              duration: const Duration(milliseconds: 300), // 动画时长
              curve: Curves.easeInOut, // 动画过渡曲线
              right: 26,
              top: _indicatorTop,
              child: Container(
                width: 40,
                height: 4,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(5),
                  gradient: const LinearGradient(
                    begin: Alignment.topCenter,
                    end: Alignment.bottomCenter,
                    colors: [
                      Color(0xffFF7EB4),
                      Color(0xffAA16CF),
                    ],
                  ),
                  boxShadow: const [
                    BoxShadow(
                      blurRadius: 10,
                      spreadRadius: -1,
                      offset: Offset(0, 4),
                      color: Color(0xffAA16CF),
                    )
                  ],
                ),
              ),
            )
          ],
        )
      ],
    );
  }
}

关键细节说明

  • 选中状态交互:点击列表项时更新_selectedIndex,同时触发指示器位置计算,同步更新选中项的文字样式,让用户清晰感知选中状态
  • 位置计算逻辑:通过GlobalKey获取列表项的渲染对象,计算其在屏幕上的绝对位置,再调整指示器的top值,确保指示器与选中项垂直居中对齐
  • 动画简化实现:用AnimatedPositioned替代原有的静态Positioned,只需修改top值就能自动触发平滑过渡动画,省去手动创建AnimationController的麻烦

注意事项

  • 如果你的ListView需要支持滚动,需要添加ScrollController监听滚动事件,实时更新指示器位置,避免滚动后指示器错位
  • 确保SvgPicture的高度足够容纳所有列表项,防止指示器超出背景图范围
  • 初始化时必须等待UI渲染完成后再获取列表项位置,所以用addPostFrameCallback避免空指针问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 13:00:56