1. 程式人生 > >Flutter 基礎元件之 ListView

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 分頁載入,這種下拉重新整理和上拉載入等今後學到網路請求時再研究,應該有前輩都造好輪子了,到時候看看自己能不能實現。