ListView的4種構造方式
預設建構函式(傳入 List children)
ListView(
children: <Widget>[
ListTile(title: Text("普通ListView")),
ListTile(
title: Text("ListView.build"),
onTap: () {
Navigator.pushNamed(context, '/listview_build');
}),
],
)
複製程式碼
適用場景:已知有限個Item的情況下
builder
ListView.builder(
itemBuilder: (context, index) => Text("Item $index"),
itemCount: 100)
複製程式碼
適用場景:長列表時採用builder模式,能提高效能。不是把所有子控制元件都構造出來,而是在控制元件viewport加上頭尾的cacheExtent這個範圍內的子Item才會被構造。在構造時傳遞一個builder,按需載入是一個慣用模式,能提高載入效能。
構造多種樣式的Item
abstract class ListItem {}
class HeadingItem implements ListItem {
final String heading;
HeadingItem(this.heading);
}
class MessageItem implements ListItem {
final String sender;
final String body;
MessageItem(this.sender, this.body);
}
ListView.builder(
itemBuilder: (context, index) {
final item = items[index];
if (item is HeadingItem) {
return ListTile(
title: Text(
item.heading,
style: Theme.of(context).textTheme.headline,
),
);
} else if (item is MessageItem) {
return ListTile(
title: Text(item.sender),
subtitle: Text(item.body),
);
}
},
itemCount: items.length))
複製程式碼
separated
ListView.separated(
itemBuilder: (context, index) {
return Text("Item $index");
},
separatorBuilder: (context, index) {
return Container(
color: Colors.grey,
height: 3,
);
},
itemCount: 100)
複製程式碼
適用場景:列表中需要分割線時,可以自定義複雜的分割線
custom
需要自定義SliverChildDelegate,各方法含義如下:
ListView.custom(childrenDelegate: CustomSliverChildDelegate())
class CustomSliverChildDelegate extends SliverChildDelegate {
/// 根據index構造child
@override
Widget build(BuildContext context, int index) {
// KeepAlive將把所有子控制元件加入到cache,已輸入的TextField文字不會因滾動消失
// 僅用於演示
return KeepAlive(
keepAlive: true,
child: TextField(decoration: InputDecoration(hintText: '請輸入')));
}
/// 決定提供新的childDelegate時是否需要重新build。在呼叫此方法前會做型別檢查,不同型別時才會呼叫此方法,所以一般返回true。
@override
bool shouldRebuild(SliverChildDelegate oldDelegate) {
return true;
}
/// 提高children的count,當無法精確知道時返回null。
/// 當 build 返回 null時,它也將需要返回一個非null值
@override
int get estimatedChildCount => 100;
/// 預計最大可滑動高度,如果設定的過小會導致部分child不可見,設定報錯
@override
double estimateMaxScrollOffset(int firstIndex, int lastIndex,
double leadingScrollOffset, double trailingScrollOffset) {
return 2500;
}
/// 完成layout後的回撥,可以通過該方法獲已完成佈局的檢視樹包括哪些子控制元件
@override
void didFinishLayout(int firstIndex, int lastIndex) {
print('didFinishLayout firstIndex=$firstIndex firstIndex=$lastIndex');
}
}
複製程式碼
適用場景:上面幾種模式基本可以滿足業務需求,如果你還想做一些其它設定(如列表的最大滾動範圍)或獲取滑動時每次佈局的子Item範圍,可以嘗試custom模式
ListView屬性含義
ListView的屬性
- itemExtent: 指定Item在滑動方向上的高度,用來提高滑動效能
繼承自父控制元件 BoxScrollView 的屬性
- padding: 用來設定BoxScrollView中子控制元件與父控制元件的間距
繼承自 ScrollView 的屬性
- ScrollDirection: 滾動方向
- reverse:決定滾動方向是否與閱讀方向一致
- scrollController:控制滾動的位置
- primary:當內容不足以滾動時,是否支援滾動;對於iOS系統還有一個效果:當使用者點選狀態列時是否滑動到頂部。
- ScrollPhysics:控制使用者滾動檢視的互動
- AlwaysScrollableScrollPhysics:列表總是可滾動的。在iOS上會有回彈效果,在android上不會回彈。那麼問題來了,如果primary設定為false(內容不足時不滾動),且 physics設定為AlwaysScrollableScrollPhysics,列表是否可以滑動?答案是可以,感興趣的可以試一下
- PageScrollPhysics:一般是給PageView控制元件用的滑動效果。如果listview設定的話在滑動到末尾時會有個比較大的彈起和回彈
- ClampingScrollPhysics:滾動時沒有回彈效果,同android系統的listview效果
- NeverScrollableScrollPhysics:就算內容超過列表範圍也不會滑動
- BouncingScrollPhysics:不論什麼平臺都會有回彈效果
- FixedExtentScrollPhysics:不適用於ListView,原因:需要指定scroller為FixedExtentScrollController,這個scroller只能用於ListWheelScrollViews
- shrinkWrap: scroll view在滑動方向上的高度是否由內容高度決定,false:則高度為滑動方向上的最大允許高度;如果在滑動方向上沒有設定約束,則這個欄位必須設定為true,否則會報錯。
- cacheExtent:可見區域的前後會有一定高度的空間去快取子控制元件,當滑動時就可以迅速呈現
- semanticChildCount:有含義的子控制元件的數量,如ListView會用children的長度,ListView.separated會用children長度的一半
用於構造SliverChildListDelegate的屬性
- addAutomaticKeepAlives:是否將子控制元件包裹在AutomaticKeepAlive控制元件內
- addRepaintBoundaries:true:是否將子控制元件包裹在 RepaintBoundary 控制元件內。用於避免列表滾動時的重繪,如果子控制元件重繪開銷很小時,比如子控制元件就是個色塊或簡短的文字,把這個欄位設定為false效能會更好
- addSemanticIndexes:是否把子控制元件包裝在IndexedSemantics裡,用來提供無障礙語義
最後
ListView最為app最常見的控制元件之一,掌握它的用法非常重要。本文主要給Flutter萌新們介紹ListView的幾種構造方式和引數含義,檢視控制元件的學習應多動手寫寫demo,真實體會各個引數的效果。
最後附上demo以供參考。
@akindone, 本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。