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

Flutter技术问询:如何在Expandable ListView中嵌套ListView

嘿,这个需求我之前在项目里也实现过,其实核心就是处理好嵌套滚动的冲突,同时正确构建可展开列表的子项。下面我给你一步步拆解具体的实现方法:

核心思路

我们可以用Flutter原生的ExpansionTile组件来实现外层的可展开列表,然后在它的children属性中嵌套ListView。需要注意两个关键点:一是子ListView的高度自适应,二是避免滚动冲突。

具体实现步骤

1. 准备数据源

首先定义一个简单的数据模型,用来存储父列表项和对应的子列表数据:

class ParentItem {
  final String title;
  final List<String> children;

  ParentItem({required this.title, required this.children});
}

// 模拟示例数据源
final List<ParentItem> parentItems = [
  ParentItem(
    title: 'Group 1',
    children: ['Item 1-1', 'Item 1-2', 'Item 1-3', 'Item 1-4'],
  ),
  ParentItem(
    title: 'Group 2',
    children: ['Item 2-1', 'Item 2-2'],
  ),
  ParentItem(
    title: 'Group 3',
    children: ['Item 3-1', 'Item 3-2', 'Item 3-3'],
  ),
];

2. 构建嵌套列表

用外层的ListView.builder来渲染所有可展开项,每个项都是ExpansionTile,然后在ExpansionTile的children中嵌套子ListView:

class NestedExpandableListView extends StatelessWidget {
  const NestedExpandableListView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Nested Expandable ListView')),
      body: ListView.builder(
        itemCount: parentItems.length,
        itemBuilder: (context, parentIndex) {
          return ExpansionTile(
            title: Text(parentItems[parentIndex].title),
            leading: const Icon(Icons.category),
            // 展开/收起时的回调(可选)
            onExpansionChanged: (isExpanded) {
              print('Group ${parentIndex+1} is ${isExpanded ? 'expanded' : 'collapsed'}');
            },
            children: [
              ListView.builder(
                // 关键1:让子ListView根据内容自动收缩高度
                shrinkWrap: true,
                // 关键2:禁用子ListView的滚动,避免和父列表滚动冲突
                physics: const NeverScrollableScrollPhysics(),
                itemCount: parentItems[parentIndex].children.length,
                itemBuilder: (context, childIndex) {
                  return ListTile(
                    title: Text(parentItems[parentIndex].children[childIndex]),
                    subtitle: Text('Subtitle for ${parentItems[parentIndex].children[childIndex]}'),
                    onTap: () {
                      // 子项点击逻辑
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('Tapped ${parentItems[parentIndex].children[childIndex]}')),
                      );
                    },
                  );
                },
              ),
            ],
          );
        },
      ),
    );
  }
}
关键细节说明
  • shrinkWrap: true:默认情况下,ListView会占用尽可能多的垂直空间,设置这个属性后,它只会包裹住实际内容的高度,避免出现布局溢出错误。
  • physics: const NeverScrollableScrollPhysics():如果不设置这个,子ListView会和父列表各自独立滚动,导致用户操作体验混乱。禁用子列表的滚动后,整个页面的滚动由外层列表统一控制,体验更流畅。
可选:让子列表独立滚动

如果你希望子列表在展开后可以自己滚动(比如子项数量很多),可以给子ListView设置固定高度,用SizedBox包裹,同时保留默认的滚动物理效果:

children: [
  SizedBox(
    height: 250, // 自定义子列表高度
    child: ListView.builder(
      physics: const AlwaysScrollableScrollPhysics(), // 启用子列表滚动
      itemCount: parentItems[parentIndex].children.length,
      itemBuilder: (context, childIndex) {
        return ListTile(
          title: Text(parentItems[parentIndex].children[childIndex]),
        );
      },
    ),
  ),
],

这样用户既可以滚动整个页面,也可以在展开的子列表内部滚动查看更多内容。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 21:22:48