Flutter/Dart:如何触发单个Widget的AnimationController动画并实现互斥选中?
解决多Chip互斥选中与动画触发问题
一、先理清父子组件的职责划分
- 子组件(自定义Chip):只管自己的动画逻辑(
AnimationController、动画曲线)和UI渲染,接收外部传入的选中状态,当状态变化时自动触发对应动画。 - 父组件:负责管理全局的选中状态(比如记录当前选中的Chip标识),处理Chip的点击事件,点击某个Chip时更新全局状态,让所有Chip同步感知状态变化。
二、具体实现步骤
1. 封装带动画的自定义Chip
把动画逻辑留在子组件,但让它能响应外部的状态变化自动执行动画:
class AnimatedChip extends StatefulWidget { final String label; final bool isSelected; final VoidCallback onTap; const AnimatedChip({ super.key, required this.label, required this.isSelected, required this.onTap, }); @override State<AnimatedChip> createState() => _AnimatedChipState(); } class _AnimatedChipState extends State<AnimatedChip> with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation<double> _scaleAnimation; @override void initState() { super.initState(); // 初始化时匹配外部传入的选中状态 _animationController = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, value: widget.isSelected ? 1.0 : 0.0, ); _scaleAnimation = Tween<double>(begin: 0.9, end: 1.1).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); } @override void didUpdateWidget(covariant AnimatedChip oldWidget) { super.didUpdateWidget(oldWidget); // 外部选中状态变化时,触发对应动画 if (widget.isSelected != oldWidget.isSelected) { widget.isSelected ? _animationController.forward() : _animationController.reverse(); } } @override void dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: widget.onTap, child: ScaleTransition( scale: _scaleAnimation, child: Chip( label: Text(widget.label), backgroundColor: widget.isSelected ? Colors.blue : Colors.grey[200], labelStyle: TextStyle( color: widget.isSelected ? Colors.white : Colors.black, ), ), ), ); } }
2. 父组件管理全局选中状态
场景一:简单页面用setState即可
class ChipGroup extends StatefulWidget { const ChipGroup({super.key}); @override State<ChipGroup> createState() => _ChipGroupState(); } class _ChipGroupState extends State<ChipGroup> { String? _selectedChipLabel; @override Widget build(BuildContext context) { return Wrap( spacing: 8.0, children: [ AnimatedChip( label: "Chip 1", isSelected: _selectedChipLabel == "Chip 1", onTap: () => setState(() => _selectedChipLabel = "Chip 1"), ), AnimatedChip( label: "Chip 2", isSelected: _selectedChipLabel == "Chip 2", onTap: () => setState(() => _selectedChipLabel = "Chip 2"), ), AnimatedChip( label: "Chip 3", isSelected: _selectedChipLabel == "Chip 3", onTap: () => setState(() => _selectedChipLabel = "Chip 3"), ), ], ); } }
场景二:复杂跨组件状态用ChangeNotifier+Provider
先写状态管理类:
class ChipSelectionNotifier extends ChangeNotifier { String? _selectedLabel; String? get selectedLabel => _selectedLabel; void selectLabel(String label) { _selectedLabel = label; notifyListeners(); } }
再写父组件:
class ChipGroup extends StatelessWidget { const ChipGroup({super.key}); @override Widget build(BuildContext context) { final selectionNotifier = Provider.of<ChipSelectionNotifier>(context); return Wrap( spacing: 8.0, children: [ AnimatedChip( label: "Chip 1", isSelected: selectionNotifier.selectedLabel == "Chip 1", onTap: () => selectionNotifier.selectLabel("Chip 1"), ), AnimatedChip( label: "Chip 2", isSelected: selectionNotifier.selectedLabel == "Chip 2", onTap: () => selectionNotifier.selectLabel("Chip 2"), ), AnimatedChip( label: "Chip 3", isSelected: selectionNotifier.selectedLabel == "Chip 3", onTap: () => selectionNotifier.selectLabel("Chip 3"), ), ], ); } }
记得在顶层用ChangeNotifierProvider包裹:
void main() { runApp( ChangeNotifierProvider( create: (context) => ChipSelectionNotifier(), child: const MyApp(), ), ); }
三、关键注意点
- 子组件必须通过
didUpdateWidget监听外部状态变化,这是触发动画的核心——声明式UI中组件只会在属性变化时重建,这个生命周期方法能精准捕捉状态切换并执行动画。 - 全局选中状态必须由父组件统一管理,不能让每个Chip自己维护状态,这样才能保证互斥性:全局只有一个“当前选中项”,所有Chip的选中状态都基于这个值。
- 之前用Provider没成功的话,大概率是没在子组件里实现
didUpdateWidget触发动画,或者Provider的监听范围没覆盖到所有Chip组件。
内容的提问来源于stack exchange,提问作者yuzhshen
相关产品推荐
相关产品推荐

