美文网首页
重识Flutter 在不同的滑动列表场景,请选择合适的Slive

重识Flutter 在不同的滑动列表场景,请选择合适的Slive

作者: 代码我写的怎么 | 来源:发表于2023-02-13 14:12 被阅读0次

    前言

    上一篇文章中,我们了解了Flutter中的视窗和Sliver,并通过案例知道了CustomScrollViewSliverListSliverGridSliverAppBar等常用的Sliver系列组件。那么现在让我们来探索更多常用的Slivers吧!

    SliverToBoxAdapter

    如果想在CustomScrollView中添加SizedBoxRow这样基于Box协议的组件,你会发现不能直接添加,因为在CustomScrollView中需要使用Sliver协议来实现一些东西,这时也许你会Google:我应该如何将Box协议的组件更改为Sliver协议的组件? 答案是:使用SliverToBoxAdapter即可。

    — 在Flutter中,主要有两种布局协议:box协议,和sliver协议。

    SliverToBoxAdapter只是一个用于包裹Box协议的Sliver组件。如果你想在CustomScrollView中显示一个子项时,通过它会变得非常方便。

    Scaffold(
          body: CustomScrollView(
            slivers: [
              SliverToBoxAdapter(
                child: _helloText,
              )
            ],
          ),
    );
    
    Widget get _helloText {
        return Column(
          children: [
            ...List.generate(
                10, (index) => Text("Taxze SliverToBoxAdapter $index")),
            ElevatedButton(
                onPressed: () => print("Say Hello!"), child: Text("Hello"))
          ],
        );
    }
    

    但是很多朋友第一次使用该组件时,会出现使用SliverToBoxAdapter去包裹可滚动组件的情况,例如:

    SliverToBoxAdapter(
      child: ListView(
        children: [],
      ),
    )
    

    出现与sliver滑动方向一致的情况时,这个ListView便没有办法正常工作。

    SliverPersistentHeader

    上一篇文章中已经知道了SliverAppBar有很多控制属性,但如果想要控制更多的SliverAppBar的行为或自定义AppBar,又或者想要在列表某处固定一个Item,那么可以使用SliverPersistentHeader

    SliverPersistentHeader有一个必传参数和两个可选参数:

    • pinned: 默认为false,用于控制item是否固定。例如将自定义的AppBar贴在顶部。
    • floating: 默认为false,用于控制item是否浮动。例如向下滑动时,自定义的AppBar会展开。
    • delegate: 必须传入的参数。该参数需要一个实现扩展抽象类的委托类SliverPersistentHeaderDelegate

    现在就来实现一个TaxzePersistentHeaderDelegate,来体验SliverPersistentHeader的强大吧!

    可以看到有四个需要实现的方法:

    • build 需要构建渲染的内容,其中shrinkOffset参数的取值为[0,maxExtent],当header在顶部时,值为0
    • maxExtent 展开时组件的高度
    • minExtent 收起时组件的高度
    • shouldRebuild 判断Header是否需要重新构建,通常在父级状态更新时触发
    class TaxzePersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
      TaxzePersistentHeaderDelegate({
        required this.minSize,
        required this.maxSize,
      });
    
      final double minSize;
      final double maxSize;
    
      @override
      Widget build(
          BuildContext context, double shrinkOffset, bool overlapsContent) {
        print(shrinkOffset);
        return Stack(
          fit: StackFit.expand,
          children: [
            ...
          ],
        );
      }
    
      @override
      bool shouldRebuild(TaxzePersistentHeaderDelegate oldDelegate) =>
          oldDelegate.maxExtent != maxExtent || oldDelegate.minExtent != minExtent;
    
      @override
      double get maxExtent => maxSize;
    
      @override
      double get minExtent => minSize;
    }
    

    pinned属性和floating属性就不过多介绍了,现在通过一个实例,来看看SliverPersistentHeader能实现什么样的常用功能。效果图:

    实现起来也很简单,第一步可以先封装一个通用的HeaderDelegate,方便快速构建 SliverPersistentHeaderDelegate,减少重复代码。

    typedef SliverHeaderBuilder = Widget Function(
        BuildContext context, double shrinkOffset, bool overlapsContent);
    
    class TaxzePersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
      TaxzePersistentHeaderDelegate({
        this.minSize = 0,
        required this.maxSize,
        required Widget child,
      })  : builder = ((context, shrinkOffset, overlapsContent) => child),
            assert(minSize <= maxSize && minSize >= 0);
    
      final double minSize;
      final double maxSize;
      final SliverHeaderBuilder builder;
    
      @override
      Widget build(
          BuildContext context, double shrinkOffset, bool overlapsContent) {
        Widget child = builder(context, shrinkOffset, overlapsContent);
        //高度充满父约束,高度在[minHeight,maxHeight]之间变化
        return SizedBox.expand(
          child: child,
        );
      }
    
      @override
      bool shouldRebuild(TaxzePersistentHeaderDelegate oldDelegate) =>
          oldDelegate.maxExtent != maxExtent || oldDelegate.minExtent != minExtent;
    
      @override
      double get maxExtent => maxSize;
    
      @override
      double get minExtent => minSize;
    }
    

    使用起来也很简单:

    SliverPersistentHeader(
      pinned: true,
      delegate: TaxzePersistentHeaderDelegate(
        maxSize: 100,
        minSize: 60,
        child: buildItemHeader(1),
      ),
    ),
    
    Widget buildItemHeader(int i) {
        return Container(
          ...
        );
    }
    

    额外的知识点:SliverPersistentHeader 组件的设计初衷主要是为了实现 SliverAppBar,所以 SliverAppBar原理其实就像是这样:

    CustomScrollView(
      slivers: [
        SliverToBoxAdapter(),
        //防止SliverPersistentHeader成为最顶层的Sliver,以至于无法上拉刷新
        SliverPersistentHeader(
          delegate:XXXDelegate(),
          //固定在顶部
          pinned: true, 
        )
      ],
    ),
    

    SliverFixedExtentList

    SliverFixedExtentListSliverList用法一样,唯一的区别是SliverFixedExtentList是固定子组件的高度的,所以如果你确定了子组件的高度,那么请选择SliverFixedExtentList,因为它无需计算子组件的布局尺寸,更加高效!而且如果想要跳转到很远的距离时,在加载组件之前就知道尺寸是非常重要的! 例如:每个item固定为100px,现在要往下滚动2000px,那么只需要跳转20个item,只需要加载第21个item即可。如果子组件的尺寸是不固定的,那么如果想要跳转到2000px的地方,就不得不逐个渲染中间的组件,才能知道2000px的位置。

    CustomScrollView(
      slivers: [
        SliverFixedExtentList(
            //固定尺寸
          itemExtent: 100,
          delegate: SliverChildBuilderDelegate((ctx, index) {
            return Container(
              alignment: Alignment.center,
              color: Colors.primaries[index % Colors.primaries.length],
              child: Text(
                "$index",
                style: const TextStyle(color: Colors.white, fontSize: 18),
              ),
            );
          }),
        )
      ],
    ),
    

    SliverPrototypeExtentList

    知道了子组件的尺寸就能使用SliverFixedExtentList从而提高性能,但是在真实的业务中,子组件固定尺寸的场景较少,一般都是出现在设置页,想要在其他的场景下固定子组件的尺寸是非常麻烦的,但是,有了SliverPrototypeExtentList就简单多了!

    为什么说使用它会变得简单呢?在SliverPrototypeExtentList中,有一个prototypeItem属性,可以传入一个组件,但是这个组件不会被渲染到屏幕上,该组件的作用是提供一个尺寸,在SliverPrototypeExtentList中的组件尺寸都会被设置成该组件的尺寸。

    SliverPrototypeExtentList(
      prototypeItem: const Text(""),
      delegate: SliverChildBuilderDelegate((ctx, index) {
        return Text("$index");
      }),
    )
    

    SliverFillViewport

    学习这个组件之前,让我们回想一下PageView组件,PageView每一个子组件都会占据整个父约束,然后可以滑动切换。而在Sliver中,就有SliverFillViewport,它也是PageView的底层原理。在PageViewbuild函数中就可以看到SliverFillViewport

    SliverFillViewport(
      delegate: SliverChildListDelegate([
        Container(
          color: Colors.red,
        ),
        Container(
          color: Colors.blue,
        ),
        Container(
          color: Colors.green,
        ),
      ]),
    ),
    

    SliverFillRemaining

    SliverFillRemainingSliverFillViewport很类似,SliverFillViewport是生成的每一个item都占满全屏,而SliverFillRemaining是会自动填充满整个视图。SliverFillRemaining有两个属性:

    • hasScrollBody 默认为true,该输入用于判断内容是否可以滚动。
    • fillOverscroll 允许在 iOS 上看到列表过度滚动时的拉伸行为。
    CustomScrollView(
      slivers: [
        SliverList(delegate: SliverChildBuilderDelegate((ctx, index) {
          return Container(
            height: 50,
            margin: EdgeInsets.all(10),
            color: Colors.red,
          );
        },childCount: 5)),
        SliverFillRemaining(
          hasScrollBody: true,
          child: FlutterLogo(),
        )
      ],
    ),
    

    SliverPadding

    如果想在CustomScrollView中添加Padding时,在没有了解过SliverPadding之前,也许你会想使用SliverToBoxAdapter去包裹一层Padding,但是其实不用那么麻烦,Slivers中就有SliverPadding这样的组件帮助实现需求。使用它也很简单,只需要在需要PaddingSliver组件外报上一层即可。

    SliverPadding(
      padding: EdgeInsets.all(20.0),
      sliver: SliverList(
        delegate: SliverChildBuilderDelegate((ctx, index) {
          return Container(
            color: Colors.primaries[index % Colors.primaries.length],
            height: 50,
          );
        },childCount: 10),
      ),
    )
    

    — 在2020 年 9 月 29 日,修复了之前使用SliverPersistentHeader外面包裹SliverPadding导致SliverPersistentHeaderpinned 属性失效的问题,有兴趣的可以看下这个

    关于我

    Hello,我是Taxze,如果您觉得文章对您有价值,希望您能给我的文章点个❤️,有问题需要联系我的话:我在这里 ,也可以通过掘金的新的私信功能联系到我。如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章~万一哪天我进步了呢?😝

    作者:编程的平行世界
    链接:https://juejin.cn/post/7199297355748655159

    相关文章

      网友评论

          本文标题:重识Flutter 在不同的滑动列表场景,请选择合适的Slive

          本文链接:https://www.haomeiwen.com/subject/asojkdtx.html