Sliver系列组件入门使用记录

3,630 阅读6分钟

在开发的过程中比较常用的是ListView和GridView,但是如果是复杂一些的页面,单纯的ListView和GridView就不够用了。而Flutter提供了Sliver系列组件来实现复杂的滚动页面,这里就是学习的过程中所作的记录,都是入门级别的,比较简单。

Demo地址位于文章末尾。

下面就开始正文了。

SliverList和SliverGrid

SliverList只有一个属性:delegate,类型是SliverChildDelegate。SliverChildDelegate是一个抽象类,不能直接使用。Flutter中定义好了两个继承于SliverChildDelegate的类对象,可以直接用,分别是:SliverChildListDelegateSliverChildBuilderDelegate

先来看SliverChildListDelegate,声明如下:

SliverChildListDelegate(
    this.children, {
    ...
  })

只有一个必填属性children,是一个类型为Widget的List集合。其他属性几乎不用,暂时忽略。跟ListView构造函数相同,会将所有的子组件一次性的全部渲染出来。 SliverChildBuilderDelegate则跟ListView.build构造函数类似,需要时才会创建,提高了性能。

const SliverChildBuilderDelegate(
    this.builder, {
    this.childCount,
    ...
  })

主要参数是builder,是一个返回值为Widget的函数,原型如下:

Widget Function(BuildContext context, int index)

这个比较简单就不记录了。

SliverFixedExtentList是固定item高度的SliverList,只是比SliverList多了一个参数itemExtent来设置item高度,其用法跟SliverList一致。

SliverGrid有两个属性:delegategridDelegate。其中delegate跟上面的用法一样,gridDelegate的类型是SliverGridDelegate。 SliverGridDelegate是一个抽象类,定义了子控件Layout相关的接口。Flutter中提供了两个SliverGridDelegate的子类SliverGridDelegateWithFixedCrossAxisCount和SliverGridDelegateWithMaxCrossAxisExtent,可以直接使用。下面分别介绍一下。

SliverGridDelegateWithFixedCrossAxisCount

该类实现了一个横轴方向上固定子控件数量的layout的算法,构造函数为:

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

子元素的大小是通过crossAxisCount和childAspectRatio两个参数决定的。

SliverGridDelegateWithMaxCrossAxisExtent

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

SliverGridDelegateWithMaxCrossAxisExtent({
  double maxCrossAxisExtent,
  double mainAxisSpacing = 0.0,
  double crossAxisSpacing = 0.0,
  double childAspectRatio = 1.0,
})

maxCrossAxisExtent是子元素在横轴方向上的最大长度。之所以是最大长度而不是最终长度,这是因为子元素在横轴方向上的长度仍然是等分的。所以子元素在横轴方向上的元素个数num = 横轴长度 / maxCrossAxisExtent + 1,最终的子元素的宽度= 横轴长度 / num。其他参数和SliverGridDelegateWithFixedCrossAxisCount相同。

SliverGrid的使用方法跟GridView的使用方法保持一致。

SliverAnimatedList

SliverAnimatedList是带有动画的SliverList,先来看构造函数:

SliverAnimatedList({
    Key key,
    @required this.itemBuilder,
    this.initialItemCount = 0,
  })
  • initialItemCount:item的个数。
  • itemBuilder:是一个AnimatedListItemBuilder函数,原型如下:
Widget Function(BuildContext context, int index, Animation<double> animation)

使用SliverAnimatedList在添加或删除item的时候,需要通过一下方式来操作:

  1. 定义一个GlobalKey
GlobalKey<SliverAnimatedListState> _listKey = GlobalKey<SliverAnimatedListState>(); 
  1. 将key赋值给SliverAnimatedList
SliverAnimatedList(
              key: _listKey,
              initialItemCount: _list.length,
              itemBuilder: _buildItem,
            )
  1. 通过key.currentState.insertItem或key.currentState.removeItem来进行添加或删除。
_listKey.currentState.insertItem(_index);
_listKey.currentState.removeItem(_index,
        (context, animation) => _buildItem(context, item, animation));

_buildItem函数原型如下:

Widget _buildItem(BuildContext context, int index, Animation<double> animation) {
    return SizeTransition(
      sizeFactor: animation,
      child: Card(
        child: ListTile(
          title: Text(
            'Item $index',
          ),
        ),
      ),
    );
  }

如果想修改动画类型,就需要修改_buildItem中的动画方式。

SliverPersistentHeader

这个组件可以实现控件吸顶的效果。先来看构造函数:

SliverPersistentHeader({
    Key key,
    @required this.delegate,
    this.pinned = false,
    this.floating = false,
  })

其中pinned的效果就是控制header是否保持吸顶效果。另一个重要的属性则是delegate,它的类型是SliverPersistentHeaderDelegate,这是一个抽象类,所以要使用的话,需要自己定义一个子类。 子类需要重写4个父类的函数:

@override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    // TODO: implement build
    throw UnimplementedError();
  }

  @override
  // TODO: implement maxExtent
  double get maxExtent => throw UnimplementedError();

  @override
  // TODO: implement minExtent
  double get minExtent => throw UnimplementedError();

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    // TODO: implement shouldRebuild
    throw UnimplementedError();
  }

其中build返回header显示的内容,maxExtentminExtent表示最大值和最小值,即header展开和闭合时的高度,若相同则header高度保持不变,若不同,则滚动的时候header的高度会自动在两只之间进行变化。shouldRebuild表示是否需要重新绘制,需要的话则返回true。 代码如下:

CustomScrollView(
        slivers: <Widget>[
          // makeHeader('Header Section 1'),
          SliverPersistentHeader(
            pinned: true,
            delegate: _SliverAppBarDelegate(// 自定义的delegate
              minHeight: 60.0,
              maxHeight: 60.0,
              child: Container(
                color: Colors.white,
                child: Center(
                  child: Text('Header Section 1'),
                ),
              ),
            ),
          ),
          SliverGrid.count(
            crossAxisCount: 3,
            children: [
              Container(color: Colors.red, height: 150.0),
              Container(color: Colors.purple, height: 150.0),
              Container(color: Colors.green, height: 150.0),
              Container(color: Colors.orange, height: 150.0),
              Container(color: Colors.yellow, height: 150.0),
              Container(color: Colors.pink, height: 150.0),
              Container(color: Colors.cyan, height: 150.0),
              Container(color: Colors.indigo, height: 150.0),
              Container(color: Colors.blue, height: 150.0),
            ],
          ),
        ],
      )

通过SliverPersistentHeader可以实现SliverAppBar的效果。

SliverAppBar

先来看构造函数:

SliverAppBar({
	this.flexibleSpace,
    this.forceElevated = false,
    this.expandedHeight,
    this.floating = false,
    this.pinned = false,
    this.snap = false,
    this.stretch = false,
    this.stretchTriggerOffset = 100.0,
    this.onStretchTrigger,
	...
  })

其他的参数都跟AppBar是一致的,就忽略了。其中有一些重要的参数:

  • expandedHeight:展开时AppBar的高度。
  • flexibleSpace:空间大小可变的组件。
  • floating:向上滚动时,AppBar会跟随着滑出屏幕;向下滚动时,会有限显示AppBar,只有当AppBar展开时才会滚动ListView。
  • pinned:当SliverAppBar内容滑出屏幕时,将始终渲染一个固定在顶部的收起状态AppBar。
  • snap:当手指离开屏幕时,AppBar会保持跟手指滑动方向相一致的状态,即手指上滑则AppBar收起,手指下滑则AppBar展开。
  • stretch:是否拉伸。

只有floating设置为true时,snap才可以设置为true。

FlexibleSpaceBar是Flutter提供的一个现成的空间大小可变的组件,并且处理好了title和background的过渡效果。重点说一下其中的stretchModes属性,这个是用来设置AppBar拉伸效果的,有三个枚举值,可以互相搭配使用,但是前提是stretch为true。

  • blurBackground:拉伸时使用模糊效果。
  • fadeTitle:拉伸时标题将消失。
  • zoomBackground默认值,拉伸时widget将填充额外的空间。

SliverFillRemaining和SliverFillViewport

SliverFillRemaining会自动充满视图的全部空间,通常用于slivers的最后一个。 SliverFillViewport 生成的每一个item都占满全屏。

用法都比较简单,就没有记录。 详细可查看demo

SliverOpacity和SliverPadding

SliverOpacity用来设置子控件透明度,构造函数如下:

SliverOpacity({
    Key key,
    @required this.opacity,
    this.alwaysIncludeSemantics = false,
    Widget sliver,
  })

SliverPadding是设置需要padding的Sliver控件,其构造函数如下:

SliverPadding({
    Key key,
    @required this.padding,
    Widget sliver,
  })

SliverOpacity 和SliverPadding 中的sliver属性的值必须是Sliver系列的Widget。

SliverPrototypeExtentList

跟SliverList用法基本一致,但是子控件的高度是由prototypeItem的控件高度决定的。构造函数如下:

SliverPrototypeExtentList({
    Key key,
    @required SliverChildDelegate delegate,
    @required this.prototypeItem,
  })

SliverLayoutBuilder

理解有限,就不记录贻笑大方了。

可查看大佬的文章: Flutter Sliver一辈子之敌 (ExtendedList) 

如果在CustomScrollView中用到了其他非Sliver系列的组件,需要使用SliverToBoxAdapter将这些组件包裹起来。

参考文章如下:

1、在Flutter中创建有意思的滚动效果 - Sliver系列

2、Slivers, Demystified

Demo地址点击跳转