前言
它可以沿一個方向線性排布所有子組件,並且它也可以支持基於Sliver的延遲構建模型。
接口描述
ListView({
Key key,
// 可滾動widget公共參數
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
EdgeInsetsGeometry padding,
// ListView各個構造函數的共同參數
// 該參數如果不為null,則會強制children的“長度”為itemExtent的值;這里的“長度”是指滾動方向上子組件的長度,也就是說如果滾動方向是垂直方向,則itemExtent代表子組件的高度;如果滾動方向為水平方向,則itemExtent就代表子組件的寬度。在ListView中,指定itemExtent比讓子組件自己決定自身長度會更高效,這是因為指定itemExtent后,滾動系統可以提前知道列表的長度,而無需每次構建子組件時都去再計算一下,尤其是在滾動位置頻繁變化時(滾動系統需要頻繁去計算列表高度)。
this.itemExtent,
// 該屬性表示是否根據子組件的總長度來設置ListView的長度,默認值為false 。默認情況下,ListView的會在滾動方向盡可能多的占用空間。當ListView在一個無邊界(滾動方向上)的容器中時,shrinkWrap必須為true。
bool shrinkWrap = false,
// 該屬性表示是否將列表項(子組件)包裹在AutomaticKeepAlive 組件中;典型地,在一個懶加載列表中,如果將列表項包裹在AutomaticKeepAlive中,在該列表項滑出視口時它也不會被GC(垃圾回收),它會使用KeepAliveNotification來保存其狀態。如果列表項自己維護其KeepAlive狀態,那么此參數必須置為false。
bool addAutomaticKeepAlives = true,
// 該屬性表示是否將列表項(子組件)包裹在RepaintBoundary組件中。當可滾動組件滾動時,將列表項包裹在RepaintBoundary中可以避免列表項重繪,但是當列表項重繪的開銷非常小(如一個顏色塊,或者一個較短的文本)時,不添加RepaintBoundary反而會更高效。和addAutomaticKeepAlive一樣,如果列表項自己維護其KeepAlive狀態,那么此參數必須置為false。
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
// 子widget列表
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
})
代碼示例
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
// ListView 默認構造函數
/* 默認構造函數有一個children參數,它接受一個Widget列表(List)。這種方式適合只有少量的子組件的情況,因為這種方式需要將所有children都提前創建好(這需要做大量工作),而不是等到子widget真正顯示的時候再創建,也就是說通過默認構造函數構建的ListView沒有應用基於Sliver的懶加載模型。實際上通過此方式創建的ListView和使用SingleChildScrollView+Column的方式沒有本質的區別。*/
class ListViewTest1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView 默認構造函數'),
),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
children: <Widget>[
const Text('I\'m haha'),
const Text('biubiubiu`'),
const Text('qiuqiuqiu`'),
const Text('dindindin`'),
const Text('I\'m haha'),
const Text('biubiubiu`'),
const Text('qiuqiuqiu`'),
const Text('dindindin`'),
const Text('I\'m haha'),
const Text('biubiubiu`'),
const Text('qiuqiuqiu`'),
const Text('dindindin`'),
],
),
);
}
}
// ListView.builder
/* ListView.builder適合列表項比較多(或者無限)的情況,因為只有當子組件真正顯示的時候才會被創建,也就說通過該構造函數創建的ListView是支持基於Sliver的懶加載模型的。*/
class ListViewTest2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView 默認構造函數'),
),
body: ListView.builder(
// 列表項的數量,如果為null,則為無限列表
itemCount: 100,
itemExtent: 50.0,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"),);
}
)
);
}
}
// ListView.separated
/*ListView.separated可以在生成的列表項之間添加一個分割組件,它比ListView.builder多了一個separatorBuilder參數,該參數是一個分割組件生成器。 */
class ListViewTest3 extends StatelessWidget {
Widget build(BuildContext context) {
// 下划線widget預定義以供復用
Widget divider1 = Divider(color: Colors.blue,);
Widget divider2 = Divider(color: Colors.green,);
return Scaffold(
appBar: AppBar(
title: Text('ListView.separated'),
),
body: ListView.separated(
// 列表項構造器
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('$index'),);
},
// 分割器構造器
separatorBuilder: (BuildContext context, int index) {
return index%2==0?divider1:divider2;
},
itemCount: 100
),
);
}
}
// 實例:無限加載列表
class InfiniteListView extends StatefulWidget {
@override
_InfiniteListViewState createState() => _InfiniteListViewState();
}
class _InfiniteListViewState extends State<InfiniteListView> {
// 表尾標記
static const loadingTag = "##loadding##";
var _words = <String>[loadingTag];
@override
void initState() {
super.initState();
_retrieveData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView.separated'),
),
body: ListView.separated(
itemBuilder: (context, index) {
// 如果到了表尾
if (_words[index] == loadingTag) {
// 不足100條,繼續獲取數據
if (_words.length - 1 < 100) {
// 獲取數據
_retrieveData();
// 加載時顯示loading
return Container(
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
child: SizedBox(
width: 24.0,
height: 24.0,
child: CircularProgressIndicator(strokeWidth: 2.0,),
),
);
} else {
// 已經加載了100條數據,不再獲取數據
return Container(
alignment: Alignment.center,
padding: EdgeInsets.all(16.0),
child: Text('已經到我的底線了ฅʕ•̫͡•ʔฅ'),
);
}
}
// 顯示單詞列表項
return ListTile(title: Text(_words[index]),);
},
separatorBuilder: (context, index) => Divider(height: .0,),
itemCount: _words.length,
),
);
}
// 模擬從數據源異步獲取數據
void _retrieveData() {
Future.delayed(Duration(seconds: 2)).then((e) {
_words.insertAll(
_words.length - 1,
// 每次生成20個單詞
generateWordPairs().take(20).map((e) => e.asPascalCase).toList()
);
setState(() {
// 重新構建列表
});
});
}
}
總結
不同的構造函數對應了不同的列表項生成模型,如果需要自定義列表項生成模型,可以通過ListView.custom來自定義,它需要實現一個SliverChildDelegate用來給ListView生成列表項組件。
