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

Flutter如何高效处理大型列表?解决双ListView嵌套滚动卡顿问题

解决SingleChildScrollView嵌套多个ListView.builder卡顿问题

你的问题根源在于shrinkWrap: true——这个属性会让ListView强制计算所有子项的总高度,导致6000条联系人数据一次性全部渲染,自然会出现加载卡顿。下面给你两种解决办法,优先推荐第一种:

方案一:用CustomScrollView重构(性能最优)

CustomScrollView是Flutter专门用来组合多个滚动区域的组件,内部的Sliver系列组件都是按需懒加载的,只有当列表项进入视口才会被构建,完美解决一次性渲染的问题。

把原来的头部容器用SliverToBoxAdapter包裹,列表换成SliverList.separated(Flutter 3.7及以上版本支持),代码修改如下:

CustomScrollView(
  physics: const BouncingScrollPhysics(),
  slivers: [
    // 第一个头部
    SliverToBoxAdapter(
      child: Container(
        padding: EdgeInsets.symmetric(horizontal: 10.h),
        height: 25.h,
        width: displayWidth(context),
        color: PawColors().darkGreenColor,
        child: Align(
          alignment: Alignment.centerLeft,
          child: MediumSemiText(
            text: 'friends (${calls.pawFriends.length})',
          ),
        ),
      ),
    ),
    // 第一个好友列表
    SliverList.separated(
      itemCount: calls.pawFriends.length,
      separatorBuilder: (context, index) => Divider(
        color: PawColors().sawadColor,
        height: 5.h,
      ),
      itemBuilder: (context, index) => ListTile(
        title: SmallSemiText(
          colors: PawColors().darkGreenColor,
          text: calls.pawFriends[index]['Name'],
        ),
      ),
    ),
    // 第二个头部
    SliverToBoxAdapter(
      child: Container(
        padding: EdgeInsets.symmetric(horizontal: 10.h),
        height: 25.h,
        width: displayWidth(context),
        color: PawColors().darkGreenColor,
        child: Align(
          alignment: Alignment.centerLeft,
          child: MediumSemiText(
            text: 'invite (${calls.contacts.length})',
          ),
        ),
      ),
    ),
    // 第二个邀请列表
    SliverList.separated(
      itemCount: calls.contacts.length,
      separatorBuilder: (context, index) => const Divider(),
      itemBuilder: (context, index) => ListTile(
        title: SmallSemiText(
          text: calls.contacts[index].displayName,
          colors: PawColors().darkGreenColor,
        ),
      ),
    ),
  ],
)

这个方案不仅解决了卡顿问题,还能保持整个页面的统一滚动体验,是官方推荐的最佳实践。

方案二:保持原有结构但优化(仅作备选)

如果因为业务限制必须保留SingleChildScrollView嵌套结构,可以给ListView指定itemExtent属性——提前告诉ListView每个列表项的固定高度,避免它遍历所有子项计算总高度,能大幅减少初始化时的性能开销。

修改后的代码示例:

SingleChildScrollView(
  physics: const BouncingScrollPhysics(),
  child: Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      // 第一个头部
      Container(
        padding: EdgeInsets.symmetric(horizontal: 10.h),
        height: 25.h,
        width: displayWidth(context),
        color: PawColors().darkGreenColor,
        child: Align(
          alignment: Alignment.centerLeft,
          child: MediumSemiText(
            text: 'friends (${calls.pawFriends.length})',
          ),
        ),
      ),
      // 第一个好友列表
      Flexible(
        child: ListView.separated(
          shrinkWrap: true,
          physics: const NeverScrollableScrollPhysics(),
          itemExtent: 50.h, // 这里指定每个列表项的固定高度
          itemBuilder: (context, index) => ListTile(
            title: SmallSemiText(
              colors: PawColors().darkGreenColor,
              text: calls.pawFriends[index]['Name'],
            ),
          ),
          separatorBuilder: (context, index) => Divider(
            color: PawColors().sawadColor,
            height: 5.h,
          ),
          itemCount: calls.pawFriends.length,
        ),
      ),
      // 第二个头部
      Container(
        padding: EdgeInsets.symmetric(horizontal: 10.h),
        height: 25.h,
        width: displayWidth(context),
        color: PawColors().darkGreenColor,
        child: Align(
          alignment: Alignment.centerLeft,
          child: MediumSemiText(
            text: 'invite (${calls.contacts.length})',
          ),
        ),
      ),
      // 第二个邀请列表
      Flexible(
        child: ListView.separated(
          shrinkWrap: true,
          physics: const NeverScrollableScrollPhysics(),
          itemExtent: 50.h, // 指定固定高度
          itemCount: calls.contacts.length,
          separatorBuilder: (context, index) => const Divider(),
          itemBuilder: (context, index) => ListTile(
            title: SmallSemiText(
              text: calls.contacts[index].displayName,
              colors: PawColors().darkGreenColor,
            ),
          ),
        ),
      ),
    ],
  ),
)

注意:这种方式只是优化了计算过程,仍然会一次性渲染所有子项,卡顿问题只能缓解,无法彻底解决,所以优先选方案一。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 22:15:40