Flutter 入门与实战(九十六):使用 AnimatedBuilder 分离组件和动画,实现动效复用

语言: CN / TW / HK


theme: channing-cyan highlight: darcula


小知识,大挑战!本文正在参与「程序员必备小知识」创作活动。

前言

我们之前讲述了动画构建的两种方式,AnimationAnimationWidget,这两种构建动画都是将组件和动画一起完成的。有些时候,我们只是想动效复用,而不关心组件构建,这个时候就可以使用 AnimatedBuilder 了。

AnimatedBuilder 介绍

根据官方文档说明,AnimatedBuilder 的使用要点如下:

  • An AnimatedBuilder understands how to render the transition. —— AnimatedBuilder 知道如何渲染转场动效。
  • An AnimatedBuilder doesn’t know how to render the widget, nor does it manage the Animation object. —— AnimatedBuilder 不知道(或者准确说不应)如何渲染组件,也不管理组件对象。
  • Use AnimatedBuilder to describe an animation as part of a build method for another widget. If you simply want to define a widget with a reusable animation, use an AnimatedWidget. —— 使用 AnimatedBuilder 作为其他组件的动效描述。如果只是想复用一个带有动效的组件,那么应该使用 AnimatedWidget。这个可以看我们之前关于 AnimatedWidget 的介绍:Flutter 入门与实战(九十四):让你的组件拥有三维动效
  • Examples of AnimatedBuilders in the Flutter API: BottomSheet, ExpansionTile, PopupMenu, ProgressIndicator, RefreshIndicator, Scaffold, SnackBar, TabBar, TextField. —— 在 Flutter 中,有很多组件使用 AnimatedBuilder 构建动效。

这段话的核心要点就是 AnimatedBuilder 应该只负责动画效果的管理,而不应该管理组件构建。如果混在一起使用,就失去设计者的初衷了。这就好比我们的状态管理和界面一样,一个负责业务逻辑,一个负责界面渲染,从而实习解耦和复用。这个AnimatedBuilder就是专门复制动效管理的,并且应当努力实现复用。 AnimatedBuilder的定义如下: dart const AnimatedBuilder({ Key? key, required Listenable animation, required this.builder, this.child, }) : assert(animation != null), assert(builder != null), super(key: key, listenable: animation); 其中关键的参数是builderbuilder 用于构建组件的转变动作,在 builder 里可以对要渲染的子组件进行转变操作,然后返回变换后的组件。builder 的定义如下,其中 child 实际就是 AnimatedBuilderchild 参数,可以根据需要是否使用。 dart Widget Function(BuildContext context, Widget? child)

Transform 组件介绍

在 Flutter 中,提供了一个专门用于对子组件进行转换操作的,定义如下: dart const Transform({ Key? key, required this.transform, this.origin, this.alignment, this.transformHitTests = true, Widget? child, }) : assert(transform != null), super(key: key, child: child); 这里的参数说明如下:

  • transform 是一个Matrix4 对象,用于定义三维空间的变换操作。
  • origin 是一个坐标偏移量,实际会加入到 Matrix4translation(平移)中。
  • alignment:即转变进行的参考方位。
  • child:被转换的子组件。

我们可以通过 Transform,实现 AnimatedBuilder 的动效管理,也就是在 AnimatedBuilder 中,通过构建 Transform 对象实现动效。

应用

基本概念讲清楚了(敲黑板:很多时候大家都是直接简单看一下文档就开始用,甚至干脆复制示例代码就上,结果很可能会用得不对),可以开始撸代码了。我们来实现下面的动效。 AnimatedBuilder动效.gif 这里其实是两个组件,通过 AnimatedBuilder 做了动效转换。在动效的一半时间是文字“点击按钮变出小姐姐”,之后的一半将组件更换为了小姐姐的图片了。使用AnimatedBuilder 的实现代码如下: ```dart class RotationSwitchAnimatedBuilder extends StatelessWidget { final Widget child1, child2; final Animation animation; const RotationSwitchAnimatedBuilder( {Key? key, required this.animation, required this.child1, required this.child2}) : super(key: key);

@override Widget build(BuildContext context) { return AnimatedBuilder( animation: animation, builder: (context, child) { if (animation.value < 0.5) { return Transform( transform: Matrix4.identity() ..rotateZ(animation.value * pi) ..setEntry(0, 1, -0.003), alignment: Alignment.center, child: child1, ); } else { return Transform( transform: Matrix4.identity() ..rotateZ(pi) ..rotateZ(animation.value * pi) ..setEntry(1, 0, 0.003), child: child2, alignment: Alignment.center, ); } }, ); } } 注意第2个组件多转了180度,是未来保证停止后正好旋转360度,以免图片倒过来。另外就是这里的 `child1`和 `child2`也可以修改为使用 `WidgetBuilder` 函数来在需要的时候再构建组件。 使用这个`RotationSwitchAnimatedBuilder`的组件就十分简单了,将需要操作的两个组件作为参数传过来,然后控制 `Animation` 对象来刷新界面就好了,对应的代码如下:dart class AnimatedBuilderDemo extends StatefulWidget { const AnimatedBuilderDemo({Key? key}) : super(key: key);

@override _AnimatedBuilderDemoState createState() => _AnimatedBuilderDemoState(); }

class _AnimatedBuilderDemoState extends State with SingleTickerProviderStateMixin { late Animation animation; late AnimationController controller;

@override void initState() { super.initState(); controller = AnimationController(duration: const Duration(seconds: 1), vsync: this); animation = Tween(begin: 0, end: 1.0).animate(controller); }

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('AnimatedBuilder 动画'), ), body: RotationSwitchAnimatedBuilder( animation: animation, child1: Center( child: Container( padding: EdgeInsets.all(10), margin: EdgeInsets.all(10), constraints: BoxConstraints(minWidth: double.infinity), decoration: BoxDecoration( borderRadius: BorderRadius.circular(4.0), gradient: LinearGradient( colors: [ Colors.orange, Colors.green, ], ), ), child: Text( '点击按钮变出小姐姐', style: TextStyle( fontSize: 20, color: Colors.white, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), ), child2: Center( child: Image.asset('images/beauty.jpeg'), ), ), floatingActionButton: FloatingActionButton( child: Icon(Icons.play_arrow, color: Colors.white), onPressed: () { if (controller.status == AnimationStatus.completed) { controller.reverse(); } else { controller.forward(); } }, ), ); }

@override void dispose() { controller.dispose(); super.dispose(); } } ``` 复用的话也很容易了,比如我们将一个圆形和一个矩形组件传过去,一样可以复用这个动画效果。 AnimatedBuilder动效复用.gif 完整源码已提交至:动画相关源码

总结

本篇介绍了 AnimatedBuilder 的概念和应用, Flutter 提供 AnimatedBuilder组件的核心理念是为了创建和管理可复用的动画效果。在使用的时候,应该将动画效果和组件构建分离,从而使得AnimatedBuilder构建的动画效果可以在需要的时候得到复用。

我是岛上码农,微信公众号同名,这是Flutter 入门与实战的专栏文章,提供体系化的 Flutter 学习文章。对应源码请看这里:Flutter 入门与实战专栏源码。如有问题可以加本人微信交流,微信号:island-coder。觉得有收获请按如下方式给个爱心三连

👍🏻:点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!

「其他文章」