1. 程式人生 > >Flutter: 圖解 ListView 的多種繫結方式

Flutter: 圖解 ListView 的多種繫結方式

   小菜昨天剛學習了一下底部狀態列 BottomNavigationBar 的基本使用方法,今天學習一下 ListView 的基本用法。
      小菜覺得 Flutter 中 ListView 這個控制元件很強大,它兼顧了 Android 中的 ScrollView 和 ListView 兩個控制元件的效果,既可以當列表用也可以充當可滑動佈局。小菜今天主要測試作為普通列表時的基本用法。

                              效果圖1.jpg

列表 item -> ListTile

      Flutter 很貼心的提供了一種常見的列表 item 樣式,可以包括前後圖示以及大小標題的樣式;小菜特意瞭解了一下 ListTile 的基本屬性,如下:

const ListTile({
    Key key,
    this.leading,              // item 前置圖示
    this.title,                // item 標題
    this.subtitle,             // item 副標題
    this.trailing,             // item 後置圖示
    this.isThreeLine = false,  // item 是否三行顯示
    this.dense,                // item 直觀感受是整體大小
    this
.contentPadding, // item 內容內邊距 this.enabled = true, this.onTap, // item onTap 點選事件 this.onLongPress, // item onLongPress 長按事件 this.selected = false, // item 是否選中狀態 })

     Tips: 小菜對 dense 屬性理解不是很好,直觀的感覺是 dense 為 true 時整體會小一些,文字更為明顯,就像整體解析度變高;如下圖:

                           dense 為 false.jpg

列表 -> ListView

      Flutter 中 ListView 用法與 Android 中類似,首先新增資料,之後繫結列表;Flutter 中繫結列表有四種方式,分別是 預設 List / ListView.builder / ListView.separated / ListView.custom;小菜主要對前三種方式逐一測試;如圖:

無論是用那種繫結資料的方式首先第一步都要新增資料,小菜測試基本樣式包括 item 前置圖示(leading)、標題文字(title)、後置圖示(trailing),並設定了基本的 onTap() 方法;如下:

List<String> strItems = <String>[
    '圖示 -> keyboard', '圖示 -> print',
    '圖示 -> router', '圖示 -> pages',
    '圖示 -> zoom_out_map', '圖示 -> zoom_out',
    '圖示 -> youtube_searched_for', '圖示 -> wifi_tethering',
    '圖示 -> wifi_lock', '圖示 -> widgets',
    '圖示 -> weekend', '圖示 -> web',
    '圖示 -> accessible', '圖示 -> ac_unit',
];

List<Icon> iconItems = <Icon>[
    new Icon(Icons.keyboard), new Icon(Icons.print),
    new Icon(Icons.router), new Icon(Icons.pages),
    new Icon(Icons.zoom_out_map), new Icon(Icons.zoom_out),
    new Icon(Icons.youtube_searched_for), new Icon(Icons.wifi_tethering),
    new Icon(Icons.wifi_lock), new Icon(Icons.widgets),
    new Icon(Icons.weekend), new Icon(Icons.web),
    new Icon(Icons.accessible), new Icon(Icons.ac_unit),
];

Widget buildListData(BuildContext context, String strItem, Icon iconItem) {
    return new ListTile(
      isThreeLine: false,
      leading: iconItem,
      title: new Text(strItem),
      trailing: new Icon(Icons.keyboard_arrow_right),
      onTap: () {
        showDialog(
          context: context,
          builder: (BuildContext context) {
            return new AlertDialog(
              title: new Text(
                'ListViewDemo',
                style: new TextStyle(
                  color: Colors.black54,
                  fontSize: 18.0,
                ),
              ),
              content: new Text('您選擇的item內容為:$strItem'),
            );
          },
        );
      },
    );
}

1. 預設 List

      小菜理解預設 List 方式,是把資料 Iterable 新增到列表中,之後直接新增到 ListView 即可;如下:

List<Widget> _list = new List();
for (int i = 0; i < strItems.length; i++) {
    _list.add(buildListData(context, strItems[i], iconItems[i]));
}
// 新增分割線
var divideList =
        ListTile.divideTiles(context: context, tiles: _list).toList();
body: new Scrollbar(
    child: new ListView(
        // 新增ListView控制元件
//        children: _list,    // 無分割線
        children: divideList, // 新增分割線
    ),
);

      Tips: 如果需要設定分割線,需要對列表 item 新增處理,ListTile.divideTiles

2. ListView.builder

      小菜理解 builder 方式很像對話方塊型別逐個新增需要的屬性;需要在 builder 中新增列表資料;而新增分割線的方式更讓小菜體會到 Flutter 一切都是 widget 思想的重要性,如下:

// 沒有分割線
child: new ListView.builder(
    itemCount: iconItems.length,  // 資料長度
    itemBuilder: (context, item) {
        return buildListData(context, strItems[item], iconItems[item]);
    },
),

// 新增分割線
child: new ListView.builder(
    itemCount: iconItems.length,
    itemBuilder: (context, item) {
        return new Container(
            child: new Column(
              children: <Widget>[
                buildListData(context, strItems[item], iconItems[item]),
                new Divider()
              ],
            ),
        );
    },
),

3. ListView.separated

      小菜對 separated 方式最大的理解是有直接的分隔符設定方式,對分隔符列表應用更實用;設定 separatorBuilder 屬性即可;如下:

child: new ListView.separated(
    itemCount: iconItems.length,
    separatorBuilder: (BuildContext context, int index) => new Divider(),  // 新增分割線
    itemBuilder: (context, item) {
        return buildListData(context, strItems[item], iconItems[item]);
    },
),

4. ListView.custom

      小菜暫時不對本載入方式做例項嘗試,小菜理解 ListView.custom 更適合對 item 中含有子類 item,並對子類 item 的顯隱性有更多操作時使用該方式更好;在以後的嘗試中小菜會單獨對這種方式進行測試整理。

主要原始碼

List<Widget> _list = new List();

@override
Widget build(BuildContext context) {
    for (int i = 0; i < strItems.length; i++) {
      _list.add(buildListData(context, strItems[i], iconItems[i]));
    }
    var divideList =
        ListTile.divideTiles(context: context, tiles: _list).toList();
    return new Scaffold(
      body: new Scrollbar(
        // 預設方式 List
//        child: new ListView(
//          children: divideList, //新增ListView控制元件
//        ),
        // ListView.separated 方式
//        child: new ListView.separated(
//          itemCount: iconItems.length,
//          separatorBuilder: (BuildContext context, int index) => new Divider(),
//          itemBuilder: (context, item) {
//            return buildListData(context, strItems[item], iconItems[item]);
//          },
//        ),
        // ListView.builder 方式
        child: new ListView.builder(
          itemCount: iconItems.length,
          itemBuilder: (context, item) {
            return new Container(
              child: new Column(
                children: <Widget>[
                  buildListData(context, strItems[item], iconItems[item]),
                  new Divider()
                ],
              ),
            );
          },
        ),
//        child: new ListView.custom(
//
//        ),
      ),
    );
}

      Tips: 列表中有一個屬性很有意思:reverse 是否反轉,如果設為 true,列表預設滑倒底部而且資料也是倒敘排列;若設為 false,則一切正常。其他很多屬性也很有特點,小菜還沒來得及深入探究。

      GitHub Demo