簡介
ListView是包含多個child元件的widget,在ListView中所有的child widget都是以list的形式來呈現的,你可以自定義List的方向,但是和GridView不同的是ListView中的每一個List裡面都只包含一個widget。
今天我們來詳細瞭解一下ListView的底層實現和具體的應用。
ListView詳解
和GridView一樣,ListView也是繼承自ScrollView,表示它是一個可以滾動的View。
具體而言,ListView首先繼承自BoxScrollView:
class ListView extends BoxScrollView
然後BoxScrollView又繼承自ScrollView:
abstract class BoxScrollView extends ScrollView
ListView中的特有屬性
首先我們來看下ListView中的特有屬性,ListView和它的父類相比,多了三個屬性,分別是itemExtent,prototypeItem和childrenDelegate。
其中itemExtent是一個double型別的資料,如果給定的是一個非空值,那麼表示的是child在scroll方向的extent大小。這個屬性主要用來控制children的extend資訊,這樣每個child就不需要自行來判斷自己的extend。
使用itemExtent的好處在於,ListView可以統一的在滾動機制上進行最佳化,從而提升效能表現。
prototypeItem是一個widget,從名字就可以看出,這個一個prototype的widget,也就是說是一個原型,其他的child可以參照這個原型widget的大小進行extent的設定。
ListView中的最後一個自定義屬性是childrenDelegate,這個childrenDelegate和GridView中的含義是一樣的,用來生成ListView中child。
之前我們在講解GirdView的時候有提到過,GirdView中還有一個自定義的屬性叫做gridDelegate,這個gridDelegate是一個SliverGridDelegate的例項,用來控制子元件在GridView中的佈局。
因為ListView的子元件的佈局是已經確定的,所以就不再需要gridDelegate了,這是ListView和GridView的一大區別。
ListView作為一個繼承的類,需要實現一個buildChildLayout的方法:
@override Widget buildChildLayout(BuildContext context) { if (itemExtent != null) { return SliverFixedExtentList( delegate: childrenDelegate, itemExtent: itemExtent!, ); } else if (prototypeItem != null) { return SliverPrototypeExtentList( delegate: childrenDelegate, prototypeItem: prototypeItem!, ); } return SliverList(delegate: childrenDelegate); }
這個方法的實現邏輯和我們之前講到的三個屬性是相關聯的,在buildChildLayout中,如果itemExtent有值的話,因為itemExtent本身就是一個固定值,所以返回的是SliverFixedExtentList。
如果itemExtent沒有設定,並且prototypeItem有值的話,返回的是一個SliverPrototypeExtentList。
最後,如果itemExtent和prototypeItem都沒有設定的話,返回的是一個SliverList物件。
ListView的建構函式
和GridView一樣,為了滿足我們的多樣性的設計需求,ListView也提供了多個建構函式。
首先我們來看下ListView的最基本的建構函式:
ListView({ Key? key, Axis scrollDirection = Axis。vertical, bool reverse = false, ScrollController? controller, bool? primary, ScrollPhysics? physics, bool shrinkWrap = false, EdgeInsetsGeometry? padding, this。itemExtent, this。prototypeItem, bool addAutomaticKeepAlives = true, bool addRepaintBoundaries = true, bool addSemanticIndexes = true, double? cacheExtent, List
這裡itemExtent和prototypeItem這兩個屬性是外部傳入的,childrenDelegate是透過其他的引數構造而來的:
childrenDelegate = SliverChildListDelegate( children, addAutomaticKeepAlives: addAutomaticKeepAlives, addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, ),
ListView中所有的child元件都在List Widget的children中。
這個預設的建構函式,適用於child比較少的情況,因為需要一次傳入所有的child元件到list中,所以對效能的影響還是挺大的,並且傳入的child是不可變的。
如果child比較多的情況下,就需要使用到其他的構造函數了,比如 ListView。builder。
ListView。builder使用的是builder模式來構建child元件,具體而言他的childrenDelegate實現如下:
childrenDelegate = SliverChildBuilderDelegate( itemBuilder, childCount: itemCount, addAutomaticKeepAlives: addAutomaticKeepAlives, addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, ),
這裡的childrenDelegate是一個SliverChildBuilderDelegate,透過傳入itemBuilder和總的itemCount就可以實現動態建立child的功能。
在ListView的實際使用過程中,為了頁面好看或者更有區分度,我們一般會在list的item中新增一些分隔符separator,為了自動化實現這個功能,ListView提供了一個ListView。separated的建構函式,用來提供list item中間的分隔符。
ListView。separated需要傳入兩個IndexedWidgetBuilder,分別是itemBuilder和separatorBuilder。
下面是childrenDelegate的具體實現:
childrenDelegate = SliverChildBuilderDelegate( (BuildContext context, int index) { final int itemIndex = index ~/ 2; final Widget widget; if (index。isEven) { widget = itemBuilder(context, itemIndex); } else { widget = separatorBuilder(context, itemIndex); assert(() { if (widget == null) { throw FlutterError(‘separatorBuilder cannot return null。’); } return true; }()); } return widget; }, childCount: _computeActualChildCount(itemCount), addAutomaticKeepAlives: addAutomaticKeepAlives, addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, semanticIndexCallback: (Widget _, int index) { return index。isEven ? index ~/ 2 : null; }, ),
可以看到,如果index是even的話就會使用itemBuilder生成一個widget,如果index是odd的話,就會使用separatorBuilder來生成一個separator的widget。
最後,ListView還有一個更加開放的建構函式ListView。custom,custom和其他建構函式不同的地方在於他可以自定義childrenDelegate,從而提供了更多的擴充套件空間。
ListView的使用
有了上面的建構函式,我們可以很方便的根據自己的需要來使用ListView,下面是一個簡單的使用圖片做child的例子:
class ListViewApp extends StatelessWidget{ const ListViewApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return ListView。builder( itemCount: 5, itemBuilder: (BuildContext context, int index) { return Container( constraints: const BoxConstraints(maxWidth:100,maxHeight: 100), child: Image。asset(‘images/head。jpg’) ); }, ); }}
上面的例子中,我們使用的是ListView。builder建構函式,返回的Widget中,中的widget個數是5,每個item是由itemBuilder來生成的。
這裡我們把Image封裝在一個Container中,並且為Container設定了一個constraints來控制圖片的大小。
最終生成的介面如下:
上面的例子中,item之間是沒有分隔符的,我們可以講上面的例子進行稍微的修改一下,使用ListView。separated來構造ListView,如下所示:
class ListViewSeparatedApp extends StatelessWidget{ @override Widget build(BuildContext context) { return ListView。separated( itemCount: 10, separatorBuilder: (BuildContext context, int index) => const Divider(), itemBuilder: (BuildContext context, int index) { return Container( constraints: const BoxConstraints(maxWidth:50,maxHeight: 50), child: Image。asset(‘images/head。jpg’) ); }, ); }}
這裡我們需要傳入separatorBuilder來作為分隔符,為了簡單起見,我們直接使用了Divider這個widget。
最後生成的介面如下:
總結
以上就是ListView的介紹和基本的使用。
本文的例子:https://github。com/ddean2009/learn-flutter。git