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

Flutter中ListView.builder内ExpansionTile图标联动问题求助

解决方案

问题核心是所有ExpansionTile共享了同一状态变量,导致动画同步触发。要实现单个项的独立动画,需为每个列表项单独维护展开状态,以下是两种可行方案:

方案一:用StatefulWidget封装独立列表项

将每个ExpansionTile封装为带独立状态的StatefulWidget,让每个项自行管理展开状态与动画:

class CustomExpansionItem extends StatefulWidget {
  final String title;
  final Widget content;
  final String trailingText;

  const CustomExpansionItem({
    super.key,
    required this.title,
    required this.content,
    required this.trailingText,
  });

  @override
  State<CustomExpansionItem> createState() => _CustomExpansionItemState();
}

class _CustomExpansionItemState extends State<CustomExpansionItem> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return ExpansionTile(
      title: Text(widget.title),
      children: [widget.content],
      onExpansionChanged: (expanded) {
        setState(() {
          _isExpanded = expanded;
        });
      },
      trailing: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(widget.trailingText),
          const SizedBox(width: 8),
          AnimatedRotation(
            duration: const Duration(milliseconds: 200),
            turns: _isExpanded ? 0.25 : 0, // 0.25圈对应90度旋转
            child: const Icon(Icons.arrow_forward_ios),
          ),
        ],
      ),
    );
  }
}

在ListView.builder中直接使用该自定义组件:

ListView.builder(
  itemCount: 5,
  itemBuilder: (context, index) {
    return CustomExpansionItem(
      title: "标题 $index",
      content: Padding(
        padding: const EdgeInsets.all(16),
        child: Text("内容 $index"),
      ),
      trailingText: "更多",
    );
  },
)

方案二:用ValueNotifier+AnimatedBuilder(无需额外StatefulWidget)

若不想创建新的StatefulWidget,可通过ValueNotifier为每个项单独维护状态,配合AnimatedBuilder实现动画:

ListView.builder(
  itemCount: 5,
  itemBuilder: (context, index) {
    final isExpandedNotifier = ValueNotifier(false);
    return ExpansionTile(
      title: Text("标题 $index"),
      children: [
        Padding(
          padding: const EdgeInsets.all(16),
          child: Text("内容 $index"),
        )
      ],
      onExpansionChanged: (expanded) {
        isExpandedNotifier.value = expanded;
      },
      trailing: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Text("更多"),
          const SizedBox(width: 8),
          ValueListenableBuilder(
            valueListenable: isExpandedNotifier,
            builder: (context, isExpanded, child) {
              return AnimatedRotation(
                duration: const Duration(milliseconds: 200),
                turns: isExpanded ? 0.25 : 0,
                child: child,
              );
            },
            child: const Icon(Icons.arrow_forward_ios),
          ),
        ],
      ),
    );
  },
)

关键说明

  • 两种方案的核心是让每个列表项拥有独立状态变量,避免状态共享导致的同步动画问题
  • AnimatedRotation的turns参数:0对应0度,0.25对应90度,刚好匹配展开/收起的箭头旋转需求
  • 保留Row组件实现文本+图标的组合,解决了最初用Row丢失动画的问题(本质是之前未给每个项绑定独立状态)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 09:10:33