重识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...的优化

相关文章
|
3月前
|
传感器 缓存 监控
Stream 组件在 Flutter 中的应用场景有哪些?
Stream 组件在 Flutter 中的应用场景有哪些?
194 58
|
3月前
|
UED 开发者
Flutter|常用数据通信组件
Flutter|常用数据通信组件
114 49
|
3月前
|
开发工具
Flutter-AnimatedWidget组件源码解析
Flutter-AnimatedWidget组件源码解析
184 60
|
1月前
Flutter 自定义组件继承与调用的高级使用方式
本文深入探讨了 Flutter 中自定义组件的高级使用方式,包括创建基本自定义组件、继承现有组件、使用 Mixins 和组合模式等。通过这些方法,您可以构建灵活、可重用且易于维护的 UI 组件,从而提升开发效率和代码质量。
136 1
|
1月前
|
开发工具 UED
Flutter&鸿蒙next中封装一个输入框组件
本文介绍了如何创建一个简单的Flutter播客应用。首先,通过`flutter create`命令创建项目;接着,在`lib`目录下封装一个自定义输入框组件`CustomInput`;然后,在主应用文件`main.dart`中使用该输入框组件,实现简单的UI布局和功能;最后,通过`flutter run`启动应用。本文还提供了后续扩展建议,如状态管理、网络请求和UI优化。
107 1
|
1月前
|
Dart UED
Flutter用户交互组件
Flutter用户交互组件
30 2
|
2月前
|
存储 开发框架 开发者
flutter:代码存储&基本组件 (五)
本文档介绍了Flutter中的一些基本组件和代码示例,包括代码存储、基本组件如AppBar的简单使用、可滑动切换的标签栏、TextField的多种用法(如简单使用、登录页面、文本控制器的监听与使用、修饰等),以及如何实现点击空白区域隐藏键盘等功能。通过这些示例,开发者可以快速掌握在Flutter应用中实现常见UI元素的方法。
|
1月前
|
开发工具
Flutter&鸿蒙next中封装一个列表组件
Flutter&鸿蒙next中封装一个列表组件
50 0
|
3月前
|
开发者
Flutter|常用数据通信组件
Flutter|常用数据通信组件
|
3月前
Stream 组件在 Flutter 中的具体使用方法是什么?
Stream 组件在 Flutter 中的具体使用方法是什么?