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

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],
            ),
          ),
        ),
      ),
    );
  }
}

问题原因

  1. 局部setState无法触发全局重绘:修改globals.selectedChip时,仅当前点击的ChipTile调用了setState,其他ChipTile的状态未收到更新通知,不会重新执行build方法切换颜色。
  2. 类型不匹配(可能):如果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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 14:10:28