iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 12
1
Mobile Development

iOS Developer Learning Flutter系列 第 12

iOS Developer Learning Flutter. Lesson11 又是列表(置頂與刷新)

昨天我們講了列表的基本用法
今天講講列表非常常見的兩種功能
header置頂與下拉刷新(附贈上拉加載)

Today Preview

1. header置頂

Flutter不像TableView有section跟row分組的概念⚠️⚠️⚠️
就是一條列表而已
所以如果要做到像以前那樣有分組
可能就是要從資料動手, 插入標題
在builder去做判斷
然後返回不同的header或cell

但如果要做到hrader置頂
就有點麻煩了
Flutter似乎也沒有提供相應的Widget
上網找了一下
好像也沒有其他的做法
大部分是使用套件
好吧
被同事稱為

的我
就來用用fluttercommunity出品的sticky_headers套件

先快速提一下Flutter怎麼安裝套件

只要照個黃綠紅三個步驟(出現exit0)就好了

然後說明怎麼使用sticky_headers

官方有提到兩種姿勢

You can place a StickyHeader or StickyHeaderBuilder inside any scrollable content, such as: ListView, GridView, CustomScrollView, SingleChildScrollView or similar.

StickyHeader的話比較單純

有一個header屬性, 把你想要出現的widget餵給他
如下官方範例:

class Example extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(itemBuilder: (context, index) {
      return StickyHeader(
        header: Container(
          height: 50.0,
          color: Colors.blueGrey[700],
          padding: EdgeInsets.symmetric(horizontal: 16.0),
          alignment: Alignment.centerLeft,
          child: Text('Header #$index',
            style: const TextStyle(color: Colors.white),
          ),
        ),
        content: Container(
          child: Image.network(imageForIndex(index), fit: BoxFit.cover,
            width: double.infinity, height: 200.0),
        ),
      );
    });
  }
}

StickyHeaderBuilder的話則是比較有彈性

builder屬性給它一個StickyHeaderWidgetBuilder的call back
可以在裡面做一些判斷
回傳不同的widget當作header
然而不管哪種用法
都要注意content屬性裡面不能給ListView
不然會爆炸Vertical viewport was given unbounded height.
所以我們改用Column
如下小弟範例:

return ListView.builder(
  itemCount: stairs.length,
  itemBuilder: (ctx, section) {
    return StickyHeaderBuilder(
      builder: (ctx, amount) {
        return Container(
          color: Colors.white,
          child: ListTile(
            title: Text("Header $section"),
          ),
        );
      },
      content: Column(
        children: List.generate(stairs[section].length, (row) {
          return Container(
            color: rainbowColors[row],
            child: ListTile(
              title: Text("Cell $row"),
            )
          );
        })
      )
    );
  }
);

2. 下拉刷新&上拉加載

用個費氏數列來當作DEMO
寫完了之後才發現原來官方首頁也有這個例子XD

另外我偶然發現
ListView.builder竟然可以不給itemCount
然後就怎麼滑也滑不完了XDDD

2.1 refresh

這個部分Flutter有提供RefreshIndicator
用它把你的listView包起來就成了
setState寫在onRefresh去刷新畫面

至於有沒有庫比蒂諾(怎麼聽起來好像瑪利歐裡面會出現的角色)版本的刷新元件呢
答案是肯定的
大概三天後會提到
敬請期待(笑)

2.2 reload more

就必須自己實作了
有看到參考連結的兩種做法
目前採取了第一種
「利用scrollController去監聽是不是滑到底」
但是這種作法會有如果一開始資料就沒有超過頁面時就無法觸發
然後就怎麼轉也轉不完了XDDD

如果採用第二種
「先在資料裡安插最末筆資料讓itemBuilder去判斷是否到底」
應該就不會有這種問題了
完整的code請見:


class LessonPageListViewRefresh extends StatefulWidget {
  @override
  _LessonPageListViewRefreshState createState() => _LessonPageListViewRefreshState();
}

class _LessonPageListViewRefreshState extends State<LessonPageListViewRefresh> {

  final pageSize = 20;
  final maxPage = 4;
  int currentPage = 1;
  List<int> fibonacci = [];
  ScrollController _scrollController = ScrollController();

  List<int> _createFib() {
    List<int> fib = [];
    for (var index = 0;
             index < pageSize;
             index ++) {
      if (index == 0 || index == 1) {
        if (fibonacci.isEmpty) {
          fib.add(index);
        } else if (index == 0) {
          fib.add(fibonacci[fibonacci.length - 2] + fibonacci.last);
        } else {
          fib.add(fibonacci.last + fib.first);
        }
      } else {
        fib.add(fib[index - 2] + fib[index - 1]);
      }
    }
    return fib;
  }

  @override
  void initState() {
    super.initState();
    fibonacci.addAll(_createFib());

    _scrollController.addListener(() {的
      if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
        setState(() {
          if (currentPage < maxPage) {
            currentPage ++;
            fibonacci.addAll(_createFib());
          }
        });
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: () async {
        setState(() {
          currentPage = 1;
          fibonacci.clear();
          fibonacci.addAll(_createFib());
        });
      },
      child: ListView.builder(
        controller: _scrollController,
        itemCount: fibonacci.length,
        itemBuilder: (ctx, idx) {

          //如果設為-1才看得到loading, 但會暫時看不到當頁最後一筆 (條件二是防止看不到所有資料的最後一筆)
          if (idx == fibonacci.length - 1 && currentPage < maxPage) {
            return Padding(
                padding: EdgeInsets.all(16),
                child: Center(
                    child: CircularProgressIndicator()
                )
            );
          } else {
            return ListTile(title: Text("${idx + 1} : ${fibonacci[idx]}"));
          }
        }
      )
    );
  }
  
}

參考連結

  1. Flutter ListView 上拉加载更多,下拉刷新功能实现
  2. 实例:无限加载列表

下集預告:還是列表(輸入與開合)


上一篇
iOS Developer Learning Flutter. Lesson10 建立列表
下一篇
iOS Developer Learning Flutter. Lesson12 還是~列表(輸入與折疊)
系列文
iOS Developer Learning Flutter30

尚未有邦友留言

立即登入留言