flutter SliverAppBar|8月更文挑戰

__white發表於2021-08-03

SliverAppBar

A material design app bar that integrates with a [CustomScrollView].

AppBar 和 SliverAppBar 是Material Design中的 App Bar,也就是 Android 中的 Toolbar

SliverAppBar是一個與 CustomScrollView 結合使用的material design風格的標題欄 .

不同於AppBar, 它可以展開或收縮.

構造方法:

const SliverAppBar({
  Key key,
  this.leading, //左側標題
  this.automaticallyImplyLeading = true, //如果leading為null,是否自動填充一個leading
  this.title, //標題
  this.actions, //選單
  this.flexibleSpace, //可以展開區域,通常是一個FlexibleSpaceBar
  this.bottom, //底部內容區域
  this.elevation,
  this.forceElevated = false, //結合 elevation 使用,當elevation 不為 0 的時候,是否顯示陰影
  this.backgroundColor,
  this.brightness,
  this.iconTheme,
  this.actionsIconTheme,
  this.textTheme,
  this.primary = true,
  this.centerTitle,
  this.excludeHeaderSemantics = false,
  this.titleSpacing = NavigationToolbar.kMiddleSpacing,
  this.expandedHeight, //展開後的高度
  this.floating = false, //是否向下滑動時立即顯示appBar
  this.pinned = false, //appBar是否置頂
  this.snap = false, //當手指放開時,SliverAppBar是否會根據當前的位置展開/收起
  this.stretch = false,
  this.stretchTriggerOffset = 100.0,
  this.onStretchTrigger,
  this.shape,
})
複製程式碼

如果你看過AppBar的構造方法,那麼你會發現AppBar的構造方法都是在這裡面的。其屬性也是一致的,下面我們針對公共的屬性做一下詳細的解釋:

  • snap : 需要注意的是snap是和floading結合使用的 , 並且要求floating=true , 否則使用會報錯.

  • automaticallyImplyLeading : 如果為true(預設) , 那麼頁面可以回退的話,會新增一個返回按鈕.

  • leading:在標題前面顯示的一個控制元件,在首頁通常顯示應用的 logo;在其他介面通常顯示為返回按鈕

  • title: Toolbar 中主要內容,通常顯示為當前介面的標題文字

  • actions:一個 Widget 列表,代表 Toolbar 中所顯示的選單,對於常用的選單,通常使用 IconButton 來表示;對於不常用的選單通常使用 PopupMenuButton 來顯示為三個點,點選後彈出二級選單

  • bottom:一個 AppBarBottomWidget 物件,通常是 TabBar。用來在 Toolbar 標題下面顯示一個 Tab 導航欄

  • elevation:紙墨設計中控制元件的 z 座標順序,預設值為 4,對於可滾動的 SliverAppBar,當 SliverAppBar 和內容同級的時候,該值為 0, 當內容滾動 SliverAppBar 變為 Toolbar 的時候,修改 elevation 的值 flexibleSpace:一個顯示在 AppBar 下方的控制元件,高度和 AppBar 高度一樣,可以實現一些特殊的效果,該屬性通常在 SliverAppBar 中使用

  • backgroundColor:APP bar 的顏色,預設值為 ThemeData.primaryColor。改值通常和下面的三個屬性一起使用

  • brightness:App bar 的亮度,有白色和黑色兩種主題,預設值為 ThemeData.primaryColorBrightness

  • iconTheme:App bar 上圖示的顏色、透明度、和尺寸資訊。預設值為 ThemeData.primaryIconTheme

  • textTheme: App bar 上的文字樣式。預設值為 ThemeData.primaryTextTheme

  • centerTitle: 標題是否居中顯示,預設值根據不同的作業系統,顯示方式不一樣

一個簡單的SliverAppBar

SliverAppBar(
              automaticallyImplyLeading: false,
              elevation: 5,
              forceElevated: true,
              expandedHeight: 200,
              floating: true,
              snap: false,
              pinned: true,
              stretch: true,
              flexibleSpace: FlexibleSpaceBar(
                title: Text('SliverAppBar'),
                background: Image.asset(
                  'images/pic1.jpg',
                  fit: BoxFit.fill,
                ),
                //標題是否居中
                centerTitle: false,
                //標題間距
                //titlePadding: EdgeInsetsDirectional.only(start: 0, bottom: 16),
                collapseMode: CollapseMode.parallax,
              ))
複製程式碼

s1

雖然基本相同,構造方法也是非常的簡單,但是我們卻不能直接使用它,由官方文件可以看到我們通常結合ScrollView來使用它。

我們結合CustomScrollView來看下例子吧:

/*
 * Created by 李卓原 on 2018/9/13.
 * email: zhuoyuan93@gmail.com
 *
 */

import 'package:flutter/material.dart';

class DiscoverListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
          headerSliverBuilder: _sliverBuilder,
          body: Center(
            child: ListView.builder(
              itemBuilder: _itemBuilder,
              itemCount: 15,
            ),
          )),
    );
  }

  List<Widget> _sliverBuilder(BuildContext context, bool innerBoxIsScrolled) {
    return <Widget>[
      SliverAppBar(
        centerTitle: true,    //標題居中
        expandedHeight: 200.0,  //展開高度200
        floating: false,  //不隨著滑動隱藏標題
        pinned: true,    //固定在頂部
        flexibleSpace: FlexibleSpaceBar(
          centerTitle: true,
          title: Text('我是一個FlexibleSpaceBar'),
          background: Image.network(
            "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1531798262708&di=53d278a8427f482c5b836fa0e057f4ea&imgtype=0&src=http%3A%2F%2Fh.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F342ac65c103853434cc02dda9f13b07eca80883a.jpg",
            fit: BoxFit.cover,
          ),
        ),
      )
    ];
  }

Widget _itemBuilder(BuildContext context, int index) {
    return ListTile(
      leading: Icon(Icons.android),
      title: Text('無與倫比的標題+$index'),
    );
  }
}

複製程式碼

首先我們使用了NestedScrollView中的headerSliverBuilder屬性新增了SliverAppBar

然後我們設定展開的高度為200,不讓標題欄隨著滑動滾動出可視區域, 最後我們給NestedScrollView的body加了一個長度為15的ListView

然後我們來看下效果: 在這裡插入圖片描述

我們把 pinned的屬性設定為false(不固定在頂部)再看下效果 在這裡插入圖片描述

接下來我們來看下bottom屬性,允許我們在在下面放置你想放置其他Widget,好吧我們來放個TabBar看下

其實程式碼很簡單,只不過我們需要讓DiscoverListPage繼承於 StatefulWidget,然後讓State with TickerProviderStateMixin , 併為SliverAppBar新增個bottom ,改造後的程式碼 :

/*
 * Created by 李卓原 on 2018/9/13.
 * email: zhuoyuan93@gmail.com
 *
 */

import 'package:flutter/material.dart';

class DiscoverListPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => DiscoverListState();
}

class DiscoverListState extends State<DiscoverListPage>
    with TickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
          headerSliverBuilder: _sliverBuilder,
          body: Center(
            child: ListView.builder(
              itemBuilder: _itemBuilder,
              itemCount: 15,
            ),
          )),
    );
  }

  List<Widget> _sliverBuilder(BuildContext context, bool innerBoxIsScrolled) {
    return <Widget>[
      SliverAppBar(
        //標題居中
        centerTitle: true,
        //展開高度200
        expandedHeight: 200.0,
        //不隨著滑動隱藏標題
        floating: false,
        //固定在頂部
        pinned: false,
        flexibleSpace: FlexibleSpaceBar(
          centerTitle: true,
          title: Text('我是一個FlexibleSpaceBar'),
          background: Image.network(
            "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1531798262708&di=53d278a8427f482c5b836fa0e057f4ea&imgtype=0&src=http%3A%2F%2Fh.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F342ac65c103853434cc02dda9f13b07eca80883a.jpg",
            fit: BoxFit.cover,
          ),
        ),
        //    bottom   這是新增的    這是新增的    這是新增的      這是新增的
        bottom: TabBar(
          tabs: [
            Tab(icon: Icon(Icons.cake), text: '左側'),
            Tab(icon: Icon(Icons.golf_course), text: '右側'),
          ],
          controller: TabController(length: 2, vsync: this),
        ),
      )
    ];
  }

  Widget _itemBuilder(BuildContext context, int index) {
    return ListTile(
      leading: Icon(Icons.android),
      title: Text('無與倫比的標題+$index'),
    );
  }
}

複製程式碼

看一下效果: 在這裡插入圖片描述 簡直, 醜的不忍直視。 當然我們是希望這個TabBar在SliverAppBar下方,並且隨著SliverAppBar滾動的。

由於TabBar的高度所以我們並不能讓SliverAppBar滑動到頂部,所以要想實現隨著SliverAppBar的移動,把TabBar放在bottom也不是很合適的。 在這裡,我們可以藉助於SliverPersistentHeader中的SliverPersistentHeader屬性來解決

SliverPersistentHeader的構造很簡單,只有簡單的幾個屬性,不再具體講了

 const SliverPersistentHeader({
    Key key,
    @required this.delegate,
    this.pinned = false,
    this.floating = false,
  }) 
複製程式碼

全部程式碼如下:

/*
 * Created by 李卓原 on 2018/9/13.
 * email: zhuoyuan93@gmail.com
 *
 */

import 'package:flutter/material.dart';

class DiscoverListPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => DiscoverListState();
}

class DiscoverListState extends State<DiscoverListPage>
    with TickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
          headerSliverBuilder: _sliverBuilder,
          body: Center(
            child: ListView.builder(
              itemBuilder: _itemBuilder,
              itemCount: 15,
            ),
          )),
    );
  }

  List<Widget> _sliverBuilder(BuildContext context, bool innerBoxIsScrolled) {
    return <Widget>[
      SliverAppBar(
        //標題居中
        centerTitle: true,
        //展開高度200
        expandedHeight: 200.0,
        //不隨著滑動隱藏標題
        floating: false,
        //固定在頂部
        pinned: false,
        flexibleSpace: FlexibleSpaceBar(
          centerTitle: true,
          title: Text('我是一個FlexibleSpaceBar'),
          background: Image.network(
            "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1531798262708&di=53d278a8427f482c5b836fa0e057f4ea&imgtype=0&src=http%3A%2F%2Fh.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F342ac65c103853434cc02dda9f13b07eca80883a.jpg",
            fit: BoxFit.cover,
          ),
        ),
      ),
      SliverPersistentHeader(
          delegate: _SliverAppBarDelegate(TabBar(
        labelColor: Colors.red,
        unselectedLabelColor: Colors.grey,
        tabs: [
          Tab(icon: Icon(Icons.cake), text: '左側'),
          Tab(icon: Icon(Icons.golf_course), text: '右側'),
        ],
        controller: TabController(length: 2, vsync: this),
      )))
    ];
  }

  Widget _itemBuilder(BuildContext context, int index) {
    return ListTile(
      leading: Icon(Icons.android),
      title: Text('無與倫比的標題+$index'),
    );
  }
}

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate(this._tabBar);

  final TabBar _tabBar;

  @override
  double get minExtent => _tabBar.preferredSize.height;

  @override
  double get maxExtent => _tabBar.preferredSize.height;

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      child: _tabBar,
    );
  }

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return false;
  }
}

複製程式碼

效果: 在這裡插入圖片描述

比剛才有了明顯的進步,但是有童鞋就問了,我們怎麼讓這個SliverPersistentHeader中的內容(TabBar)不隨著ListView的滾動而滑動呢?

其實很簡單,上面我貼出了SliverPersistentHeader的構造方法,因為SliverPersistentHeader跟SliverAppBar一樣都有一個 pinned屬性,將它設定為true這裡面的內容就會在到達頂部後停止跟隨ListView移動了。

github

相關文章