前言
在原生android和ios上关于列表的组件默认都会支持复用和懒加载来提升性能和用户体验,在flutter中也不例外。flutter中提供的常用的列表组件例如ListView、GridView等也都支持复用和懒加载。在正常使用或者说不复杂的布局中,我们不需要去关心这些列表组件是否正常使用了复用和懒加载,但是在复杂(多种嵌套)或有大量数据交互的界面中,如果列表没有使用复用和懒加载那么带来的性能问题将是一个灾难。
懒加载测试
简单布局中测试
首先我们来看看简单布局下flutter中列表懒加载的表现:
import 'package:flutter/material.dart';
class ListPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return ListPageState();
}
}
class ListPageState extends State<ListPage> {
int count = 60;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: InkWell(
child: Text('List Test'),
onTap: () {},
),
),
body: ListView.builder(
itemBuilder: (context, index) {
print('this is index = $index');
return Container(
height: 100,
decoration: BoxDecoration(border: Border.all(color: Colors.purple, width: 1)),
child: Center(child: Text('$index')),
);
},
itemCount: 60,
),
);
}
}
在我们刚打开页面时,一共只加载了8个布局,而页面上我们能看见的只有5个,这个是因为列表会预加载超出Viewport(不知道这个是什么同学,可以先去学习一下)的几个布局。当我们往下滑动的时候,就会一个一个将我们的布局加载出来。
在实际开发中使用列表组件则有可能是在多层的嵌套当中使用,下面我们来看看在嵌套布局中的列表组件的表现:
嵌套布局中测试
我们在之前简单布局的基础上加上SingleChildScrollView和Column的嵌套。并且为了使Listview正常显示还必须为其添加 shrinkWrap: true和physics: NeverScrollableScrollPhysics()两条属性。
import 'package:flutter/material.dart';
class ListPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return ListPageState();
}
}
class ListPageState extends State<ListPage> {
int count = 60;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: InkWell(
child: Text('List Test'),
onTap: () {
setState(() {});
},
),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
height: 50,
alignment: Alignment.center,
child: Text('this is start'),
),
ListView.builder(
itemBuilder: (context, index) {
print('this is index = $index');
return _buildItem(index);
},
itemCount: 60,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
),
Container(
height: 50,
alignment: Alignment.center,
child: Text('this is end'),
),
],
),
),
);
}
Widget _buildItem(int index) {
return Container(
height: 100,
decoration: BoxDecoration(border: Border.all(color: Colors.purple, width: 1)),
child: Center(child: Text('$index')),
);
}
}
从效果图中可以看到,当我们打开页面时Listview将全部的item瞬间加载出来了,而不是像之前一样在滑动时一条一条的加载。在多次测试中,无论是ListView还是GridView只要是设置了shrinkWrap: true属性,都没有了懒加载的效果了。由此可见,这样布局中的Listview并没有使用懒加载的策略。试想一下,如果有超过上千个item,或者每个item中又存在复杂的布局和较多的数据,那么这将是一个非常耗费性能的布局。
解决方案
使用Sliver组件
Sliver这个家族中有众多的组件,与ListView和GridVIew对应的有SliverList和SliverGrid,他们的用法都相差无几。但是他们可以在嵌套中支持懒加载的使用。我们改变一下之前的布局重新来看看效果。
代码中我同时使用了SliverList和SliverGrid,并且给他们各自加上了一个标题。
import 'package:flutter/material.dart';
class ListPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return ListPageState();
}
}
class ListPageState extends State<ListPage> {
int count = 60;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: InkWell(
child: Text('List Test'),
onTap: () {
setState(() {});
},
),
),
body: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate([
Container(
height: 50,
alignment: Alignment.center,
child: Text('this is SliverList'),
),
]),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
print('this is SliverList index = $index');
return _buildSliverListItem(index);
}, childCount: 20),
),
SliverList(
delegate: SliverChildListDelegate([
Container(
height: 50,
alignment: Alignment.center,
child: Text('this is SliverGrid'),
),
]),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 20,
crossAxisSpacing: 20,
childAspectRatio: 0.5,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
print('this is SliverGrid index = $index');
return _buildSliverGridItem(index);
},
childCount: 20,
),
),
SliverList(
delegate: SliverChildListDelegate([
Container(
height: 50,
alignment: Alignment.center,
child: Text('this is end'),
),
]),
),
],
),
);
}
Widget _buildSliverListItem(int index) {
return Container(
height: 100,
decoration: BoxDecoration(border: Border.all(color: Colors.purple, width: 1)),
child: Center(child: Text('$index')),
);
}
Widget _buildSliverGridItem(int index) {
return Container(
decoration: BoxDecoration(border: Border.all(color: Colors.blue, width: 1)),
child: Center(child: Text('$index')),
);
}
}
在动图中我们可以看到无论是SliverList还是SliverGrid他们都使用了懒加载的策略。
总结
在Flutter开发中的列表组件,设置了shrinkWrap: true属性的都不具有懒加载功能,解决方案是使用Sliver家族中的列表组件对其进行替换。
虽说这样的确解决了问题,但是这样写布局的确是很麻烦的事情。如果有更好的解决办法,还希望各位大佬多多指教!