Flutter學(xué)習(xí)之九 ListView

極端很容易,平衡才是最難的。

???? Flutter學(xué)習(xí)之八 Container

前言

Flutter中的ListView的地位,就好比于iOS中的UITableView,算是最常用的可滾動(dòng)組件之一,它可以沿一個(gè)方向線性排布所有子組件,并且它也支持列表項(xiàng)懶加載(在需要時(shí)才會(huì)創(chuàng)建)。

默認(rèn)構(gòu)造函數(shù)

我們看看ListView的默認(rèn)構(gòu)造函數(shù)定義:

ListView({
  ...  
  //可滾動(dòng)widget公共參數(shù)
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController? controller,
  bool? primary,
  ScrollPhysics? physics,
  EdgeInsetsGeometry? padding,
  
  //ListView各個(gè)構(gòu)造函數(shù)的共同參數(shù)  
  double? itemExtent,
  Widget? prototypeItem, //列表項(xiàng)原型,后面解釋
  bool shrinkWrap = false,
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  double? cacheExtent, // 預(yù)渲染區(qū)域長度
    
  //子widget列表
  List<Widget> children = const <Widget>[],
})

上面的可滑動(dòng)公共參數(shù)就不再贅述了,下面是ListView各個(gè)構(gòu)造函數(shù)(ListView有多個(gè)構(gòu)造函數(shù))的共同參數(shù),我們重點(diǎn)來看看這些參數(shù):

  • itemExtent:該參數(shù)如果不為null,則會(huì)強(qiáng)制children的“長度”為itemExtent的值;這里的“長度”是指滾動(dòng)方向上子組件的長度,也就是說如果滾動(dòng)方向是垂直方向,則itemExtent代表子組件的高度;如果滾動(dòng)方向?yàn)樗椒较?,則itemExtent就代表子組件的寬度。在ListView中,指定itemExtent比讓子組件自己決定自身長度會(huì)有更好的性能,這是因?yàn)橹付?code>itemExtent后,滾動(dòng)系統(tǒng)可以提前知道列表的長度,而無需每次構(gòu)建子組件時(shí)都去再計(jì)算一下,尤其是在滾動(dòng)位置頻繁變化時(shí)(滾動(dòng)系統(tǒng)需要頻繁去計(jì)算列表高度),和原生很像。

  • prototypeItem:如果我們知道列表中的所有列表項(xiàng)長度都相同但不知道具體是多少,這時(shí)我們可以指定一個(gè)列表項(xiàng),該列表項(xiàng)被稱為prototypeItem(列表項(xiàng)原型)。指定 prototypeItem后,可滾動(dòng)組件會(huì)在 layout 時(shí)計(jì)算一次它延主軸方向的長度,這樣也就預(yù)先知道了所有列表項(xiàng)的延主軸方向的長度,所以和指定 itemExtent一樣,指定prototypeItem會(huì)有更好的性能。注意,itemExtentprototypeItem互斥,不能同時(shí)指定它們。

  • shrinkWrap:該屬性表示是否根據(jù)子組件的總長度來設(shè)置ListView的長度,默認(rèn)值為false 。默認(rèn)情況下,ListView會(huì)在滾動(dòng)方向盡可能多的占用空間。當(dāng)ListView在一個(gè)無邊界(滾動(dòng)方向上)的容器中時(shí),shrinkWrap必須為true

  • addAutomaticKeepAlives:該屬性比較復(fù)雜,以后再說。

  • addRepaintBoundaries:該屬性表示是否將列表項(xiàng)(子組件)包裹在RepaintBoundary組件中。RepaintBoundary 讀者可以先簡單理解為它是一個(gè)”繪制邊界“,將列表項(xiàng)包裹在RepaintBoundary中可以避免列表項(xiàng)不必要的重繪,但是當(dāng)列表項(xiàng)重繪的開銷非常?。ㄈ缫粋€(gè)顏色塊,或者一個(gè)較短的文本)時(shí),不添加RepaintBoundary反而會(huì)更高效(具體原因會(huì)在本書后面 Flutter 繪制原理相關(guān)章節(jié)中介紹)。如果列表項(xiàng)自身來維護(hù)是否需要添加繪制邊界組件,則此參數(shù)應(yīng)該指定為false。

默認(rèn)構(gòu)造函數(shù)有一個(gè)children參數(shù),它接受一個(gè)Widget列表(List<Widget>)。這種方式適合只有少量的子組件數(shù)量已知且比較少的情況,反之則應(yīng)該使用ListView.builder 按需動(dòng)態(tài)構(gòu)建列表項(xiàng)。

注意:雖然這種方式將所有children一次性傳遞給 ListView,但子組件仍然是在需要時(shí)才會(huì)加載(build(如有)、布局、繪制),也就是說通過默認(rèn)構(gòu)造函數(shù)構(gòu)建的ListView 也是基于 Sliver的列表懶加載模型。

舉個(gè)栗子:

_listView() {
  return ListView(
    shrinkWrap: true,
    padding: const EdgeInsets.all(20.0),
    children: const <Widget>[
      Text('張三'),
      Text('李四'),
      Text('王五'),
      Text('趙六'),
    ],
  );
}

效果如下:


ListView.png

可以看到,雖然使用默認(rèn)構(gòu)造函數(shù)創(chuàng)建的列表也是懶加載的,但我們還是需要提前將Widget創(chuàng)建好,等到真正需要加載的時(shí)候才會(huì)對(duì) Widget 進(jìn)行布局和繪制。

ListView.builder

ListView.builder適合列表項(xiàng)比較多或者列表項(xiàng)不確定的情況,下面看一下ListView.builder的核心參數(shù)列表:

ListView.builder({
  // ListView公共參數(shù)已省略  
  ...
  required IndexedWidgetBuilder itemBuilder,
  int itemCount,
  ...
})

+itemBuilder:它是列表項(xiàng)的構(gòu)建器,類型為IndexedWidgetBuilder,返回值為一個(gè)widget。當(dāng)列表滾動(dòng)到具體的index位置時(shí),會(huì)調(diào)用該構(gòu)建器構(gòu)建列表項(xiàng)。

  • itemCount:列表項(xiàng)的數(shù)量,如果為null,則為無限列表

舉個(gè)栗子:

_listViewBuild() {
  return ListView.builder(
      itemCount: 100,
      itemExtent: 50.0, //強(qiáng)制高度為50.0
      itemBuilder: (BuildContext context, int index) {
        return ListTile(title: Text("_listViewBuild $index"));
      });
}

效果如下:


ListViewBuilder.gif

ListView.separated

ListView.separated可以在生成的列表項(xiàng)之間添加一個(gè)分割組件,它比ListView.builder多了一個(gè)separatorBuilder參數(shù),該參數(shù)是一個(gè)分割組件生成器。

舉個(gè)栗子:

_listViewSeparated() {
  // widget預(yù)定義以供復(fù)用。
  Widget blue = Container(color: Colors.blue, height: 10);
  Widget red = Container(color: Colors.red, height: 10);
  return ListView.separated(
    itemCount: 100,
    //列表項(xiàng)構(gòu)造器
    itemBuilder: (BuildContext context, int index) {
      return ListTile(title: Text("$index"));
    },
    //分割器構(gòu)造器
    separatorBuilder: (BuildContext context, int index) {
      return index % 2 == 0 ? blue : red;
    },
  );
}

效果如下:


_listViewSeparated.gif

當(dāng)然還有一個(gè)場景就是,每個(gè)item之間有一定的間距,也可以用這個(gè)實(shí)現(xiàn)。

總結(jié)

  • 我在開發(fā)中一般使用ListView.builder,里面的itemBuilder對(duì)應(yīng)的原生的cell,然后自定義cell就行了,用起來還是比較舒服的。
  • 如果item之間有分割線或是間距的,ListView.separated當(dāng)首選。

后記

常見的列表頁面ListView一般都能搞定,其對(duì)應(yīng)iOS 原生的UITableView。那原生的UICollectionView呢,在Flutter中要使用GridView,這個(gè)以后再寫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容