Flutter:Slivers你們族,讓滑動視圖的組合變得很簡單!

更新:

  這篇文章寫的比較早了,只是講了些基礎的東西,粗略掃一掃就好了。實際開發中會遇到更多的問題,好比頭部自定義動畫、上拉刷新/下拉加載、和TabBar組合使用等等場景,所以建議你們可看下法海大佬的擴展ListView的文章:Flutter 擴展NestedScrollView,能夠知足大部分開發場景。git

文中全部示例代碼請點擊:gitee.com/yumi0629/Fl…程序員

  今天呢,我小拉麪主要想給你們講一講Flutter中的Slivers你們族的使用場景和方法。開發過列表佈局的同窗們應該對Slivers系列的控件不陌生,或多或少都用過這個庫中的控件,來解決複雜的滑動嵌套佈局。
  好比以前講Hero的時候提到的下面這個界面,使用普通的GridView的話是無法實現的,咱們選擇使用CustomScrollView,而後在slivers屬性中添加子控件,在這個例子裏,咱們能夠用SliverToBoxAdapter來作HeaderView,GridView來作主體佈局,總體爲一個CustomScrollView,徹底不會出現任何滑動衝突的問題。
bash

  Flutter中的 Slivers你們族基本都是配合 CustomScrollView來實現的,除了上面提到的滑動佈局嵌套,你還可使用 Slivers來實現頁面頭部展開/收起、 AppBar隨手勢變換等等功能。官方的Sliver庫裏面的控件不少,能夠去Flutter API網站搜一下,這篇文章我只講一些經常使用的控件。   OK, Let's start !!

SliverAppBar

  若是你是一名Android開發者,必定使用過CollapsingToolbarLayout這個佈局來實現AppBar展開/收起的功能,在Flutter裏面則對應SliverAppBar控件。給SliverAppBar設置flexibleSpaceexpandedHeight屬性,就能夠輕鬆完成AppBar展開/收起的功能:app

CustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            actions: <Widget>[
              _buildAction(),
            ],
            title: Text('SliverAppBar'),
            backgroundColor: Theme.of(context).accentColor,
            expandedHeight: 200.0,
            flexibleSpace: FlexibleSpaceBar(
              background: Image.asset('images/food01.jpeg', fit: BoxFit.cover),
            ),
            // floating: floating,
            // snap: snap,
            // pinned: pinned,
          ),
          SliverFixedExtentList(
            itemExtent: 120.0,
            delegate: SliverChildListDelegate(
              products.map((product) {
                return _buildItem(product);
              }).toList(),
            ),
          ),
        ],
      );
複製代碼

sliver_app_bar_01.gif
  若是設置 floating屬性爲 true,那麼AppBar會在你作出下拉手勢時就當即展開(即便ListView並無到達頂部),該展開狀態不顯示flexibleSpace:
sliver_app_bar_02.gif
  若是同時設置 floatingsnap屬性爲 true,那麼AppBar會在你作出下拉手勢時就當即所有展開(即便ListView並無到達頂部),該展開狀態顯示flexibleSpace:
sliver_app_bar_03.gif

  若是不想AppBar消失,則設置pinned屬性爲true便可:ide

sliver_app_bar_04.gif

SliverList

  SliverList的使用很是簡單,只需設置delegate屬性便可,咱們通常使用SliverChildBuilderDelegate,注意記得設置childCount,不然Flutter無法知道怎麼繪製:函數

CustomScrollView(
        slivers: <Widget>[
          SliverList(
            delegate: SliverChildBuilderDelegate(
                  (BuildContext context, int index) {
                return _buildItem(context, products[index]);
              },
              childCount: 3,
            ),
          )
        ],
      );
複製代碼

sliver_list.png

  你也能夠經過下面的方式來設置childCount,若是不設置childCount,Flutter一旦發現delegate的某個index返回了null,就會認爲childCount就是這個index。佈局

delegate: SliverChildBuilderDelegate(
                  (BuildContext context, int index) {
                    if(index>products.length){
                      return null;
                    }
                return _buildItem(context, products[index]);
              },
複製代碼

  你也可使用SliverChildListDelegate來構建delegate:post

delegate: SliverChildListDelegate([
              _buildItem(),
              _buildItem(),
              _buildItem(),
            ]),
複製代碼

SliverChildListDelegateSliverChildBuilderDelegate的區別:flex

  • SliverChildListDelegate通常用來構item建數量明確的列表,會提早build好全部的子item,因此在效率上會有問題,適合item數量很少的狀況(不超過一屏)。
  • SliverChildBuilderDelegate構建的列表理論上是能夠無限長的,由於使用來lazily construct優化。 (二者的區別有些相似於ListView和ListView.builder()的區別。)

SliverGrid

  SliverGrid有三個構造函數:SliverGrid.count()SliverGrid.extentSliverGrid()優化

  • SliverGrid.count()指定了一行展現多少個item,下面的例子表示一行展現4個:
SliverGrid.count(children: scrollItems, crossAxisCount: 4)
複製代碼
  • SliverGrid.extent能夠指定item的最大寬度,而後讓Flutter本身決定一行展現多少個item:
SliverGrid.extent(children: scrollItems, maxCrossAxisExtent: 90.0)
複製代碼
  • SliverGrid()則是須要指定一個gridDelegate,它提供給了程序員一個自定義Delegate的入口,你能夠本身決定每個item怎麼排列:
SliverGrid(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: products.length,
  ),
  delegate: SliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return _buildItem(products[index]);;
    }
);
複製代碼

sliver_grid.png

SliverPersistentHeader

  SliverPersistentHeader顧名思義,就是給一個可滑動的視圖添加一個頭(實際上,在CustomScrollView的slivers列表中,header能夠出如今視圖的任意位置,不必定要是在頂部)。這個Header會隨着滑動而展開/收起,使用pinnedfloating屬性來控制收起時Header是否展現(pinnedfloating屬性不能夠同時爲true),pinnedfloating屬性的具體意義和SliverAppBar中相同,這裏就再也不次解釋了。

sliver_persistent_header.gif

SliverPersistentHeader(
      pinned: pinned,
      floating: floating,
      delegate: _SliverAppBarDelegate(
        minHeight: 60.0,
        maxHeight: 180.0,
        child: Container(),
      ),
    );
複製代碼

  構建一個SliverPersistentHeader須要傳入一個delegate,這個delegate是SliverPersistentHeaderDelegate類型的,而SliverPersistentHeaderDelegate是一個abstract類,咱們不能直接new一個SliverPersistentHeaderDelegate出來,所以,咱們須要自定義一個delegate來實現SliverPersistentHeaderDelegate類:

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate({
    @required this.minHeight,
    @required this.maxHeight,
    @required this.child,
  });

  final double minHeight;
  final double maxHeight;
  final Widget child;

  @override
  double get minExtent => minHeight;

  @override
  double get maxExtent => math.max(maxHeight, minHeight);

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return new SizedBox.expand(child: child);
  }

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return maxHeight != oldDelegate.maxHeight ||
        minHeight != oldDelegate.minHeight ||
        child != oldDelegate.child;
  }
}
複製代碼

  寫一個自定義SliverPersistentHeaderDelegate很簡單,只需重寫build()get maxExtentget minExtentshouldRebuild()這四個方法,上面就是一個最簡單的SliverPersistentHeaderDelegate的實現。其中,maxExtent表示header徹底展開時的高度,minExtent表示header在收起時的最小高度。所以,對於咱們上面的那個自定義Delegate,若是將minHeightmaxHeight的值設置爲相同時,header就不會收縮了,這樣的Header跟咱們日常理解的Header更像。
  以前也提到了,實際使用時,header不必定要放在slivers列表的最前面,能夠隨意混搭,固然,通常來講不會有這種視覺需求的:

CustomScrollView(
        slivers: <Widget>[
          _buildHeader(0),
          SliverGrid.count(
            crossAxisCount: 3,
            children: _products.map((product) {
              return _buildItemGrid(product);
            }).toList(),
          ),
          _buildHeader(1),
          SliverFixedExtentList(
            itemExtent: 100.0,
            delegate: SliverChildListDelegate(
              products.map((product) {
                return _buildItemList(product);
              }).toList(),
            ),
          ),
          _buildHeader(2),
          SliverGrid(
            gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
              maxCrossAxisExtent: 200.0,
              mainAxisSpacing: 10.0,
              crossAxisSpacing: 10.0,
              childAspectRatio: 3.0,
            ),
            delegate: new SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                return _buildItemGrid2(_products2[index]);
              },
              childCount: _products2.length,
            ),
          ),
        ],
      );
複製代碼

SliverToBoxAdapter

  SliverPersistentHeader通常來講都是會展開/收起的(除非minExtent和maxExtent值相同),那麼若是想要在滾動視圖中添加一個普通的控件,那麼就可使用SliverToBoxAdapter來將各類視圖組合在一塊兒,放在CustomListView中。

sliver_adapter.png
  上圖中框起來的部分所有都是SliverToBoxAdapter,結合SliverToBoxAdapter,滾動視圖能夠任意組合:

CustomScrollView(
        physics: ScrollPhysics(),
        slivers: <Widget>[
          SliverToBoxAdapter(
            child: _buildHeader(),
          ),
          SliverGrid.count(
            crossAxisCount: 3,
            children: products.map((product) {
              return _buildItemGrid(product);
            }).toList(),
          ),
          SliverToBoxAdapter(
            child: _buildSearch(),
          ),
          SliverFixedExtentList(
            itemExtent: 100.0,
            delegate: SliverChildListDelegate(
              products.map((product) {
                return _buildItemList(product);
              }).toList(),
            ),
          ),
          SliverToBoxAdapter(
            child: _buildFooter(),
          ),
        ],
      );
複製代碼
相關文章
相關標籤/搜索