Flutter 基礎元件之 ListView
跟 Android 中的 ListView 差不多,就是一個可滾動的列表,這種元件在開發中是很常用的。
1 構造方法
ListView({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, double itemExtent, bool addAutomaticKeepAlives: true, bool addRepaintBoundaries: true, bool addSemanticIndexes: true, double cacheExtent, List<Widget> children: const [], int semanticChildCount })
根據顯式的 Widget 集合來建立一個 ListView。
ListView.builder({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, double itemExtent, @required IndexedWidgetBuilder itemBuilder, int itemCount, bool addAutomaticKeepAlives: true, bool addRepaintBoundaries: true, bool addSemanticIndexes: true, double cacheExtent, int semanticChildCount })
根據需要來自定義建立 ListView,該構造方法必傳的一個引數為 itemBuilder,它是一個 IndexedWidgetBuilder ,它的構造方法中會返回 index,可以根據這個 index 來讓 ListView 的子 Item 有不同的展示。
ListView.custom({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, double itemExtent, @required SliverChildDelegate childrenDelegate, double cacheExtent, int semanticChildCount })
建立一個自定義子模型的 ListView。
ListView.separated({Key key, Axis scrollDirection: Axis.vertical, bool reverse: false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap: false, EdgeInsetsGeometry padding, @required IndexedWidgetBuilder itemBuilder, @required IndexedWidgetBuilder separatorBuilder, @required int itemCount, bool addAutomaticKeepAlives: true, bool addRepaintBoundaries: true, bool addSemanticIndexes: true, double cacheExtent })
建立一個帶分隔的 ListView,這個分隔可以幫助我們實現分隔線的效果,它除了要傳入一個 itemBuilder 之外,還要傳入一個 separatorBuilder,這個就是分隔線。
2 常用屬性
childrenDelegate:自定義子模型時用到。
itemExtent:Item 的範圍,scrollDirection 為 Axis.vertical 時限制高度,scrollDirection 為 Axis.horizontal 限制寬度。
cacheExtent:預載入的區域。
controller:滑動監聽,值為一個 ScrollController 物件,這個屬性應該可以用來做下拉重新整理和上垃載入,後面詳細研究。
padding:整個 ListView 的內間距。
physics:設定 ListView 如何響應使用者的滑動行為,值為一個 ScrollPhysics 物件,它的實現類常用的有:
AlwaysScrollableScrollPhysics:總是可以滑動。
NeverScrollableScrollPhysics:禁止滾動。
BouncingScrollPhysics:內容超過一屏,上拉有回彈效果。
ClampingScrollPhysics:包裹內容,不會有回彈,感覺跟 AlwaysScrollableScrollPhysics 差不多。
primary:是否是與 PrimaryScrollController 關聯的主滾動檢視,若為 true 則 controller 必須為空。
reverse:Item 的順序是否反轉,若為 true 則反轉。
scrollDirection:ListView 的方向,為 Axis.vertical 表示縱向,為 Axis.horizontal 表示橫向。
shrinkWrap:不太明白。
itemCount:子 Item 數量,只有使用 new ListView.builder() 和 new ListView.separated() 構造方法的時候才能指定,其中 new ListView.separated() 是必須指定。
下面是一個設定了上述屬性的 Demo:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//是否顯示 debug 標籤
debugShowCheckedModeBanner: false,
title: "ListView",
home: Scaffold(
appBar: new AppBar(
title: new Text("ListView"),
),
body: new Container(
child: new MyListView1(),
),
),
);
}
}
class MyListView1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
List<Widget> widgetList = new List();
for (int i = 0; i < 100; i++) {
widgetList.add(new MyText("Item " + i.toString()));
}
ScrollController scrollController = new ScrollController();
scrollController.addListener(() {
print("scrollController--->" + scrollController.offset.toString());
});
return new ListView(
//Item 的範圍,scrollDirection 為 Axis.vertical 時限制高度,scrollDirection 為 Axis.horizontal 限制寬度
itemExtent: 30,
// shrinkWrap: true,
//預載入的區域
// cacheExtent: 0,
//滑動監聽,值為一個 ScrollController 物件
controller: scrollController,
// //內邊距
padding: EdgeInsets.all(10),
//設定 ListView 如何響應使用者的滑動行為,值為一個 ScrollPhysics 物件,它的實現類常用的有:
//AlwaysScrollableScrollPhysics:總是可以滑動
//NeverScrollableScrollPhysics:禁止滾動
//BouncingScrollPhysics:內容超過一屏,上拉有回彈效果
//ClampingScrollPhysics:包裹內容,不會有回彈,感覺跟 AlwaysScrollableScrollPhysics 差不多
physics: new BouncingScrollPhysics(),
//是否是與 PrimaryScrollController 關聯的主滾動檢視,若為 true 則 controller 必須為空
// primary: true,
//Item 的順序是否反轉,若為 true 則反轉
reverse: true,
//ListView 的方向,為 Axis.vertical 表示縱向,為 Axis.horizontal 表示橫向
// scrollDirection: Axis.vertical,
children: widgetList,
);
}
}
執行效果如下:
上面是用第一種構造方法建立的 ListView,接下來使用 Builder 來建立一下 ListView,它與普通的構造方法不同的是,普通構造方法傳入的是已經建立好的子元件集合,而 Builder 是先指定子元件的個數,然後在 itemBuilder 中可以知道子元件所屬的 position,根據這個 position 可以建立不同的子元件:
class MyListView2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
if (index.isOdd) {
return new Container(
padding: new EdgeInsets.all(15.0),
child: new Text(
"builder 奇數 Item " + index.toString(),
style:
new TextStyle(fontSize: 20.0, color: new Color(0xFFFF0000)),
),
);
} else {
return new Container(
padding: new EdgeInsets.all(15.0),
child: new Text(
"builder 偶數 Item " + index.toString(),
style:
new TextStyle(fontSize: 20.0, color: new Color(0xFF0000FF)),
),
);
}
},
);
}
}
執行效果如下:
其實使用 Builder 來構造 ListView 的話,也可以實現分隔線的效果,只需要根據 position 一個返回子元件,下一個返回分隔線元件即可:
class MyListView2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
if (index.isOdd) {
return Container(
padding: EdgeInsets.all(15.0),
child: Text(
"builder 奇數 Item " + index.toString(),
style:
new TextStyle(fontSize: 20.0, color: new Color(0xFFFF0000)),
),
);
} else {
return Divider(color: Color(0xFF000000),);
}
},
);
}
}
這樣寫出來會是這種效果:
雖然實現了分隔線效果,但是想必從 Item 上的文字就能看出問題,分隔線是佔了 Item 的位置的,所以如果要用這種方式來實現分隔線的話,子元件的長度會增大一倍,因為一半都給了分隔線,所以實現分隔線的話一般是使用 ListView.separated() 構造方法:
class MyListView3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
List<Widget> widgetList = new List();
for (int i = 0; i < 100; i++) {
widgetList.add(new MyText("Item " + i.toString()));
}
return new ListView.separated(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
print("itemBuilder--->" + index.toString());
return new Container(
padding: new EdgeInsets.all(15.0),
child: new Text(
"separated Item " + index.toString(),
style: new TextStyle(fontSize: 20.0, color: new Color(0xFF000000)),
),
);
},
separatorBuilder: (BuildContext context, int index) {
print("separatorBuilder--->" + index.toString());
return new Divider(
color: new Color(0xFF888888),
);
},
);
}
}
執行效果如下:
而且從列印中也可以看到分隔線並沒有佔據子元件空間,它的 position 跟子元件的 position 是一樣的:
還有一種構造方法 ListView.custom() 這個暫時還沒有去研究。水平方向的 ListView 很簡單,只需要將 scrollDirection 設定為 Axis.horizontal 就行,簡單貼個效果:
3 總結
ListView 在開發中用得非常多,通常的場景是通過網路請求到一堆資料,然後通過 ListView 分頁載入,這種下拉重新整理和上拉載入等今後學到網路請求時再研究,應該有前輩都造好輪子了,到時候看看自己能不能實現。