Flutter 掌握动画开发
主本文主要说明动画的基本原理和简单的动画的实例
阅读本文大约需要 6 分钟
背景
给UI界面设计合理的动画,可以让用户觉得更加流畅、直观,提高用户的交互使用感受,改善用户体验。
在 Flutter 中动画分为两类:基于补间 (Tween) 的和基于物理 (Physics) 的;
补间动画是介于两者之间的简称,在补间动画中定义起点和终点、时间点以及定义时间变化和速度的曲线,然后由系统计算如何从开始点到结束点。
物理动画是运动被模拟为与真实世界的行为相似,比如抛一件物体,它落在什么地方取决于这个物体的重量,抛出去的速度以及这个物体与地面的高度,类似数学中的抛物线运动轨迹。
介绍
在 Flutter 中想要实现动画效果离不开几个核心的角色:Animation(动画对象),AnimationController(动画控制器),Tweens(插值器),Curves(动画曲线);
1、Animation
在 Flutter 中动画本身和UI渲染没有任何关系,Animation是一个抽象类,它拥有其当前值和状态(完成或停止),Flutter 中的动画系统就是基于 Animation
对象的。其中比较常用的就是Animation类是Animation<double>
。它可以通过其 value 属性来获取当前动画的值。
Animation 除了可以生成 double 的值之外还可以生成如:颜色--Animation<Color>
或者大小--Animation<Size>
。
Animation 对象可以拥有 Listeners 和 StatusListeners 监听器,可以用 addListener()
和addStatusListener()
来添加。只要动画的的值发生变化,就会调用监听器。正常我们在 Listeners 中调用setState() 来触发UI重建;动画开始、结束、向前移动或向后移动时会调用StatusListener。
2、AnimationController
AnimationController 是一个特殊的 Animation
对象,在屏幕刷新的每一帧,就会生成一个新的值。默认情况下,AnimationController 会在特定的时间内线性的生成0.0到1.0的数字。AnimationController派生于 Animation<double>,因此可以在需要Animation对象的任何地方使用。不但如此,AnimationController还具有控制动画的其他方法,比如
forward()`方法可以启动动画。
AnimationController({
double value,
this.duration,
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
@required TickerProvider vsync,
})
创建 AnimationController 必须需传入 vsync
,传入 vsunc
是为了防止动画的UI不在当前屏幕时,不需要绘制,从而防止消耗不必要的资源。通过将 SingleTickerProviderStateMixin
混入到类定义中,就可以将 statefu l对象作为 vsync
的值。
除了 vsync
还可以传入正向动画执行的时间 duration
以及反向动画执行时间 reverseDuration
等。
常用函数:
序号 | 方法 | 介绍 |
---|---|---|
1 | forward() | 开始播放动画 |
2 | stop() | 停止动画 |
3 | reset() | 重制动画 |
4 | reverse() | 反向播放动画,必须处于正向动画播放完成的状态之后才有用 |
5 | dispose() | 释放动画占用资源 |
6 | repeat() | 循环播放动画 |
注意:动画完成时释放控制器(调用 dispose()
方法)以防止内存泄漏
@override
void dispose() {
animationController.dispose();
super.dispose();
}
3、Tween
默认情况下,AnimationController对象的范围从0.0到1.0。如果您需要不同的范围或不同的数据类型,则可以使用Tween来配置动画以生成不同的范围或数据类型的值。比如,可以生产从0-100的数字:
final Tween doubleTween = new Tween<double>(begin: 0.0, end: 100.0);
Tween是一个无状态(stateless)对象,继承自Animatable<T>
,而不是继承自Animation<T>
。Tween 需要两个值,分别是:begin 和 end。Tween的唯一职责就是定义从输入范围到输出范围的映射。
Animatable与Animation相似,不是必须输出double值,也可以是颜色,比如,从白色到黑色:
final Tween colorTween = new ColorTween(begin: Colors.withe, end: Colors.black);
Tween 可以通过 animate() 方法传入 controller 对象创建 Animation 对象。如下
AnimationController _animationController = AnimationController(animationBehavior:AnimationBehavior.normal,vsync: this);
Tween<double> _tween = Tween<double>(begin: 0.0, end: 100.0)..animate(_animationController);
4、CurvedAnimation
Curves 用来调整动画过程中随时间的变化率,默认情况下,动画以均匀的线性模型变化。Flutter 内部也提供了一系列实现相应变化率的 Curves 对象:linea ------ 线性,decelerate ------ 减速等。
当然,也可以自定义继承 Curves 的类来定义动画的变化率,如:
class ShakeCurve extends Curve {
@override
double transform(double t) {
return math.sin(t * math.PI * 2);
}
}
5、添加监听
目前为止动画只是实现了自身数值的变化,并没有让 Widget 动起来,这里我们需要对动画数值进行监听,然后使用 setstatus 来更新 Widget 的属性,从而使 Widget 动起来。
添加数值监听:
Animation animation = CurvedAnimation(parent: _animationController, curve: Curves.linear);
animation.addListener((){
setState(() {
});
});
除此之外我们还可以监听动画的状态变更,当动画结束时我们反转动画,当动画的反转也结束后我们从新开始动画,这样动画就会一直这样循环下去。
状态变更监听:
animation.addStatusListener((status){
print(status);
});
6、AnimatedWidget
AnimatedWidget 类允许您从 setState()
调用中的动画代码中分离出 widget 代码。AnimatedWidget 不需要维护一个 State 对象来保存动画。
以下代码为官方文档自定义 AnimatedLogo
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
class AnimatedLogo extends AnimatedWidget {
AnimatedLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: animation.value,
width: animation.value,
child: new FlutterLogo(),
),
);
}
}
class LogoApp extends StatefulWidget {
_LogoAppState createState() => new _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
controller.forward();
}
Widget build(BuildContext context) {
return new AnimatedLogo(animation: animation);
}
dispose() {
controller.dispose();
super.dispose();
}
}
AnimatedWidget
为什么不需要维护一个 State
对象来保存动画呢?
从 AnimatedWidget
源码中看一看出 AnimatedWidget 是继承自 StatefulWidget
类,在 AnimatedWidget
中,创建 state
是创建了 _AnimatedState
,接着看 _AnimatedState
类部分源码:
abstract class AnimatedWidget extends StatefulWidget{
@override
_AnimatedState createState() => _AnimatedState();
}
在 _AnimatedState
类的 initState
方法添加了监听 _handleChange
,并在 didUpdateWidget
和 dispose
方法中移除了,_handleChange
里面只有一行代码就是 setState
方法:
_AnimatedState 源码
class _AnimatedState extends State<AnimatedWidget> {
@override
void initState() {
super.initState();
// 添加监听
widget.listenable.addListener(_handleChange);
}
@override
void didUpdateWidget(AnimatedWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// 移除
if (widget.listenable != oldWidget.listenable) {
oldWidget.listenable.removeListener(_handleChange);
widget.listenable.addListener(_handleChange);
}
}
@override
void dispose() {
// 移除
widget.listenable.removeListener(_handleChange);
super.dispose();
}
void _handleChange() {
// 更新状态
setState(() {
// The listenable's state is our build state, and it changed already.
});
}
@override
Widget build(BuildContext context) => widget.build(context);
}
7、并行动画
所谓的并行动画就是一起执行多个动画,在 Flutter 中可以在同一个动画控制器上使用多个Tween,然后每个Tween管理动画中的不同效果,从而实现多个动画同时执行。
final AnimationController controller =
new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
final Animation<double> sizeAnimation =
new Tween(begin: 0.0, end: 300.0).animate(controller);
final Animation<double> opacityAnimation =
new Tween(begin: 0.1, end: 1.0).animate(controller);
可以通过sizeAnimation.value
来获取大小,通过opacityAnimation.value
来获取不透明度,但AnimatedWidget的构造函数只接受一个动画对象。 为了解决这个问题,可以创建了自己的Tween对象并显式计算了这些值。
其build
方法.evaluate()
在父级的动画对象上调用Tween函数以计算所需的size
和opacity
值。
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
class AnimatedLogo extends AnimatedWidget {
// The Tweens are static because they don't change.
static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
static final _sizeTween = new Tween<double>(begin: 0.0, end: 300.0);
AnimatedLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: new Opacity(
opacity: _opacityTween.evaluate(animation),
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
child: new FlutterLogo(),
),
),
);
}
}
实例
效果图
1、缩放动画
直接贴代码
///放大缩小动画
Widget scale() {
return Column(
children: <Widget>[
Container(
height: 170,
child: Center(
child: Container(
width: _scaleAnimation.value,
height: _scaleAnimation.value,
child: new FlutterLogo(),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
color: Colors.blue,
child: Text(
"放大",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_scaleController.forward();
},
),
RaisedButton(
color: Colors.red,
child: Text(
"缩小",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_scaleController.reverse();
},
)
],
),
],
);
}
2、淡入淡出动画
代码:
/// 淡入淡出
Widget alpha() {
return Column(
children: <Widget>[
Container(
height: 170,
child: Center(
child: Container(
height: 100,
width: 100,
child: Opacity(
opacity: _alphaAnimation.value,
child: FlutterLogo(),
),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
color: Colors.blue,
child: Text(
"淡入",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_alphaController.forward();
},
),
RaisedButton(
color: Colors.red,
child: Text(
"淡出",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_alphaController.reverse();
},
)
],
),
],
);
}
注意,一个 Widget 使用多个animationController 需要修改混入SingleTickerProviderStateMixin 为 TickerProviderStateMixin。
结尾
完整代码奉上GitHub地址:fluter_demo ,欢迎star和fork。
到此,本文就结束了,如有不当之处敬请指正,一起学习探讨,谢谢🙏。