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
相关产品推荐
相关产品推荐

