【Flutter學習】頁面跳轉之SliverAppBar,CustomScrollView,NestedScrollView的使用


一,flutter SliverAppbar 控件介紹

  SliverAppBar “應用欄” 相當於升級版的 appbar 於 AppBar 位置的固定的應用最上面的; 而 SliverAppBar 是可以跟隨內容滾動的;

  • 使用方法
    • 與CustomScrollView、NestedScrollView集成的材質設計應用欄。應用欄由工具欄和其他小部件組成,例如 TabBar和FlexibleSpaceBar。應用欄通常會使用IconButton公開一個或多個常見操作,后者可選地后跟 PopupMenuButton以進行不太常見的操作

    • 注意點
      通常結合 CustomScrollView 、 NestedScrollView 來使用它, NestedScrollView里面可以嵌套Listview 完成滑動
  • 構造函數
    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結合使用
      })

二,常用屬性

  1. 在標題前面顯示的一個控件,在首頁通常顯示應用的logo;在其他界面通常顯示為返回按鈕
      leading: Icon(_selectedChoice.icon) ,
  1. 控制是否應該嘗試暗示前導小部件為null(如果有 leading 這個不會管用 ,相當於忽略這個參數 ; 如果沒有leading ,當有側邊欄的時候, false:不會顯示默認的圖片,true 會顯示 默認圖片,並響應打開側邊欄的事件)
    automaticallyImplyLeading:true,
  1. 當前界面的標題文字
    title: Text(_selectedChoice.title )
  1. 一個 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();
             },
          )
       ],
  1. 一個顯示在 AppBar 下方的控件,高度和 AppBar 高度一樣,可以實現一些特殊的效果,該屬性通常在 SliverAppBar 中使用
     
                

    flexibleSpace: Container(
      color: Colors.blue,
      width: MediaQuery.of(context).size.width,
      child: Text("aaaaaaaaaa"),
        height: 10,
    )

  1. 一個 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(),
              ) 
  1. 材料設計中控件的 z 坐標順序,默認值為 4,對於可滾動的 SliverAppBar, 當 SliverAppBar 和內容同級的時候,該值為 0, 當內容滾動 SliverAppBar 變為 Toolbar 的時候,修改 elevation 的值
    elevation: 1
  1. APP bar 的顏色,默認值為 ThemeData.primaryColor。改值通常和下面的三個屬性一起使用
    backgroundColor: Colors.red,
  1. App bar 的亮度,有白色和黑色兩種主題,默認值為 ThemeData.primaryColorBrightness
    brightness:Brightness.light ,
  1. App bar 上圖標的顏色、透明度、和尺寸信息。默認值為 ThemeData().primaryIconTheme
     iconTheme: ThemeData().iconTheme,
  1. App bar 上的文字主題。默認值為 ThemeData().primaryTextTheme

    textTheme: ThemeData().accentTextTheme,
  1. 此應用欄是否顯示在屏幕頂部
    primary: true,
  1. 標題是否居中顯示,默認值根據不同的操作系統,顯示方式不一樣,true居中 false居左
    centerTitle: true,
  1. 橫軸上標題內容 周圍的間距
    titleSpacing: NavigationToolbar.kMiddleSpacing,
  1. 頂部的工具欄部分的透明度 <=1.0
    toolbarOpacity:1.0,
  1. bottom的工具欄部分的透明度 <=1.0
    bottomOpacity: 0.5,
  1. appbar是否隨着滑動隱藏標題
     floating: true,
  1. tab 是否固定在頂部(為true是固定,為false是不固定)
     pinned: true,
  1. 與floating結合使用,如果snap和floating為true,則浮動應用欄將“捕捉”到視圖中
     snap: true,
  2. 可滾動視圖的高度(默認高度是狀態欄和導航欄的高度,如果有滾動視差的話,要大於前兩者的高度)
    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),
    //          ],
    //        ),
    //      ),
        );
      }
    }

     

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM