重识Flutter 非常用Slivers组件速览 - part3

简介: 通过前面两篇文章,从为什么要使用Sliver,再根据使用频率逐个解析Slivers系列的组件。本文就来讲讲那些不常用的组件,看完这三篇,相信你会入门Sliver的世界。

前言

上一篇文章中,我们了解了Flutter SliversSliverToBoxAdapterSliverPersistentHeaderSliverFixedExtentList等常用的组件。本章则是一些不那么常用的Slivers组件,大家仅需快速阅读一遍,有个印象即可,方便遇到一些业务时,能快速定位到需要使用的组件。

SliverLayoutBuilder

基于Box协议的组件布局中,经常会使用LayoutBuilder,因为可以通过它获取在布局过程中父组件的约束,然后就可以根据约束信息动态的创建布局。例如:

LayoutBuilder(

 builder: (BuildContext context, BoxConstraints constraints) {

   if (constraints.maxWidth < 200) {

     // 最大宽度小于200,显示一张图片

     return Image.network("");

   } else {

     return Container();

   }

 },

),

那么在slivers中对应的就是SliverLayoutBuilder,可以通过SliverLayoutBuilder创建一个折叠的sliver widget树,根据组件的约束条件提供不同子组件

CustomScrollView(

       slivers: [

         SliverLayoutBuilder(

             builder: (BuildContext context, SliverConstraints constraints) {

           if (constraints.userScrollDirection == ScrollDirection.forward) {

             _color = Colors.blue; //向下滑动显示蓝色

           } else if (constraints.userScrollDirection ==

               ScrollDirection.idle) {

             _color = Colors.yellow; //正常显示黄色

           } else {

             _color = Colors.purple; //向上滑动显示紫色

           }

           return SliverToBoxAdapter(

             child: Container(

               height: 200,

               color: _color,

             ),

           );

         }),

         SliverList(

             delegate:

                 SliverChildBuilderDelegate(childCount: 20, (ctx, index) {

           return Container(

             color: Colors.primaries[index % Colors.primaries.length],

             height: 50,

           );

         }))

       ],

     ),

SliverAnimatedList

在一个有很多子Item的列表中,如果用户需要删除或添加事件,如果没有任何过渡动画,那么很有可能会让用户混淆刚刚发生的事情,不知道列表item是否添加或删除了。所以为了解决这样的问题,可以使用AnimatedContainer这样带动画的组件,而在Slivers中,可以使用SliverAnimatedList,通过SliverAnimatedListState用于动态插入item或删除item。让我们通过一个例子来学习SliverAnimatedList

添加SliverAnimatedList

这里_listKey是一个SliverAnimatedListStateGlobalKey类型。该GlobalKey有助于插入和删除列表项。initialItemCount是定义列表初始化有几个itemitemBuilder是需要延迟构建的组件。

final GlobalKey<SliverAnimatedListState> _listKey =

     GlobalKey<SliverAnimatedListState>();

SliverAnimatedList(

 key: _listKey,

 initialItemCount: _list.length,

 itemBuilder: _buildItem,

),

再来看下_buildItem:该index参数指示项目在列表中的位置。参数的值index将介于0和initialItemCount加上已插入insertItem的项目总数和减去已删除removeItem的项目总数。

Widget _buildItem(

   BuildContext context, int index, Animation<double> animation) {

 return CardItem(

   animation: animation,

   item: _list[index],

   selected: _selectedItem == _list[index],

   onTap: () {

     setState(() {

       _selectedItem = _selectedItem == _list[index] ? null : _list[index];

     });

   },

 );

}

实现将插入列表的_insert函数和从列表中删除的_remove函数

void _insert() {

 final int index =

     _selectedItem == null ? _list.length : _list.indexOf(_selectedItem!);

//添加

_list.insert(index, _nextItem++);

//更新UI

_listKey.currentState!.insertItem(index);

}


void _remove() {

   final int index = _selectedItem == null

       ? _list.length - 1

       : _list.indexOf(_selectedItem!);

   _list.removeAt(index);

//之所以需要这个方法,是因为移除的item在动画完成之前都是可见的。

   _listKey.currentState!.removeItem(

     index,

     (context, animation) => SizeTransition(

       sizeFactor: animation,

       child: Card(

         child: Center(

           child: Text(

             'Item $index',

             style: Theme.of(context).textTheme.headline4,

           ),

         ),

       ),

     ),

   );

   setState(() {

     _selectedItem = null;

   });

}

具体代码详见:点击跳转github详情代码地址

SliverOpacity

使用Opacity可以使组件变得透明,而在slivers中可以使用SliverOpacity。它们的不同之处只在于:Opacity适用于基于Box协议的组件,SliverOpacity适用于基于sliver协议的组件。

可控制属性有:

opacity:它通常取值介于0.0到之间1.0。当值为 0.0,则不会绘制 sliver child,若值为1.0,则会立即绘制 sliver child。除了0.01.0之外,使用其他的值是非常昂贵的,例如0.5,因为需要将sliver child 绘制到缓冲区中。

alwaysIncludeSemantics是否总是包含语义信息,默认是 false。该属性主要是用于辅助访问的对于视障人员来说会更友好。如果属性的值是 true,则不管透明度是多少,都会显示语义信息(可以辅助朗读)。

使用也非常简单:

bool _isVisible = true;


...

CustomScrollView(

 slivers: [

   SliverOpacity(

     opacity: _isVisible ? 1.0 : 0.0,

     sliver: SliverList(

       delegate: SliverChildListDelegate(

         [

           Container(

             height: 50,

             color: Colors.green,

             child: Center(

               child: Text(

                 "Taxze & SliverOpacity",

                 style: TextStyle(fontSize: 20, color: Colors.white),

               ),

             ),

           ),

         ],

       ),

     ),

   ),

   SliverPadding(

     padding: EdgeInsets.only(top: 24),

     sliver: SliverToBoxAdapter(

       child: FloatingActionButton(

         child: const Icon(Icons.flip),

         onPressed: () {

           setState(() {

             _isVisible = !_isVisible;

           });

         },

       ),

     ),

   )

 ],

),

SliverAnimatedOpacity

如果使用SliverOpacity,那么组件的不透明度会直接从0.0跳到1.0,转变的过程看起来不光滑。为了让Opacity平滑的过渡,可以使用SliverAnimatedOpacity

bool _visible = true;


CustomScrollView(

 slivers: [

   SliverAnimatedOpacity(

     //动画执行完毕

     onEnd: () => print("动画完成"),

     curve: Curves.linear,

     opacity: _visible ? 1.0 : 0.0,

     duration: const Duration(seconds: 2),

     sliver: SliverFixedExtentList(

         delegate: SliverChildBuilderDelegate(

             (BuildContext context, int index) {

           return Image.network(

               "https://p3-passport.byteimg.com/img/user-avatar/af5f7ee5f0c449f25fc0b32c050bf100~180x180.awebp");

         }, childCount: 1),

         itemExtent: 200.0),

   ),

   SliverPadding(

     padding: EdgeInsets.only(top: 18),

     sliver: SliverToBoxAdapter(

         child: FloatingActionButton(

       onPressed: () {

         setState(() {

           _visible = !_visible;

         });

       },

       tooltip: 'Toggle opacity',

       child: const Icon(Icons.flip),

     )),

   ),

 ],

),

SliverFadeTransition

这也是使用opacitysliver组件设置动画的。唯一的区别是,使用Animation<double>值而不是double值。

class _SliverPart3SliverFadeTransitionState

   extends State<SliverPart3SliverFadeTransition>

   with SingleTickerProviderStateMixin {

 late final AnimationController controller = AnimationController(

   duration: const Duration(milliseconds: 1000),

   vsync: this,

 );

 late final Animation<double> animation = CurvedAnimation(

   parent: controller,

   curve: Curves.easeIn,

 );


 @override

 void initState() {

   super.initState();

   animation.addStatusListener((AnimationStatus status) {

     if (status == AnimationStatus.completed) {

       controller.reverse();

     } else if (status == AnimationStatus.dismissed) {

       controller.forward();

     }

   });

   controller.forward();

 }


 @override

 Widget build(BuildContext context) {

   return CustomScrollView(slivers: <Widget>[

     SliverAppBar(

       title: Text('SliverPart3SliverFadeTransition'),

       pinned: true,

     ),

     SliverFadeTransition(

         opacity: animation,

         sliver: SliverToBoxAdapter(

           child: Center(

             child: Image.network(

               "https: //p3-passport.byteimg.com/img/user-avatar/af5f7ee5f0c449f25fc0b32c050bf100~180x180.awebp",

               width: double.infinity,

               height: 300,

               fit: BoxFit.cover,

             ),

           ),

         ))

   ]);

 }

}

SliverVisibility

SliverVisibilityVisibility组件相同,只是它是基于sliver协议的组件。

SliverVisibility有许多的参数,如:

  • maintainAnimation:用于判断animations不可见时是否保持在sliver子树内。
  • maintainInteractivity : 用于判断sliver隐藏时是否允许交互。
  • maintainSemantics : 用于确定隐藏时是否保持sliver的语义。
  • maintainSize:用于确定是否为sliver所在的位置保留空间。
  • maintainState : 用于判断不可见时是否维护sliver子树的State对象。
  • replacementSliver:sliver 不可见时,此处定义的 Widget 可见。

一个简单的例子来了解SliverVisibility:

bool _visible = true;


CustomScrollView(

 slivers: <Widget>[

   SliverVisibility(

       visible: _visible,

       replacementSliver: SliverToBoxAdapter(

         child: Container(

           width: double.infinity,

           height: 300,

           color: Colors.brown,

           child: Center(

             child: Text(

               "Image is hidden. Tap below button to show",

               style: TextStyle(color: Colors.white),

             ),

           ),

         ),

       ),

       sliver: SliverToBoxAdapter(

         child: Image.network(

           "https://p3-passport.byteimg.com/img/user-avatar/af5f7ee5f0c449f25fc0b32c050bf100~180x180.awebp",

           width: double.infinity,

           height: 300,

           fit: BoxFit.cover,

         ),

       )),

   SliverPadding(

     padding: EdgeInsets.only(top: 18),

     sliver: SliverToBoxAdapter(

         child: FloatingActionButton(

       onPressed: () {

         setState(() {

           _visible = !_visible;

         });

       },

       tooltip: 'Toggle visibility',

       child: Icon(!_visible ? Icons.visibility : Icons.visibility_off),

     )),

   ),

 ],

),

SliverIgnorePointer

如果希望sliver忽略点击或其他任何类型的交互时,只需将sliver组件包裹在里面SliverIgnorePointer。它将禁用该小部件的交互。

ignoring属性为true时,组件不可交互。当ignoringSemantics属性为true时,子树将对Semantics(语义)层不可见。

就用SliverVisibility组件的例子,只需要在控制按钮上,包裹上一层SliverIgnorePointer即可忽略点击事件。

SliverIgnorePointer(

 sliver: SliverPadding(

   padding: EdgeInsets.only(top: 18),

   sliver: SliverToBoxAdapter(

       child: FloatingActionButton(

     onPressed: () {

       setState(() {

         _visible = !_visible;

       });

     },

     tooltip: 'Toggle visibility',

     child:

         Icon(!_visible ? Icons.visibility : Icons.visibility_off),

   )),

 ),

),

SliverSafeArea

如果你曾经使用过SafeArea,那么SliverSafeArea除了它基于sliver协议外,它也在做同样的事情。例如当你想要显示一篇长文章时,便可使用SliverSafeArea来避免 iPhone X 上的缺口或其他类似的。

SliverSafeArea(sliver: SliverList(

 delegate: SliverChildBuilderDelegate((ctx, index) {

   return Container(

     color: Colors.primaries[index % Colors.primaries.length],

     height: 100,

   );

 },childCount: 20),

))

尾述

三篇文章,从为什么要使用Sliver,再根据使用频率逐个解析Slivers系列的组件。相信您已经入门了Sliver的世界。那么在下一章,将单独详解NestedScrollView,包括SliverOverlapAbsorber SliverOverlapInjector...的优化

相关文章
|
2月前
Flutter 组件(二)文本 与 输入框组件
Flutter 组件(二)文本 与 输入框组件
174 0
|
2月前
|
容器
Flutter 组件(一)组件概述
Flutter 组件(一)组件概述
71 0
|
10月前
|
存储 Dart 数据库
重识Flutter状态管理 — 探索Flutter中的状态
我遇到过很多没有了解过响应式编程框架的,或者从事后端开发,自己想用Flutter写个app玩玩的朋友,一上来,不管在哪里都用`setState`,我问为啥不用状态管理,大部分都回了一句:啥是状态管理?
|
11天前
flutter 导航组件 AppBar (含顶部选项卡TabBar,抽屉菜单 drawer ,自定义导航图标)
flutter 导航组件 AppBar (含顶部选项卡TabBar,抽屉菜单 drawer ,自定义导航图标)
11 1
|
2月前
|
设计模式 JavaScript 前端开发
flutter组件封装技巧
工厂函数不会自动调用,需要手动调用
31 3
|
2月前
|
Dart
Flutter 中优雅切换应用主题的组件
【Flutter】使用adaptive_theme组件优雅切换应用主题:封装MaterialApp,支持light/dark/system模式,自定义色彩,保存选择,含调试按钮。安装配置、设置主题、监听切换、自定义颜色、读取配置步骤详细。代码示例与学习路径推荐。[查看完整教程](https://flutter.ducafecat.com/blog/flutter-app-theme-switch)
Flutter 中优雅切换应用主题的组件
|
2月前
|
前端开发 搜索推荐 UED
【Flutter前端技术开发专栏】Flutter中的高级UI组件应用
【4月更文挑战第30天】探索Flutter的高级UI组件,如`TabBar`、`Drawer`、`BottomSheet`,提升应用体验和美观度。使用高级组件能节省开发时间,提供内置交互逻辑和优秀视觉效果。示例代码展示了如何实现底部导航栏、侧边导航和底部弹出菜单。同时,自定义组件允许个性化设计和功能扩展,但也带来性能优化和维护挑战。参考Flutter官方文档和教程,深入学习并有效利用这些组件。
【Flutter前端技术开发专栏】Flutter中的高级UI组件应用
|
10月前
|
人机交互
Flutter笔记 - ListTile组件及其应用
Flutter笔记 - ListTile组件及其应用
159 0
|
缓存 Dart 前端开发
Flutter 中使用 Widgetbook 管理你的组件 | 猫哥
Flutter 界面开发中我们有几个痛点 : - 与设计师协作复用一套设计规范(figma) - 可视化的管理你的组件代码(基础组件、业务组件) - 不同设备尺寸测试你的组件 - 实时修改你的测试组件参数
4229 1
Flutter 中使用 Widgetbook 管理你的组件 | 猫哥
|
2月前
|
开发者 索引 容器
Flutter开发笔记:Flutter 布局相关组件
Flutter开发笔记:Flutter 布局相关组件
177 0