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

简介: 在Flutter中,碰到复杂的、不同的滑动业务场景,若是选择了一个合适的Slivers组件,那么我认为问题会变得简单!

前言

上一篇文章中,我们了解了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(),

   )

 ],

),

hasScrollBody:true

hasScrollBody:false

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

相关文章
|
前端开发 机器人 数据安全/隐私保护
Flutter笔记:手写并发布一个人机滑动验证码插件
写 Flutter 项目时,遇到需要滑块验证码功能。滑块验证码属于人机验证码的一种,看起来像是在一个图片中“挖去”了一块,然后通过用户手动操作滑块,让被“挖去”的部分移回来。由于我不想使用各种第三方模块,因此决定自己实现一个初版以后慢慢添砖加瓦。本文是对第一个版本的一点记录。
380 1
|
存储 Dart 数据库
重识Flutter状态管理 — 探索Flutter中的状态
我遇到过很多没有了解过响应式编程框架的,或者从事后端开发,自己想用Flutter写个app玩玩的朋友,一上来,不管在哪里都用`setState`,我问为啥不用状态管理,大部分都回了一句:啥是状态管理?
|
1月前
|
前端开发 数据处理 开发者
Flutter应用开发中滚动性能优化与无限列表实现的重要性
本文深入探讨了Flutter应用开发中滚动性能优化与无限列表实现的重要性。首先分析了影响滚动性能的因素,如布局复杂度、重绘频率和数据处理等。接着介绍了优化方法,包括懒加载、简化布局、控制重绘和高效数据处理。最后详细讲解了无限列表的实现原理及步骤,并通过案例分析展示了具体应用,旨在为开发者提供实用的技术指导。
43 5
|
3月前
|
传感器 缓存 监控
Stream 组件在 Flutter 中的应用场景有哪些?
Stream 组件在 Flutter 中的应用场景有哪些?
194 58
|
1月前
|
开发工具
Flutter&鸿蒙next中封装一个列表组件
Flutter&鸿蒙next中封装一个列表组件
50 0
|
4月前
|
UED
Flutter之ListView实现自动滑动到底部
Flutter之ListView实现自动滑动到底部
229 1
|
5月前
Flutter 列表学习(listview,gridview,ExpansionTile,ScrollController,RefreshIndicator)
Flutter 列表学习(listview,gridview,ExpansionTile,ScrollController,RefreshIndicator)
|
7月前
|
前端开发 UED 开发者
【Flutter前端技术开发专栏】Flutter中的列表与滚动视图优化
【4月更文挑战第30天】Flutter开发中,优化列表和滚动视图至关重要。本文介绍了几种优化方法:1) 使用`ListView.builder`和`GridView.builder`实现懒加载;2) 复用子组件以减少实例创建;3) 利用`CustomScrollView`和`Slivers`提升滚动性能;4) 通过`NotificationListener`监听滚动事件;5) 使用`KeepAlive`保持列表项状态。掌握这些技巧能提升应用性能和用户体验。
114 1
【Flutter前端技术开发专栏】Flutter中的列表与滚动视图优化
|
7月前
|
缓存
使用Riverpod在Flutter中创建Todo列表
学习如何使用Riverpod在Flutter中构建一个功能完整的Todo列表应用。通过Consumer组件、ConsumerStatefulWidget类、ref.read方法和provider build重写,了解Riverpod的状态管理和更新状态机制。
341 7
使用Riverpod在Flutter中创建Todo列表
|
7月前
|
存储 缓存 监控
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
【4月更文挑战第30天】本文探讨了Flutter中优化列表滚动性能的策略。建议使用`ListView.builder`以节省内存,避免一次性渲染所有列表项。为防止列表项重建,可使用`UniqueKey`或`ObjectKey`。缓存已渲染项、减少不必要的重绘和异步加载大数据集也是关键。此外,选择轻量级组件,如`StatelessWidget`,并利用Flutter DevTools监控性能以识别和解决瓶颈。持续测试和调整以提升用户体验。
230 0
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化