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

Flutter中如何在ListView内部嵌套使用ListView

Flutter 双索引独立模型下嵌套ListView实现方案

两层ListView分别绑定依托自身索引的独立模型时,核心要解决滚动冲突、约束溢出、Widget复用时数据错位三个问题,具体实现步骤如下:

核心配置原则

  • 内层列表必须禁用自身滚动,将滚动手势控制权完全交给外层列表,避免手势冲突导致的滑动卡顿、顿挫
  • 内层列表开启高度自适应,解决垂直方向无界约束导致的布局溢出错误
  • 内外层列表项都绑定和自身模型唯一标识对应的Key,避免Widget树复用时出现索引错位、状态串项问题

第一步:定义两层独立索引模型

先分别定义外层、内层绑定的模型类,每个外层模型持有对应自己的内层子模型列表:

// 外层列表绑定模型,依托外层列表索引
class ParentItemModel {
  final int parentId; // 外层项唯一标识
  final String parentTitle;
  final List<ChildItemModel> childItems; // 对应内层子项数据

  ParentItemModel({
    required this.parentId,
    required this.parentTitle,
    required this.childItems,
  });
}

// 内层列表绑定模型,依托内层列表索引
class ChildItemModel {
  final int childId; // 内层项唯一标识
  final String content;
  final bool isChecked;

  ChildItemModel({
    required this.childId,
    required this.content,
    required this.isChecked,
  });
}

第二步:完整实现代码

class NestedListPage extends StatelessWidget {
  NestedListPage({super.key});

  // 模拟测试数据
  final List<ParentItemModel> parentList = List.generate(
    8,
    (outerIndex) => ParentItemModel(
      parentId: outerIndex,
      parentTitle: "分组 $outerIndex",
      childItems: List.generate(
        3 + outerIndex % 4, // 每个分组子项数量不等,模拟真实业务场景
        (innerIndex) => ChildItemModel(
          childId: innerIndex,
          content: "子条目 $outerIndex-$innerIndex",
          isChecked: innerIndex % 2 == 0,
        ),
      ),
    ),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("嵌套列表示例")),
      // 外层ListView,绑定外层索引与ParentItemModel
      body: ListView.builder(
        key: const PageStorageKey("outer_list_view"),
        padding: const EdgeInsets.all(12),
        itemCount: parentList.length,
        itemBuilder: (context, outerIndex) {
          final currentParent = parentList[outerIndex];
          return Container(
            key: ValueKey("parent_${currentParent.parentId}"),
            margin: const EdgeInsets.only(bottom: 12),
            padding: const EdgeInsets.all(10),
            decoration: BoxDecoration(
              border: Border.all(color: Colors.grey.shade300),
              borderRadius: BorderRadius.circular(8),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  currentParent.parentTitle,
                  style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
                ),
                const SizedBox(height: 6),
                // 内层ListView,绑定内层索引与ChildItemModel
                ListView.builder(
                  // 核心配置1:禁用内层滚动,手势权交外层
                  physics: const NeverScrollableScrollPhysics(),
                  // 核心配置2:自适应内容高度,解决无界约束溢出
                  shrinkWrap: true,
                  itemCount: currentParent.childItems.length,
                  itemBuilder: (context, innerIndex) {
                    final currentChild = currentParent.childItems[innerIndex];
                    return ListTile(
                      key: ValueKey("child_${currentParent.parentId}_${currentChild.childId}"),
                      leading: Icon(
                        currentChild.isChecked ? Icons.check_circle : Icons.radio_button_unchecked,
                        color: currentChild.isChecked ? Colors.green : Colors.grey,
                      ),
                      title: Text(currentChild.content),
                      onTap: () {
                        // 可通过outerIndex、innerIndex精准定位两层模型处理交互
                        debugPrint("点击分组$outerIndex下的子项$innerIndex");
                      },
                    );
                  },
                )
              ],
            ),
          );
        },
      ),
    );
  }
}

注意事项

  • 不要给内层列表写死高度:shrinkWrap: true会自动计算所有子项的总高度适配,写死高度容易出现内容截断、多余留白的问题
  • 不要保留内层列表的滚动能力:如果不设置NeverScrollableScrollPhysics,内外层会抢滚动手势,出现滑动卡顿、列表拉不动的问题
  • Key不要直接用列表索引:Key要绑定模型自身的唯一标识,否则列表增删、滚动复用时会出现子项状态错乱,比如选中状态串到其他条目上
  • 单组内层子项超过50条时不建议用当前方案:shrinkWrap: true会一次性渲染所有内层子项,内存占用高,这种场景建议换用CustomScrollView配合slivers嵌套实现,性能更好

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 04:12:31