一,flutter SliverAppbar 控件介紹
SliverAppBar “應用欄” 相當於升級版的 appbar 於 AppBar 位置的固定的應用最上面的; 而 SliverAppBar 是可以跟隨內容滾動的;
- 使用方法
- 與CustomScrollView、NestedScrollView集成的材質設計應用欄。應用欄由工具欄和其他小部件組成,例如 TabBar和FlexibleSpaceBar。應用欄通常會使用IconButton公開一個或多個常見操作,后者可選地后跟 PopupMenuButton以進行不太常見的操作
- 注意點:
通常結合 CustomScrollView 、 NestedScrollView 來使用它, NestedScrollView里面可以嵌套Listview 完成滑動
- 與CustomScrollView、NestedScrollView集成的材質設計應用欄。應用欄由工具欄和其他小部件組成,例如 TabBar和FlexibleSpaceBar。應用欄通常會使用IconButton公開一個或多個常見操作,后者可選地后跟 PopupMenuButton以進行不太常見的操作
- 構造函數
const SliverAppBar({ Key key, this.leading, //在標題左側顯示的一個控件,在首頁通常顯示應用的 logo;在其他界面通常顯示為返回按鈕 this.automaticallyImplyLeading = true,//? 控制是否應該嘗試暗示前導小部件為null this.title, //當前界面的標題文字 this.actions, //一個 Widget 列表,代表 Toolbar 中所顯示的菜單,對於常用的菜單,通常使用 IconButton 來表示;對於不常用的菜單通常使用 PopupMenuButton 來顯示為三個點,點擊后彈出二級菜單 this.flexibleSpace, //一個顯示在 AppBar 下方的控件,高度和 AppBar 高度一樣, // 可以實現一些特殊的效果,該屬性通常在 SliverAppBar 中使用 this.bottom, //一個 AppBarBottomWidget 對象,通常是 TabBar。用來在 Toolbar 標題下面顯示一個 Tab 導航欄 this.elevation, //陰影 this.forceElevated = false, this.backgroundColor, //APP bar 的顏色,默認值為 ThemeData.primaryColor。改值通常和下面的三個屬性一起使用 this.brightness, //App bar 的亮度,有白色和黑色兩種主題,默認值為 ThemeData.primaryColorBrightness this.iconTheme, //App bar 上圖標的顏色、透明度、和尺寸信息。默認值為 ThemeData().primaryIconTheme this.textTheme, //App bar 上的文字主題。默認值為 ThemeData().primaryTextTheme this.primary = true, //此應用欄是否顯示在屏幕頂部 this.centerTitle, //標題是否居中顯示,默認值根據不同的操作系統,顯示方式不一樣,true居中 false居左 this.titleSpacing = NavigationToolbar.kMiddleSpacing,//橫軸上標題內容 周圍的間距 this.expandedHeight, //展開高度 this.floating = false, //是否隨着滑動隱藏標題 this.pinned = false, //是否固定在頂部 this.snap = false, //與floating結合使用 })
二,常用屬性
- 在標題前面顯示的一個控件,在首頁通常顯示應用的logo;在其他界面通常顯示為返回按鈕
leading: Icon(_selectedChoice.icon) ,
- 控制是否應該嘗試暗示前導小部件為null(如果有 leading 這個不會管用 ,相當於忽略這個參數 ; 如果沒有leading ,當有側邊欄的時候, false:不會顯示默認的圖片,true 會顯示 默認圖片,並響應打開側邊欄的事件)
automaticallyImplyLeading:true,
- 當前界面的標題文字
title: Text(_selectedChoice.title )
- 一個 Widget 列表,代表 Toolbar 中所顯示的菜單,對於常用的菜單,通常使用 IconButton 來表示;對於不常用的菜單通常使用 PopupMenuButton 來顯示為三個點,點擊后彈出二級菜單
actions: <Widget>[ new IconButton( // action button icon: new Icon(choices[0].icon), onPressed: () { _select(choices[0]); }, ), new IconButton( // action button icon: new Icon(choices[1].icon), onPressed: () { _select(choices[1]); }, ), new PopupMenuButton<Choice>( // overflow menu onSelected: _select, itemBuilder: (BuildContext context) { return choices.skip(2).map((Choice choice) { return new PopupMenuItem<Choice>( value: choice, child: new Text(choice.title), ); }).toList(); }, ) ],
- 一個顯示在 AppBar 下方的控件,高度和 AppBar 高度一樣,可以實現一些特殊的效果,該屬性通常在 SliverAppBar 中使用
flexibleSpace: Container(
color: Colors.blue,
width: MediaQuery.of(context).size.width,
child: Text("aaaaaaaaaa"),
height: 10,
)
- 一個 AppBarBottomWidget 對象,通常是 TabBar。用來在 Toolbar 標題下面顯示一個 Tab 導航欄
bottom: new TabBar( isScrollable: true, tabs: choices.map((Choice choice) { return new Tab( text: choice.title, icon: new Icon(choice.icon), ); }).toList(), )
- 材料設計中控件的 z 坐標順序,默認值為 4,對於可滾動的 SliverAppBar, 當 SliverAppBar 和內容同級的時候,該值為 0, 當內容滾動 SliverAppBar 變為 Toolbar 的時候,修改 elevation 的值
elevation: 1
- APP bar 的顏色,默認值為 ThemeData.primaryColor。改值通常和下面的三個屬性一起使用
backgroundColor: Colors.red,
- App bar 的亮度,有白色和黑色兩種主題,默認值為 ThemeData.primaryColorBrightness
brightness:Brightness.light ,
- App bar 上圖標的顏色、透明度、和尺寸信息。默認值為 ThemeData().primaryIconTheme
iconTheme: ThemeData().iconTheme,
- App bar 上的文字主題。默認值為 ThemeData().primaryTextTheme
textTheme: ThemeData().accentTextTheme,
- 此應用欄是否顯示在屏幕頂部
primary: true,
- 標題是否居中顯示,默認值根據不同的操作系統,顯示方式不一樣,true居中 false居左
centerTitle: true,
- 橫軸上標題內容 周圍的間距
titleSpacing: NavigationToolbar.kMiddleSpacing,
- 頂部的工具欄部分的透明度 <=1.0
toolbarOpacity:1.0,
- bottom的工具欄部分的透明度 <=1.0
bottomOpacity: 0.5,
- appbar是否隨着滑動隱藏標題
floating: true,
- tab 是否固定在頂部(為true是固定,為false是不固定)
pinned: true,
- 與floating結合使用,如果snap和floating為true,則浮動應用欄將“捕捉”到視圖中
snap: true,
- 可滾動視圖的高度(默認高度是狀態欄和導航欄的高度,如果有滾動視差的話,要大於前兩者的高度)
expandedHeight: 200.0,
三,CustomScrollView
CustomScrollView是可以使用sliver來自定義滾動模型(效果)的ScrollView類型的widget。它可以包含多種滾動模型,舉個例子,假設有一個頁面,頂部需要一個GridView,底部需要一個ListView,而要求整個頁面的滑動效果是統一的,即它們看起來是一個整體,如果使用GridView+ListView來實現的話,就不能保證一致的滑動效果,因為它們的滾動效果是分離的,所以這時就需要一個"膠水",把這些彼此獨立的可滾動widget(Sliver)"粘"起來,而CustomScrollView的功能就相當於“膠水”。
CustomScrollView讓你可以直接提供 slivers
來創建不同的滾動效果,比如Lists,grids 以及 expanding headers。
-
Sliver
Sliver有細片、小片之意,在Flutter中,Sliver通常指具有特定滾動效果的可滾動塊。可滾動widget,如ListView、GridView等都有對應的Sliver實現如SliverList、SliverGrid等。對於大多數Sliver來說,它們和可滾動Widget最主要的區別是Sliver不會包含Scrollable Widget,也就是說Sliver本身不包含滾動交互模型 ,正因如此,CustomScrollView才可以將多個Sliver"粘"在一起,這些Sliver共用CustomScrollView的Scrollable,最終實現統一的滑動效果。
Sliver系列Widget比較多,我們不會一一介紹,讀者只需記住它的特點,需要時再去查看文檔即可。上面之所以說“大多數“Sliver都和可滾動Widget對應,是由於還有一些如SliverPadding、SliverAppBar等是和可滾動Widget無關的,它們主要是為了結合CustomScrollView一起使用,這是因為CustomScrollView的子widget必須都是Sliver。
-
構造函數
const CustomScrollView({ Key key, Axis scrollDirection = Axis.vertical, bool reverse = false, ScrollController controller, bool primary, ScrollPhysics physics, bool shrinkWrap = false, Key center, double anchor = 0.0, double cacheExtent, this.slivers = const <Widget>[], int semanticChildCount, DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : super( key: key, scrollDirection: scrollDirection, reverse: reverse, controller: controller, primary: primary, physics: physics, shrinkWrap: shrinkWrap, center: center, anchor: anchor, cacheExtent: cacheExtent, semanticChildCount: semanticChildCount, dragStartBehavior: dragStartBehavior, );
- 與CustomScrollView集成SLiverAppBar
//例子2 @override Widget build(BuildContext context) { return Scaffold( body:CustomScrollView( slivers: <Widget>[ const SliverAppBar( pinned: true, expandedHeight: 250.0, flexibleSpace: FlexibleSpaceBar( title: Text('Demo'), ), ), SliverGrid( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 200.0, mainAxisSpacing: 10.0, crossAxisSpacing: 10.0, childAspectRatio: 4.0, ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return Container( alignment: Alignment.center, color: Colors.teal[100 * (index % 9)], child: Text('grid item $index'), ); }, childCount: 20, ), ), SliverFixedExtentList( itemExtent: 50.0, delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return Container( alignment: Alignment.center, color: Colors.lightBlue[100 * (index % 9)], child: Text('list item $index'), ); }, ), ), ], ), ); }
四,NestedScrollView
- 概述:
NestedScrollView 即 支持嵌套滑動的 ScrollView。
因此,我們可以簡單的把 NestedScrollView 類比為 ScrollView,其作用就是作為控件父布局,從而具備(嵌套)滑動功能。
- 與ScrollView的區別
NestedScrollView 與 ScrollView 的區別就在於 NestedScrollView 支持 嵌套滑動,無論是作為父控件還是子控件,嵌套滑動都支持,且默認開啟。
因此,在一些需要支持嵌套滑動的情景中,比如一個 ScrollView 內部包裹一個 RecyclerView,那么就會產生滑動沖突,這個問題就需要你自己去解決。而如果使用 NestedScrollView 包裹 RecyclerView,嵌套滑動天然支持,你無需做什么就可以實現前面想要實現的功能了。
- 構造函數
const NestedScrollView({ Key key, this.controller, this.scrollDirection = Axis.vertical, this.reverse = false, this.physics, @required this.headerSliverBuilder, @required this.body, this.dragStartBehavior = DragStartBehavior.start, }) : assert(scrollDirection != null), assert(reverse != null), assert(headerSliverBuilder != null), assert(body != null), super(key: key);
- 與NestedScrollView 集成SliverAppBar
@override Widget build(BuildContext context) { return DefaultTabController( length: choices.length, child: Scaffold( body: NestedScrollView( headerSliverBuilder: _headerSliverBuilder, body : TabBarView( children: choices.map((Choice choice) { return new Padding( padding: const EdgeInsets.all(16.0), child: new ChoiceCard(choice: choice), ); }).toList(), ), ) ) ); }
五,完成的例子
- 代碼demo
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget{ @override Widget build(BuildContext context) { return MaterialApp( title: 'Text Demo', theme: ThemeData( primarySwatch: Colors.green ), home: MyHomePage(title: 'Text Demo'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage>{ Choice _selectedChoice = choices[0]; // The app's "state". void _select(Choice choice) { setState(() { // Causes the app to rebuild with the new _selectedChoice. _selectedChoice = choice; }); } @override Widget build(BuildContext context) { return DefaultTabController( length: choices.length, child: Scaffold( body: NestedScrollView( headerSliverBuilder: _headerSliverBuilder, body : TabBarView( children: choices.map((Choice choice) { return new Padding( padding: const EdgeInsets.all(16.0), child: new ChoiceCard(choice: choice), ); }).toList(), ), ) ) ); } List<Widget> _headerSliverBuilder(BuildContext context, bool innerBoxIsScrolled){ return <Widget>[ SliverAppBar( //1.在標題左側顯示的一個控件,在首頁通常顯示應用的 logo;在其他界面通常顯示為返回按鈕 leading: Icon(_selectedChoice.icon) , //2. ? 控制是否應該嘗試暗示前導小部件為null automaticallyImplyLeading:true , //3.當前界面的標題文字 title: Text(_selectedChoice.title ), //4.一個 Widget 列表,代表 Toolbar 中所顯示的菜單,對於常用的菜單,通常使用 IconButton 來表示; //對於不常用的菜單通常使用 PopupMenuButton 來顯示為三個點,點擊后彈出二級菜單 actions: <Widget>[ new IconButton( // action button icon: new Icon(choices[0].icon), onPressed: () { _select(choices[0]); }, ), new IconButton( // action button icon: new Icon(choices[1].icon), onPressed: () { _select(choices[1]); }, ), new PopupMenuButton<Choice>( // overflow menu onSelected: _select, itemBuilder: (BuildContext context) { return choices.skip(2).map((Choice choice) { return new PopupMenuItem<Choice>( value: choice, child: new Text(choice.title), ); }).toList(); }, ) ], //5.一個顯示在 AppBar 下方的控件,高度和 AppBar 高度一樣, // 可以實現一些特殊的效果,該屬性通常在 SliverAppBar 中使用 flexibleSpace: FlexibleSpaceBar( centerTitle: true, background: Image( image: NetworkImage("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1551944816841&di=329f747e3f4c2554f24c609fd6f77c49&imgtype=0&src=http%3A%2F%2Fimg.tupianzj.com%2Fuploads%2Fallimg%2F160610%2F9-160610114520.jpg"), fit: BoxFit.cover, ), ), //6.一個 AppBarBottomWidget 對象,通常是 TabBar。用來在 Toolbar 標題下面顯示一個 Tab 導航欄 bottom: new TabBar( isScrollable: true, tabs: choices.map((Choice choice) { return new Tab( text: choice.title, icon: new Icon(choice.icon), ); }).toList(), ) , //7.? 材料設計中控件的 z 坐標順序,默認值為 4,對於可滾動的 SliverAppBar, // 當 SliverAppBar 和內容同級的時候,該值為 0, 當內容滾動 SliverAppBar 變為 Toolbar 的時候,修改 elevation 的值 elevation: 1, //APP bar 的顏色,默認值為 ThemeData.primaryColor。改值通常和下面的三個屬性一起使用 backgroundColor: Colors.red, //App bar 的亮度,有白色和黑色兩種主題,默認值為 ThemeData.primaryColorBrightness brightness:Brightness.light , //App bar 上圖標的顏色、透明度、和尺寸信息。默認值為 ThemeData().primaryIconTheme iconTheme: ThemeData().primaryIconTheme, //App bar 上的文字主題。默認值為 ThemeData().primaryTextTheme textTheme: ThemeData().accentTextTheme, //此應用欄是否顯示在屏幕頂部 primary: true, //標題是否居中顯示,默認值根據不同的操作系統,顯示方式不一樣,true居中 false居左 centerTitle: true, //橫軸上標題內容 周圍的間距 titleSpacing: NavigationToolbar.kMiddleSpacing, //展開高度 expandedHeight: 200, //是否隨着滑動隱藏標題 floating: true, //tab 是否固定在頂部 pinned: true, //與floating結合使用 snap: true, ) ]; } // //例子2 // @override // Widget build(BuildContext context) { // return Scaffold( // body:CustomScrollView( // slivers: <Widget>[ // const SliverAppBar( // pinned: true, // expandedHeight: 250.0, // flexibleSpace: FlexibleSpaceBar( // title: Text('Demo'), // ), // ), // SliverGrid( // gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( // maxCrossAxisExtent: 200.0, // mainAxisSpacing: 10.0, // crossAxisSpacing: 10.0, // childAspectRatio: 4.0, // ), // delegate: SliverChildBuilderDelegate( // (BuildContext context, int index) { // return Container( // alignment: Alignment.center, // color: Colors.teal[100 * (index % 9)], // child: Text('grid item $index'), // ); // }, // childCount: 20, // ), // ), // SliverFixedExtentList( // itemExtent: 50.0, // delegate: SliverChildBuilderDelegate( // (BuildContext context, int index) { // return Container( // alignment: Alignment.center, // color: Colors.lightBlue[100 * (index % 9)], // child: Text('list item $index'), // ); // }, // ), // ), // ], // ), // ); // } } class Choice { const Choice({ this.title, this.icon }); final String title; final IconData icon; } const List<Choice> choices = const <Choice>[ const Choice(title: 'Car', icon: Icons.directions_car), const Choice(title: 'Bicycle', icon: Icons.directions_bike), const Choice(title: 'Boat', icon: Icons.directions_boat), const Choice(title: 'Bus', icon: Icons.directions_bus), const Choice(title: 'Train', icon: Icons.directions_railway), const Choice(title: 'Walk', icon: Icons.directions_walk), ]; class ChoiceCard extends StatelessWidget { const ChoiceCard({ Key key, this.choice }) : super(key: key); final Choice choice; @override Widget build(BuildContext context) { final TextStyle textStyle = Theme.of(context).textTheme.display1; Widget _itemBuilder(BuildContext context,int index){ return ListTile( leading: Icon(choice.icon), title: Text("this is a " + choice.title), ); } return new Card( color: Colors.white, child: Center( child: ListView.builder( itemBuilder: _itemBuilder, itemCount: 30, ), ), // body: Center( // child: ListView.builder( // itemBuilder: _itemBuilder, // itemCount: 100, // // ), // )), // child: new Center( // child: new Column( // mainAxisSize: MainAxisSize.min, // crossAxisAlignment: CrossAxisAlignment.center, // children: <Widget>[ // new Icon(choice.icon, size: 128.0, color: textStyle.color), // new Text(choice.title, style: textStyle), // ], // ), // ), ); } }