Flutter自定义Chip点击缩放动画正常但未选中项未取消着色问题
问题:Flutter自定义Chip选中状态无法正确切换
我在Flutter项目中自定义了4个Chip组件,通过带装饰的Container和GestureDetector监听点击事件,期望实现:点击Chip时通过Transform.scale实现缩放动画,同时该Chip变为选中状态,之前选中的Chip切换为未选中状态,始终保持仅1个Chip选中。
目前缩放动画运行正常,但选中新Chip后,原选中的Chip未取消选中状态(未切换回未选中颜色),甚至出现所有Chip都显示为选中状态的异常。
相关代码如下:
class ChipTile extends StatefulWidget { final String text; final index; const ChipTile({ super.key, required this.text, required this.index, }); @override State<ChipTile> createState() => _ChipTileState(); } class _ChipTileState extends State<ChipTile> with SingleTickerProviderStateMixin { //animation on tap late AnimationController _animationController; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: Duration(milliseconds: 100), lowerBound: 0.0, upperBound: 0.1, )..addListener(() { setState(() {}); }); } void onTapDown(TapDownDetails details) { _animationController.forward(); } void onTapUp(TapUpDetails details) { _animationController.reverse(); } void onTapCancel() { _animationController.reverse(); } //---------------------------- @override Widget build(BuildContext context) { var scale = 1 + _animationController.value; return GestureDetector( onTap: () { setState(() { globals.selectedChip = widget.index; }); }, onTapDown: onTapDown, onTapUp: onTapUp, onTapCancel: onTapCancel, child: Transform.scale( scale: scale, child: SizedBox( height: 30, width: 70, child: Container( child: Center( child: Text( widget.text, style: GoogleFonts.poppins( fontSize: 15, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: globals.selectedChip == widget.index ? Colors.blue[100] : Colors.grey[300], ), ), ), ), ); } }
问题原因
- 局部
setState无法触发全局重绘:修改globals.selectedChip时,仅当前点击的ChipTile调用了setState,其他ChipTile的状态未收到更新通知,不会重新执行build方法切换颜色。 - 类型不匹配(可能):如果
globals.selectedChip和widget.index类型不一致(比如一个是int,一个是String),会导致==判断始终为真,出现所有Chip都显示选中的异常。
修复方案
方案1:将选中状态提升至父组件管理(推荐)
把选中索引放到父组件的状态中,通过回调通知父组件更新状态,父组件重绘时会同步更新所有ChipTile的选中状态。
父组件示例:
class ChipGroup extends StatefulWidget { const ChipGroup({super.key}); @override State<ChipGroup> createState() => _ChipGroupState(); } class _ChipGroupState extends State<ChipGroup> { int _selectedChipIndex = 0; // 默认选中第一个Chip @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ChipTile( text: "Chip1", index: 0, isSelected: _selectedChipIndex == 0, onTap: () => setState(() => _selectedChipIndex = 0), ), ChipTile( text: "Chip2", index: 1, isSelected: _selectedChipIndex == 1, onTap: () => setState(() => _selectedChipIndex = 1), ), ChipTile( text: "Chip3", index: 2, isSelected: _selectedChipIndex == 2, onTap: () => setState(() => _selectedChipIndex = 2), ), ChipTile( text: "Chip4", index: 3, isSelected: _selectedChipIndex == 3, onTap: () => setState(() => _selectedChipIndex = 3), ), ], ); } }
修改后的ChipTile:
class ChipTile extends StatefulWidget { final String text; final int index; final bool isSelected; final VoidCallback onTap; const ChipTile({ super.key, required this.text, required this.index, required this.isSelected, required this.onTap, }); @override State<ChipTile> createState() => _ChipTileState(); } class _ChipTileState extends State<ChipTile> with SingleTickerProviderStateMixin { late AnimationController _animationController; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 100), lowerBound: 0.0, upperBound: 0.1, )..addListener(() => setState(() {})); } void onTapDown(TapDownDetails details) => _animationController.forward(); void onTapUp(TapUpDetails details) => _animationController.reverse(); void onTapCancel() => _animationController.reverse(); @override Widget build(BuildContext context) { final scale = 1 + _animationController.value; return GestureDetector( onTap: widget.onTap, onTapDown: onTapDown, onTapUp: onTapUp, onTapCancel: onTapCancel, child: Transform.scale( scale: scale, child: SizedBox( height: 30, width: 70, child: Container( alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: widget.isSelected ? Colors.blue[100] : Colors.grey[300], ), child: Text( widget.text, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ), ); } @override void dispose() { _animationController.dispose(); // 销毁控制器,避免内存泄漏 super.dispose(); } }
方案2:使用全局状态管理(如Provider)
如果项目已使用全局状态管理库,可通过它监听选中状态变化,触发所有ChipTile重绘。
1. 创建全局状态类:
class SelectedChipProvider extends ChangeNotifier { int _selectedIndex = 0; int get selectedIndex => _selectedIndex; void setSelectedIndex(int index) { _selectedIndex = index; notifyListeners(); // 通知所有监听者更新 } }
2. 在main.dart中注册:
void main() { runApp( ChangeNotifierProvider( create: (context) => SelectedChipProvider(), child: const MyApp(), ), ); }
3. 修改ChipTile的build方法:
@override Widget build(BuildContext context) { final provider = Provider.of<SelectedChipProvider>(context); final isSelected = provider.selectedIndex == widget.index; final scale = 1 + _animationController.value; return GestureDetector( onTap: () => provider.setSelectedIndex(widget.index), onTapDown: onTapDown, onTapUp: onTapUp, onTapCancel: onTapCancel, child: Transform.scale( scale: scale, child: SizedBox( height: 30, width: 70, child: Container( alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: isSelected ? Colors.blue[100] : Colors.grey[300], ), child: Text( widget.text, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ), ); }
额外注意点
- 确保
widget.index与选中索引的类型一致(统一为int或String),避免==判断失效。 - 必须在
_ChipTileState的dispose方法中销毁AnimationController,防止内存泄漏。
内容的提问来源于stack exchange,提问作者Ceoo
相关产品推荐
相关产品推荐

