在Flutter 动画详解系列——隐式动画中我们介绍了Flutter中的隐式动画,隐式动画创建简单、灵活性差,而显式动画相对来说更加灵活。接下来让我们来看看 Flutter 中的显式动画。
Flutter内置显示动画FooTransition(Foo代表动画需要改变的属性值)
先来看一张图,下面列出了部分Flutter中内置的显式动画Widget:
flutter_transition.png
我们可以通过直接使用上述的Widget,来创建显示动画,让我们以RotationTransition
为例来看看显式动画如何创建。
RotationTransition Demo
// RotationTransition 构造器
RotationTransition({
Widget child,
Alignment alignment,
Animation<double> turns,
})
child 指定哪个widget用来做旋转动画,alignment是坐标系原点的对齐方式,我们想要有银河星系的旋转效果使用center作为坐标系原点。turns:在API文档中定义是Animation
抽象类型,turns是显示动画有别于隐式动画的的重要参数。我们使用隐式动画Widget AnimatedContainer
更改transform
也能达到图片旋转的效果,但是隐式动画只会执行一次便会停止。借助于显式动画,我们不仅可以控制动画时间长,并可以让动画一直重复执行。
创建AnimationController
AnimationController 顾名思义,是用来控制动画,AnimationController 继承自抽象类Animation<double>
,AnimationController 可以监听动画的执行、还能提供一些动画的控制操作。
class _TimeMachineState extends State<TimeMachine> {
AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
// ...
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
AnimationController 需要传递两个必要参数,duration
即我们动画的执行时间,默认 AnimationController 在给定的时间段内会线性的生成从0.0到1.0的数字。而具体值的数量和粒度取决于之前的duration参数。设置 vsync
时用来防止屏幕外动画(动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
class _TimeMachineState extends State<TimeMachine>
with SingleTickerProviderStateMixin {
AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: Duration(seconds: 15),
vsync: this,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
当我们创建完AnimationController后,不要忘记开启动画:
_animationController = AnimationController(
duration: Duration(seconds: 15),
vsync: this,
)..repeat();
最后,我们回到上面 RotationTransition 银河旋转动画效果的代码部分,补充完turns参数。
RotationTransition(
child: GalaxyFitz(),
alignment: Alignment.center,
turns: _animationController,
)
同时,我们也可以通过_animationController来对动画进行控制,如下我们添加了一个100的正方形手势,来暂停、开启动画效果。
class TimeStopper extends StatelessWidget {
final AnimationController controller;
const TimeStopper({Key key, this.controller}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (controller.isAnimating) {
controller.stop();
} else {
controller.repeat();
}
},
child: Container(color: Colors.red,width: 100,height: 100,),
);
}
}
小结
相对于隐式动画,显式动画需要一个动画的控制器,我们知道了在Flutter中存在内置的显示动画效果FooTransition
。我们可以通过设置 duration 、和 AnimationController 来创建一个重复循环的动画效果,并且我们可以通过 AnimationController 来控制动画。那么当系统自带的显式动画Widget无法满足我们时,我们就需要创建自定义的显式动画啦。
使用AnimatedBuilder和AnimatedWidget创建自定义显式动画
为了更加具体的了解AnimatedBuilder和AnimatedWidget,我们编写一个外星飞船的动画demo,动画效果是飞船的传输光线。
首先自定义一个Path剪切器,形成光束的形态。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyHomePage(),
));
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
ClipPath(
clipper: const BeamClipper(),
child: Container(
height: 1000,
decoration: BoxDecoration(
gradient: RadialGradient(
radius: 1.5,
colors: [
Colors.yellow,
Colors.transparent,
],
),
),
),
),
],
);
}
}
class BeamClipper extends CustomClipper<Path> {
const BeamClipper();
@override
getClip(Size size) {
return Path()
..lineTo(size.width / 2, size.height / 2)
..lineTo(size.width, size.height)
..lineTo(0, size.height)
..lineTo(size.width / 2, size.height / 2)
..close();
}
/// Return false always because we always clip the same area.
@override
bool shouldReclip(CustomClipper oldClipper) => false;
}
截屏2020-10-14 下午2.08.37.png
接下来就可通过AnimatedBuilder和 AnimatedWidget 来实现动画效果了。
AnimatedBuilder
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
final Image starsBackground = Image.asset(
'assets/milky-way.jpg',
);
final Image ufo = Image.asset('assets/ufo.png');
AnimationController _animation;
@override
void initState() {
super.initState();
_animation = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)..repeat();
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
starsBackground,
AnimatedBuilder(
animation: _animation,
builder: (_, __) {
return ClipPath(
clipper: const BeamClipper(),
child: Container(
height: 1000,
decoration: BoxDecoration(
gradient: RadialGradient(
radius: 1.5,
colors: [
Colors.yellow,
Colors.transparent,
],
stops: [0, _animation.value],
),
),
),
);
},
),
ufo,
],
);
}
@override
void dispose() {
_animation.dispose();
super.dispose();
}
}
class BeamClipper extends CustomClipper<Path> {
const BeamClipper();
@override
getClip(Size size) {
return Path()
..lineTo(size.width / 2, size.height / 2)
..lineTo(size.width, size.height)
..lineTo(0, size.height)
..lineTo(size.width / 2, size.height / 2)
..close();
}
/// Return false always because we always clip the same area.
@override
bool shouldReclip(CustomClipper oldClipper) => false;
}
跟之前Flutter 系统内置的显式动画一样,我们需要创建一个动画控制器、混入 SingleTickerProviderStateMixin 防止屏幕外动画。
之前在介绍 TweenAnimationBuilder 文章中提到过,在设置 child 参数的时候,如果在动画过程中 child 不会变动,我们可以提前构建它,然后将其传递给AnimatedBuilder。在这个例子中,BeamClipper 可以提供一个常量的构造函数生成常量对象,用于优化动画的性能。
AnimatedWidget
使用 AnimatedBuilder 代码构建出来的动画效果,可能代码有点臃肿,难以阅读。此时我们可以尝试创建一个 AnimatedWidget 的子类,来完成同样的动画效果。与系统的内置显式动画效果命名一致,推荐FooTransition的命名原则。这里我们将其命名为BeamTransition
,
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
final Image starsBackground = Image.asset(
'assets/milky-way.jpg',
);
final Image ufo = Image.asset('assets/ufo.png');
AnimationController _animation;
@override
void initState() {
super.initState();
_animation = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)..repeat();
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: AlignmentDirectional.center,
children: <Widget>[
starsBackground,
BeamTransition(animation: _animation),
ufo,
],
);
}
@override
void dispose() {
_animation.dispose();
super.dispose();
}
}
class BeamTransition extends AnimatedWidget {
BeamTransition({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return ClipPath(
clipper: const BeamClipper(),
child: Container(
height: 1000,
decoration: BoxDecoration(
gradient: RadialGradient(
radius: 1.5,
colors: [
Colors.yellow,
Colors.transparent,
],
stops: [0, animation.value],
),
),
),
);
}
}
我们将上面AnimatedBuilder中build的方法替换了下。将创建动画的代码抽取到了BeamTransition中。注意在BeamTransition中 child 中不变部分的我们使用常量值。
总结
到此,Flutter 中显式动画的介绍也告一段落了,显式动画能够循环的执行下去、并且通过动画控制器我们可以轻松的控制动画。在自定义显式动画时,比较推荐使用 AnimatedWidget
单独的抽取成一个独立的Widget。当然如果动画足够简单,使用 AnimatedBuilder 不会造成代码阅读的困难的话,也可以是用AnimatedBuilder。
网友评论