在Flutter 框架中大致有3 类与动画相关的组件,分别是隐式动画(Implicit Animation)、显式动画(Explicit Animation)及其他的动画实现方法。本章将延续第7章的内容,继续深入探讨Flutter中与动画相关的组件和知识。
01
如何选择动画组件
《Flutter 组件详解与实战》第7章“过渡动画”中介绍了一部分较为简单易用的动画组件,如AnimatedContainer、AnimatedOpacity、AnimatedSwitcher等。这些组件的特点为使用门槛较低,不要求开发者掌握太多与动画相关的知识,往往只需传入一个表示动画时长的duration参数。每当这些组件的属性值发生改变时,它们就会自动完成补间动画。这背后的实现原理和烦琐的操控细节都被隐去了,因此这类动画在Flutter框架中被称为隐式动画。
然而有时候隐式动画并不能满足实际需求。例如当动画需要循环重播、随时中断或多方协调时,往往开发者会更希望能精确地控制动画的进程。这时就应该考虑使用Flutter框架提供的“显式动画”组件了。从命名习惯而言,不同于常以Animated...开头的隐式动画组件,显式动画组件通常以...Transition结尾,如FadeTransition、SlideTransition、SizeTransition等。在使用显式动画时,开发者需要自行创建并维护一个AnimationController(动画控制器),通过它来控制动画的开始、暂停、重置、跳转、倒播等操作。
当Flutter框架提供的隐式动画和显式动画组件都不足以满足实际需求时,也可以考虑使用TweenAnimationBuilder自定义隐式动画,或使用AnimatedBuilder自定义显式动画。尤其是后者,若再配合CustomPaint等支持随意绘制的画布组件,则更可完成一切动画需求,但显然这么做难免会增加代码的难度,延长开发周期,因此实战中选择合适的动画组件相对重要。图12-1总结了在选择动画组件时应考虑的一些因素。
■ 图12-1 如何选择合适的动画组件
02
显式动画
在Flutter框架中使用显式动画就可以通过动画控制器完成循环重播、随时中断或多方协调等相对复杂的动画需求。
1
●
显式动画
这是一个可用于制作旋转效果的显式动画组件,旋转角度由turns参数传入,代码如下:
RotationTransition(
turns: _controller,
child: FlutterLogo,
)
这里turns参数需要接收的是Animation double 类型,用于描述一个旋转的动画,其中double部分表示转了几圈,例如0.5就表示转了半圈,即180°,而1.0则表示旋转了360°,与完全没有旋转时的0.0效果一致,因此,若需实现不停旋转的效果,这里需要传入的是0.0~1.0的循环值。
由于AnimationController的默认区间恰好也是0.0~1.0,因此可以直接将一个动画控制器作为Animation double 值传给RotationTransition组件的turns参数,完整代码如下:
//第12章/rotation_transition_demo.dart
import'package:flutter/material.dart';
voidmain{
runApp(MyApp);
}
classMyAppextendsStatelessWidget{
@override
Widget build(BuildContext context){
returnMaterialApp(
home: Scaffold(
appBar: AppBar(title: Text( "RotationTransition Demo")),
body: Center(
child: AnimationDemo,
),
),
);
}
}
classAnimationDemoextendsStatefulWidget{
@override
_AnimationDemoState createState=> _AnimationDemoState;
}
class_AnimationDemoStateextendsState< AnimationDemo>
withSingleTickerProviderStateMixin{
late AnimationController _controller;
@override
voidinitState{
_controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
)..repeat;
super.initState;
}
@override
voiddispose{
_controller.dispose;
super.dispose;
}
@override
Widget build(BuildContext context){
returnRotationTransition(
turns: _controller,
child: FlutterLogo(size: 80),
);
}
}
上述代码首先通过SingleTickerProviderStateMixin 得到ticker,并定义了一个AnimationController且在initState初始化时将其动画时长设置为1s,最后通过调用repeat方法使其循环播放,运行时即可观察到其子组件FlutterLogo循环旋转的动画效果,如图12-2所示。
■ 图12‑2使用RotationTransition实现旋转效果
除了repeat(循环播放)外,动画控制器还支持forward(正序播放一次)、reverse(倒序播放一次)、stop(原地停止)、reset(重置)、animateTo(动画至某个值后再停下)等方法。
2
●
FadeTransition
这是一个负责改变不透明度的显式动画组件,可以算作隐式动画AnimatedOpacity组件的显式动画版本,因此它可以被动画控制器操作,随时暂停等。该组件用法与12.2.1节的RotationTransition大同小异,通过opacity参数接收不透明度值,代码如下:
FadeTransition(
opacity: _controller,
child: FlutterLogo,
)
例如可提供一些按钮让用户手动控制动画的进展,效果如图12-3所示。
■ 图12‑3使用FadeTransition手动控制不透明度动画
用于实现图12-3所示效果的完整代码如下:
//第12章/fade_transition_demo.dart
import'package:flutter/material.dart';
voidmain{
runApp(MyApp);
}
classMyAppextendsStatelessWidget{
@override
Widget build(BuildContext context){
returnMaterialApp(
home: Scaffold(
appBar: AppBar(title: Text( "FadeTransition Demo")),
body: Center(
child: AnimationDemo,
),
),
);
}
}
classAnimationDemoextendsStatefulWidget{
@override
_AnimationDemoState createState=> _AnimationDemoState;
}
class_AnimationDemoStateextendsState< AnimationDemo>
withSingleTickerProviderStateMixin{
late AnimationController _controller;
@override
voidinitState{
_controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
super.initState;
}
@override
voiddispose{
_controller.dispose;
super.dispose;
}
@override
Widget build(BuildContext context){
returnRow(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FadeTransition(
opacity: _controller,
child: FlutterLogo(size: 80),
),
ElevatedButton(
child: Text( "开始"),
onPressed: => _controller.repeat(reverse: true),
),
ElevatedButton(
child: Text( "暂停"),
onPressed: => _controller.stop,
),
],
);
}
}
值得一提的是,上例的开始按钮调用_controller.repeat方法时传入了reverse: true参数,该参数的作用是每当动画控制器播放结束(值为1.0)时,倒序播放一遍,再循环重播。换言之,当没有传入该参数时,动画控制器默认的循环播放是从0.0渐变至1.0,然后瞬间重置至0.0,再循环播放,而设置reverse后,循环播放的行为会改为从0.0渐变至1.0,再从1.0渐变至0.0,以此循环播放。
3
●
ScaleTransition
这是一个可以缩放子组件的显式动画组件,用法与其他显式动画组件非常相似,不同点在于它需要通过scale参数接收缩放倍数,代码如下:
ScaleTransition(
scale: _controller,
child: FlutterLogo,
)
例如当scale为2.0时,子组件会被放大为原来的2倍,而当scale为0.5时,则子组件就会被缩小为原来的一半。
4
●
SizeTransition
这是一个借助ClipRect 对子组件尺寸进行裁剪的显式动画组件,运行时通过sizeFactor属性值决定对子组件的裁剪行为,代码如下:
SizeTransition(
sizeFactor: _controller,
child: FlutterLogo,
)
例如当sizeFactor的值从1.0渐变至0.0时,可观察到子组件逐渐被裁剪至完全消失的效果,如图12-4所示。该组件默认行为是沿垂直方向中心裁剪,如有必要,也可通过传入axis: Axis.horizontal改为沿水平方向裁剪,或通过axisAlignment属性改变裁剪的对齐方式。
■ 图12‑4使用SizeTransition对子组件进行裁剪动画
12.2.3节介绍的ScaleTransition的内部是通过Transform 组件对子组件变形,从而在绘制时达到缩放的效果,而这里的SizeTransition则是通过直接改变自身尺寸,并配合ClipRect对子组件裁剪,在布局时达到需要的动画效果,然而在Flutter布局中,组件的尺寸必须满足父级约束,因此实战中要注意该组件的父级约束不应为紧约束,否则无法观察到动画效果。对组件约束等概念不熟悉的读者可参考本书第6章“进阶布局”中关于“约束”的内容,对Transform 变形或ClipRect裁剪等内容不熟悉的读者可查阅本书第14章“渲染与特效”中对这2个组件的介绍。
5
●
SideTransition
这是一个负责平移的显式动画组件,使用时需通过position 属性传入一个Animation Offset 表示位移程度,通常借助Tween实现,代码如下:
SlideTransition(
position: Tween(
begin: Offset( 0, 0),
end: Offset( 0.5, -1.2),
).animate(_controller),
child: FlutterLogo( size: 100),
)
上述代码通过Tween的begin参数将动画起始位置定义为Offset(0,0),又通过end参数将结束位置定义为Offset(0.5, -1.2)。这些值是指相对于该组件的尺寸,即应由初始位置偏移的比例,因此动画起始的(0,0)就表示原地不动,而结束位置的(0.5, -1.2)实际表示的是向右移动0.5倍于整个子组件的宽度,并向上移动1.2倍于子组件的高度。若子组件的尺寸为100×100单位,则实际动画时长内组件会向右平移50单位,并同时向上平移120单位。
6
●
PositionedTransition
这个PositionedTransition是隐式动画AnimatedPositioned组件的显式动画版本,因此这2个组件都与第1章介绍的普通Positioned组件一样,可在Stack中使用,代码如下:
Stack(
children: [
PositionedTransition(
rect: RelativeRectTween(
begin: RelativeRect.fromLTRB( 0, 0, 200, 200),
end: RelativeRect.fromLTRB( 100, 100, 0, 0),
).animate(_controller),
child: FlutterLogo( size: 100),
),
],
)
这里值得一提的是,该组件的rect参数需要的类型为Animation RelativeRect ,可由动画控制器串联RelativeRectTween得到。通过RelativeRect.fromLTRB构造函数,开发者可同时指定left、top、right、bottom 这4个方向的值,用法与Positioned组件的同名参数非常类似。
7
●
DecoratedBoxTransition
这个组件是普通DecoratedBox 的显式动画版本,需由decoration 参数接收类型为Animation Decoration 的值,一般由DecorationTween提供,代码如下:
DecoratedBoxTransition(
decoration: DecorationTween(
begin: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular( 50.0),
),
end: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.zero,
),
).animate(_controller),
child: FlutterLogo( size: 100),
)
运行时该组件会由灰色的圆形装饰,渐变至黑色的矩形装饰,如图12-5所示。
■ 图12‑5使用DecoratedBoxTransition对子组件进行裁剪动画
这个组件看似没有对应的隐式动画组件,但实际上第7章介绍的AnimatedContainer组件就支持decoration属性,也会在decoration值发生变化时自动触发隐式动画效果,可完全当作该组件的隐式动画版本。对DecoratedBox组件或BoxDecoration类不熟悉的读者可参考本书第14章“渲染与特效”中的相关内容。
8
●
AnimatedIcon
AnimatedIcon顾名思义,是一个用于提供动画图标的组件。它的名字虽然是以Animated...开头的,但它实际上是一个显式动画组件,需要通过progress属性传入动画控制器,另外还需要由icon属性传入动画图标数据,代码如下:
AnimatedIcon(
progress: _controller,
icon: AnimatedIcons.arrow_menu,
)
■ 图12‑6AnimatedIcon之arrow_menu的动画效果
图12-7列出了目前Flutter内置的14种动画图标,以及它们对应的名称,供读者参考。
■ 图12‑7Flutter内置的14种动画图标及名称
03
参考书籍
《Flutter 组件详解与实战》
ISBN:978-7-302-59420-8
[加] 王浩然(Bradley Wang) 编著
定价:109元
04
精彩推荐
微信小程序游戏开发│猜数字小游戏(附源码+视频)
Flink编程基础│Scala编程初级实践
Flink编程基础│FlinkCEP编程实践
Flink编程基础│DataStream API编程实践
Flink编程基础│DataSet API编程实践
数 据分析实战│客户价值分析
数据分析实战│价格预测挑战
数据分析实战│时间序列预测
数据分析实战│KaggleTitanic生存预测