• 【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,
        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),
      //          ],
      //        ),
      //      ),
          );
        }
      }

      

  • 相关阅读:
    杂记
    [POI2015]PUS
    CF786B Legacy(线段树优化建图)
    SP11470 TTM
    [WC2010]重建计划
    [HNOI2014]世界树
    luogu P4842 城市旅行
    [SDOI2016]征途
    [APIO2014]序列分割
    上下界网络流构图证明
  • 原文地址:https://www.cnblogs.com/lxlx1798/p/11115573.html
Copyright © 2020-2023  润新知