Flutter ListView.builder懒加载失效问题

955 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

前言

ListView.build 组件创建的列表是基于Sliver的懒加载创建的,会根据页面需要的数据进行加载和预加载数据,能够有效提升项目性能。但在一些特殊的情况下 ListView.build 组件会失去懒加载的功能,导致页面性能丢失的问题。

ListView.build 简介

listView.builder 组件具备可滚动和无限高度特性,仅对那些实际可见的子级调用构建器,适用于具有大量(或无限)子级的列表视图。

Creates a scrollable, linear array of widgets that are created on demand
This constructor is appropriate for list views with a large (or infinite number of children because the builder is called only for those children) that are actually visible.

ListView.builder(
        itemCount: 100,
        itemBuilder: (context, index) {
          print(index);
          return Container(
            height: 200,
            color: Colors.orange[index % 5 * 100],
            child: Center(child: Text('$index')),
          );
        },
),
/// 控制台输出结果,只输出页面加载可见的数据和预加载部分数据
flutter: init called  screen_utils
flutter: 0
flutter: 1
flutter: 2
flutter: 3
flutter: 4
flutter: 5
Reloaded 0 libraries in 368ms.

懒加载失效

在实际的项目开发过程中, listView.builder 组件往往不会单独出现,它会存在在多种嵌套或有大量数据交互的界面里。根据实际需求的不同, listView.builder 可能需要嵌套在其他滚动组件内部(如ListView,SingleChildScrollView等),这样会导致页面出现报错的情况:" Dart Unhandled Exception: Vertical viewport was given unbounded height. ",因为 listView.builder 组件具有无限高度的特性,不可以被套用在另一个无限高度的组件内。为确保页面正常显示,需要修改 listView.builder 两个属性值:

shrinkWrap: true, ///在滑动方向上的高度是否由内容高度决定
physics: const NeverScrollableScrollPhysics(), ///控制用户滚动视图的交互

存在问题:重新运行项目后页面能够正常加载数据,但是通过控制台打印数据能够发现,由于修改了shrinkWrap physics这两个属性,在滑动页面内数据会一次性加载完成,ListView.builder会失去懒加载的功能;如果页面存在大量数据交互的话,这将会造成性能的严重损耗。

ListView(
        children: [
          const FlutterLogo(size: 100), /// 页面可能存在其他组件
          ListView.builder(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            itemCount: 100,
            itemBuilder: (context, index) {
              print(index);
              return Container(
                height: 200,
                color: Colors.orange[index % 5 * 100],
                child: Center(child: Text('$index')),
              );
            },
          ),
        ],
      ),
/// 控制台输出结果,数据一次性加载完成
flutter: init called  screen_utils
flutter: 0
flutter: 1
flutter: 2
flutter: 3
flutter: 4
······
flutter: 96
flutter: 97
flutter: 98
flutter: 99
Reloaded 1 of 2339 libraries in 704ms.

优化方案

在套用多层滚动组件的页面,可以使用Sliver系列组件代替普通组件进行布局。 SliverList 嵌套在 CustomScrollView 组件内可以实现懒加载功能,对页面加载性能进行优化。

/// [CustomScrollView] 允许直接提供 [slivers] 以创建各种滚动效果
CustomScrollView(    
        slivers: [
          /// [SliverToBoxAdapter] 是一个基本的 sliver,通过它可以在 [slivers] 组件内书写普通组件。
          const SliverToBoxAdapter(child: FlutterLogo(size: 100)), 
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                print(index);
                return Container(
                  height: 200,
                  color: Colors.orange[index % 5 * 100],
                  child: Center(child: Text('$index')),
                );
              },
              childCount: 100,
            ),
          ),
        ],
      )
/// 控制台输出结果,恢复懒加载,只输出页面加载可见的数据和预加载部分数据
flutter: init called  screen_utils
flutter: 0
flutter: 1
flutter: 2
flutter: 3
flutter: 4
Reloaded 0 libraries in 200ms.

小结

ListView.build 组件大部分情况下都能够实现我们的需求,但是在部分情况下容易失去懒加载功能,我们可以使用 SliverList 组件代替 ListView.build 进行布局。在Sliver大家族中,除 SliverList 以外,还有着许许多多强大的功能组件,可以实现许多复杂的滑动嵌套布局,有待进一步学习。