前言
在原生android和ios上關于列表的組件默認都會支持復用和懶加載來提升性能和用戶體驗,在flutter中也不例外。flutter中提供的常用的列表組件例如ListView、GridView等也都支持復用和懶加載。在正常使用或者說不復雜的布局中,我們不需要去關心這些列表組件是否正常使用了復用和懶加載,但是在復雜(多種嵌套)或有大量數據交互的界面中,如果列表沒有使用復用和懶加載那么帶來的性能問題將是一個災難。
懶加載測試
簡單布局中測試
首先我們來看看簡單布局下flutter中列表懶加載的表現(xiàn):
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,
),
);
}
}

GIF 2020-12-21 16-46-22.gif
在我們剛打開頁面時,一共只加載了8個布局,而頁面上我們能看見的只有5個,這個是因為列表會預加載超出Viewport(不知道這個是什么同學,可以先去學習一下)的幾個布局。當我們往下滑動的時候,就會一個一個將我們的布局加載出來。
在實際開發(fā)中使用列表組件則有可能是在多層的嵌套當中使用,下面我們來看看在嵌套布局中的列表組件的表現(xiàn):
嵌套布局中測試
我們在之前簡單布局的基礎上加上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')),
);
}
}

GIF 2020-12-21 17-21-15.gif
從效果圖中可以看到,當我們打開頁面時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')),
);
}
}

GIF 2020-12-21 17-42-55.gif
在動圖中我們可以看到無論是SliverList還是SliverGrid他們都使用了懶加載的策略。
總結
在Flutter開發(fā)中的列表組件,設置了shrinkWrap: true屬性的都不具有懶加載功能,解決方案是使用Sliver家族中的列表組件對其進行替換。
雖說這樣的確解決了問題,但是這樣寫布局的確是很麻煩的事情。如果有更好的解決辦法,還希望各位大佬多多指教!