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

89 篇文章 0 订阅
8 篇文章 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

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter的TabBarView是一个非常方便的组件,用于在不同的选项卡之间切换,并且可以包含滚动视图。如果你想在左右滑动时执行一些自定义代码,可以使用GestureDetector来监听水平拖动手势。 以下是一个简单的示例代码,它演示了如何在TabBarView中添加水平滑动手势监听器。 ```dart class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(vsync: this, length: 3); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), bottom: TabBar( controller: _tabController, tabs: [ Tab(text: 'Tab 1'), Tab(text: 'Tab 2'), Tab(text: 'Tab 3'), ], ), ), body: GestureDetector( onHorizontalDragEnd: (details) { if (details.primaryVelocity < 0) { // Swiped left, switch to the next tab _tabController.animateTo(_tabController.index + 1); } else if (details.primaryVelocity > 0) { // Swiped right, switch to the previous tab _tabController.animateTo(_tabController.index - 1); } }, child: TabBarView( controller: _tabController, children: [ Container(color: Colors.red), Container(color: Colors.green), Container(color: Colors.blue), ], ), ), ); } @override void dispose() { _tabController.dispose(); super.dispose(); } } ``` 在这个示例中,我们将GestureDetector添加到TabBarView的外部,并将其与onHorizontalDragEnd回调绑定。当用户水平滑动时,我们检查primaryVelocity属性的值,以确定用户是向左还是向右滑动。然后,我们使用TabController来切换到下一个或上一个选项卡。 注意,我们还需要在State对象的dispose方法中调用TabController的dispose方法来释放资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值