时间:2022-09-10 09:20:14 | 栏目:Android代码 | 点击:次
我们之前讲述了动画构建的两种方式,Animation
和 AnimationWidget
,这两种构建动画都是将组件和动画一起完成的。有些时候,我们只是想动效复用,而不关心组件构建,这个时候就可以使用 AnimatedBuilder
了。
根据官方文档说明,AnimatedBuilder
的使用要点如下:
AnimatedBuilder
understands how to render the transition. —— AnimatedBuilder 知道如何渲染转场动效。AnimatedBuilder
doesn’t know how to render the widget, nor does it manage the Animation
object. —— AnimatedBuilder
不知道(或者准确说不应)如何渲染组件,也不管理组件对象。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 入门与实战(九十四):让你的组件拥有三维动效BottomSheet
, ExpansionTile
, PopupMenu
, ProgressIndicator
, RefreshIndicator
, Scaffold
, SnackBar
, TabBar
, TextField
. —— 在 Flutter 中,有很多组件使用 AnimatedBuilder 构建动效。这段话的核心要点就是 AnimatedBuilder
应该只负责动画效果的管理,而不应该管理组件构建。如果混在一起使用,就失去设计者的初衷了。这就好比我们的状态管理和界面一样,一个负责业务逻辑,一个负责界面渲染,从而实现解耦和复用。这个AnimatedBuilder
就是专门复制动效管理的,并且应当努力实现复用。AnimatedBuilder
的定义如下:
const AnimatedBuilder({ Key? key, required Listenable animation, required this.builder, this.child, }) : assert(animation != null), assert(builder != null), super(key: key, listenable: animation);
其中关键的参数是builder
,builder
用于构建组件的转变动作,在 builder
里可以对要渲染的子组件进行转变操作,然后返回变换后的组件。builder
的定义如下,其中 child
实际就是 AnimatedBuilder
的 child
参数,可以根据需要是否使用。
Widget Function(BuildContext context, Widget? child)
在 Flutter 中,提供了一个专门用于对子组件进行转换操作的,定义如下:
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
是一个坐标偏移量,实际会加入到 Matrix4
的 translation
(平移)中。alignment
:即转变进行的参考方位。child
:被转换的子组件。我们可以通过 Transform
,实现 AnimatedBuilder
的动效管理,也就是在 AnimatedBuilder
中,通过构建 Transform
对象实现动效。
基本概念讲清楚了(敲黑板:很多时候大家都是直接简单看一下文档就开始用,甚至干脆复制示例代码就上,结果很可能会用得不对),可以开始撸代码了。我们来实现下面的动效。
这里其实是两个组件,通过 AnimatedBuilder
做了动效转换。在动效的一半时间是文字“点击按钮变出小姐姐”,之后的一半将组件更换为了小姐姐的图片了。使用AnimatedBuilder
的实现代码如下:
class RotationSwitchAnimatedBuilder extends StatelessWidget { final Widget child1, child2; final Animation<double> 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
对象来刷新界面就好了,对应的代码如下:
class AnimatedBuilderDemo extends StatefulWidget { const AnimatedBuilderDemo({Key? key}) : super(key: key); @override _AnimatedBuilderDemoState createState() => _AnimatedBuilderDemoState(); } class _AnimatedBuilderDemoState extends State<AnimatedBuilderDemo> with SingleTickerProviderStateMixin { late Animation<double> animation; late AnimationController controller; @override void initState() { super.initState(); controller = AnimationController(duration: const Duration(seconds: 1), vsync: this); animation = Tween<double>(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
的概念和应用, Flutter 提供 AnimatedBuilder
组件的核心理念是为了创建和管理可复用的动画效果。在使用的时候,应该将动画效果和组件构建分离,从而使得AnimatedBuilder
构建的动画效果可以在需要的时候得到复用。