• 08-可滚动Widget


    可滚动Widget

    ViewPort视口

    在Flutter中,术语ViewPort(视口),如无特别说明,则是指一个Widget的实际显示区域。例如,一个ListView的显示区域高度是800像素,虽然其列表项总高度可能远远超过800像素,但是其ViewPort仍然是800像素。

    主轴和纵轴

    在可滚动widget的坐标描述中,通常将滚动方向称为主轴,非滚动方向称为纵轴。由于可滚动widget的默认方向一般都是沿垂直方向,所以默认情况下主轴就是指垂直方向,水平方向同理。

    Scrollable

    当内容超过显示视口(ViewPort)时,如果没有特殊处理,Flutter则会提示Overflow错误。为此,Flutter提供了多种可滚动widget(Scrollable Widget)用于显示列表和长布局。可滚动Widget都直接或间接包含一个Scrollable widget:

    Scrollable({
      ...
      this.axisDirection = AxisDirection.down,
      this.controller,
      this.physics,
      @required this.viewportBuilder, //后面介绍
    })
    
    • axisDirection:滚动方向。
    • physics:此属性接受一个ScrollPhysics对象,它决定可滚动Widget如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。
    • controller:此属性接受一个ScrollController对象。ScrollController的主要作用是控制滚动位置和监听滚动事件。

    Scrollbar

    Scrollbar是一个Material风格的滚动指示器(滚动条),如果要给可滚动widget添加滚动条,只需将Scrollbar作为可滚动widget的父widget即可,如:

    Scrollbar(
      child: SingleChildScrollView(
        ...
      ),
    );
    

    SingleChildScrollView

    SingleChildScrollView类似于Android中的ScrollView,它只能接收一个子Widget。定义如下:

    SingleChildScrollView({
      this.scrollDirection = Axis.vertical, //滚动方向,默认是垂直方向
      this.reverse = false, 
      this.padding, 
      bool primary, 
      this.physics, 
      this.controller,
      this.child,
    })
    

    除了通用属性,我们重点看一下reverse和primary两个属性:

    • reverse:该属性API文档解释是:是否按照阅读方向相反的方向滑动,
    • primary:指是否使用widget树中默认的PrimaryScrollController;当滑动方向为垂直方向(scrollDirection值为Axis.vertical)并且controller没有指定时,primary默认为true.

    ListView

    ListView是最常用的可滚动widget,它可以沿一个方向线性排布所有子widget。我们看看ListView的默认构造函数定义:

    ListView({
      ...  
      //可滚动widget公共参数
      Axis scrollDirection = Axis.vertical,
      bool reverse = false,
      ScrollController controller,
      bool primary,
      ScrollPhysics physics,
      EdgeInsetsGeometry padding,
    
      //ListView各个构造函数的共同参数  
      double itemExtent,
      bool shrinkWrap = false,
      bool addAutomaticKeepAlives = true,
      bool addRepaintBoundaries = true,
      double cacheExtent,
    
      //子widget列表
      List<Widget> children = const <Widget>[],
    })
    
    • itemExtent:该参数如果不为null,则会强制children的"长度"为itemExtent的值;这里的"长度"是指滚动方向上子widget的长度,即如果滚动方向是垂直方向,则itemExtent代表子widget的高度,如果滚动方向为水平方向,则itemExtent代表子widget的长度。

    • shrinkWrap:该属性表示是否根据子widget的总长度来设置ListView的长度,默认值为false 。默认情况下,ListView的会在滚动方向尽可能多的占用空间。当ListView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true。

    • addAutomaticKeepAlives:该属性表示是否将列表项(子widget)包裹在AutomaticKeepAlive widget中;典型地,在一个懒加载列表中,如果将列表项包裹在AutomaticKeepAlive中,在该列表项滑出视口时该列表项不会被GC,它会使用KeepAliveNotification来保存其状态。如果列表项自己维护其KeepAlive状态,那么此参数必须置为false。

    • addRepaintBoundaries:该属性表示是否将列表项(子widget)包裹在RepaintBoundary中。当可滚动widget滚动时,将列表项包裹在RepaintBoundary中可以避免列表项重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary反而会更高效。和addAutomaticKeepAlive一样,如果列表项自己维护其KeepAlive状态,那么此参数必须置为false。

    ListView.builder

    ListView.builder适合列表项比较多(或者无限)的情况,因为只有当子Widget真正显示的时候才会被创建。下面看一下ListView.builder的核心参数列表:

    ListView.builder({
      // ListView公共参数已省略  
      ...
      @required IndexedWidgetBuilder itemBuilder,
      int itemCount,
      ...
    })
    
    • itemBuilder:它是列表项的构建器,类型为IndexedWidgetBuilder,返回值为一个widget。当列表滚动到具体的index位置时,会调用该构建器构建列表项。
    • itemCount:列表项的数量,如果为null,则为无限列表。

    ListView.separated

    ListView.separated可以生成列表项之间的分割器,它除了比ListView.builder多了一个separatorBuilder参数,该参数是一个分割器生成器。

    class ListView3 extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        //下划线widget预定义以供复用。  
        Widget divider1=Divider(color: Colors.blue,);
        Widget divider2=Divider(color: Colors.green);
        return ListView.separated(
            itemCount: 100,
            //列表项构造器
            itemBuilder: (BuildContext context, int index) {
              return ListTile(title: Text("$index"));
            },
            //分割器构造器
            separatorBuilder: (BuildContext context, int index) {
              return index%2==0?divider1:divider2;
            },
        );
      }
    }
    

    GridView

    GridView可以构建一个二维网格列表,其默认构造函数定义如下:

    GridView({
      Axis scrollDirection = Axis.vertical,
      bool reverse = false,
      ScrollController controller,
      bool primary,
      ScrollPhysics physics,
      bool shrinkWrap = false,
      EdgeInsetsGeometry padding,
      @required SliverGridDelegate gridDelegate, //控制子widget layout的委托
      bool addAutomaticKeepAlives = true,
      bool addRepaintBoundaries = true,
      double cacheExtent,
      List<Widget> children = const <Widget>[],
    })
    
    • gridDelegate参数,类型是SliverGridDelegate,它的作用是控制GridView子widget如何排列(layout)。

    SliverGridDelegate是一个抽象类,定义了GridView Layout相关接口,子类需要通过实现它们来实现具体的布局算法,Flutter中提供了两个SliverGridDelegate的子类SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent:

    SliverGridDelegateWithFixedCrossAxisCount

    该子类实现了一个横轴为固定数量子元素的layout算法,其构造函数为:

    SliverGridDelegateWithFixedCrossAxisCount({
      @required double crossAxisCount,
      double mainAxisSpacing = 0.0,
      double crossAxisSpacing = 0.0,
      double childAspectRatio = 1.0,
    })
    
    • crossAxisCount:横轴子元素的数量。此属性值确定后子元素在横轴的长度就确定了,即ViewPort横轴长度/crossAxisCount。
    • mainAxisSpacing:主轴方向的间距。
    • crossAxisSpacing:横轴方向子元素的间距。
    • childAspectRatio:子元素在横轴长度和主轴长度的比例。由于crossAxisCount指定后子元素横轴长度就确定了,然后通过此参数值就可以确定子元素在主轴的长度。

    可以发现,子元素的大小是通过crossAxisCount和childAspectRatio两个参数共同决定的。

    GridView.count

    GridView.count构造函数内部使用了SliverGridDelegateWithFixedCrossAxisCount,我们通过它可以快速的创建横轴固定数量子元素的GridView:

    GridView.count(
      crossAxisCount: 3,
      childAspectRatio: 1.0,
      children: <Widget>[
        Icon(Icons.ac_unit),
        Icon(Icons.airport_shuttle),
        Icon(Icons.all_inclusive),
        Icon(Icons.beach_access),
        Icon(Icons.cake),
        Icon(Icons.free_breakfast),
      ],
    );
    

    SliverGridDelegateWithMaxCrossAxisExtent

    该子类实现了一个横轴子元素为固定最大长度的layout算法,其构造函数为:

    SliverGridDelegateWithMaxCrossAxisExtent({
      double maxCrossAxisExtent,
      double mainAxisSpacing = 0.0,
      double crossAxisSpacing = 0.0,
      double childAspectRatio = 1.0,
    })
    
    • maxCrossAxisExtent为子元素在横轴上的最大长度,之所以是“最大”长度,是因为横轴方向每个子元素的长度仍然是等分的,
    • childAspectRatio所指的子元素横轴和主轴的长度比为最终的长度比。其它参数和SliverGridDelegateWithFixedCrossAxisCount相同。

    GridView.extent

    GridView.extent构造函数内部使用了SliverGridDelegateWithMaxCrossAxisExtent,我们通过它可以快速的创建纵轴子元素为固定最大长度的的GridView:

    GridView.extent(
       maxCrossAxisExtent: 120.0,
       childAspectRatio: 2.0,
       children: <Widget>[
         Icon(Icons.ac_unit),
         Icon(Icons.airport_shuttle),
         Icon(Icons.all_inclusive),
         Icon(Icons.beach_access),
         Icon(Icons.cake),
         Icon(Icons.free_breakfast),
       ],
     );
    

    GridView.builder

    上面我们介绍的GridView都需要一个Widget数组作为其子元素,这些方式都会提前将所有子widget都构建好,所以只适用于子Widget数量比较少时,当子widget比较多时,我们可以通过GridView.builder来动态创建子Widget。GridView.builder 必须指定的参数有两个:

    GridView.builder(
     ...
     @required SliverGridDelegate gridDelegate, 
     @required IndexedWidgetBuilder itemBuilder,
    )
    

    其中itemBuilder为子widget构建器。

    CustomScrollView

    CustomScrollView是可以使用sliver来自定义滚动模型(效果)的widget。它可以包含多种滚动模型,举个例子,假设有一个页面,顶部需要一个GridView,底部需要一个ListView,而要求整个页面的滑动效果是统一的,即它们看起来是一个整体,如果使用GridView+ListView来实现的话,就不能保证一致的滑动效果,因为它们的滚动效果是分离的,所以这时就需要一个"胶水",把这些彼此独立的可滚动widget(Sliver)"粘"起来,而CustomScrollView的功能就相当于“胶水”。

    Sliver

    Sliver有细片、小片之意,在Flutter中,Sliver通常指具有特定滚动效果的可滚动块。可滚动widget,如ListView、GridView等都有对应的Sliver实现如SliverList、SliverGrid等。对于大多数Sliver来说,它们和可滚动Widget最主要的区别是Sliver不会包含Scrollable Widget,也就是说Sliver本身不包含滚动交互模型 ,正因如此,CustomScrollView才可以将多个Sliver"粘"在一起,这些Sliver共用CustomScrollView的Scrollable,最终实现统一的滑动效果。

    示例

    import 'package:flutter/material.dart';
    
    class CustomScrollViewTestRoute extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        //因为本路由没有使用Scaffold,为了让子级Widget(如Text)使用
        //Material Design 默认的样式风格,我们使用Material作为本路由的根。
        return Material(
          child: CustomScrollView(
            slivers: <Widget>[
              //AppBar,包含一个导航栏
              SliverAppBar(
                pinned: true,
                expandedHeight: 250.0,
                flexibleSpace: FlexibleSpaceBar(
                  title: const Text('Demo'),
                  background: Image.asset(
                    "./images/avatar.png", fit: BoxFit.cover,),
                ),
              ),
    
              SliverPadding(
                padding: const EdgeInsets.all(8.0),
                sliver: new SliverGrid( //Grid
                  gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2, //Grid按两列显示
                    mainAxisSpacing: 10.0,
                    crossAxisSpacing: 10.0,
                    childAspectRatio: 4.0,
                  ),
                  delegate: new SliverChildBuilderDelegate(
                        (BuildContext context, int index) {
                      //创建子widget      
                      return new Container(
                        alignment: Alignment.center,
                        color: Colors.cyan[100 * (index % 9)],
                        child: new Text('grid item $index'),
                      );
                    },
                    childCount: 20,
                  ),
                ),
              ),
              //List
              new SliverFixedExtentList(
                itemExtent: 50.0,
                delegate: new SliverChildBuilderDelegate(
                        (BuildContext context, int index) {
                      //创建列表项      
                      return new Container(
                        alignment: Alignment.center,
                        color: Colors.lightBlue[100 * (index % 9)],
                        child: new Text('list item $index'),
                      );
                    },
                    childCount: 50 //50个列表项
                ),
              ),
            ],
          ),
        );
      }
    }
    

    代码分为三部分:

    • SliverAppBar:SliverAppBar对应AppBar,两者不同之处在于SliverAppBar可以集成到CustomScrollView。SliverAppBar可以结合FlexibleSpaceBar实现Material Design中头部伸缩的模型,具体效果,读者可以运行该示例查看。
    • SliverGrid:它用SliverPadding包裹以给SliverGrid添加补白。SliverGrid是一个两列,宽高比为4的网格,它有20个子widget。
    • SliverFixedExtentList:它是一个所有子元素高度都为50像素的列表。
  • 相关阅读:
    JAVA CookieUtil
    Maven打包时提示No runnable methods的解决方案
    Spring MVC中Junit测试简单讲解
    C# 连接MongoDB,含用户验证
    Spring中同一个service中方法相互调用事务不生效问题解决方案
    004. vue html模板字符串转为ast(js对象)
    03事件模型-发布订阅模式
    000 学习vue源码里面一些处理技巧
    02 响应式原理
    my-ts-axios
  • 原文地址:https://www.cnblogs.com/SLchuck/p/10332150.html
Copyright © 2020-2023  润新知