主本文主要说明动画的基本原理和简单的动画的实例,若有不当之处敬请指正。git
阅读本文大约须要 6 分钟github
给UI界面设计合理的动画,可让用户以为更加流畅、直观,提升用户的交互使用感觉,改善用户体验。markdown
在 Flutter 中动画分为两类:基于补间 (Tween) 的和基于物理 (Physics) 的;less
补间动画是介于二者之间的简称,在补间动画中定义起点和终点、时间点以及定义时间变化和速度的曲线,而后由系统计算如何从开始点到结束点。ide
物理动画是运动被模拟为与真实世界的行为类似,好比抛一件物体,它落在什么地方取决于这个物体的重量,抛出去的速度以及这个物体与地面的高度,相似数学中的抛物线运动轨迹。函数
在 Flutter 中想要实现动画效果离不开几个核心的角色:Animation(动画对象),AnimationController(动画控制器),Tweens(插值器),Curves(动画曲线);学习
在 Flutter 中动画自己和UI渲染没有任何关系,Animation是一个抽象类,它拥有其当前值和状态(完成或中止),Flutter 中的动画系统就是基于 Animation
对象的。其中比较经常使用的就是Animation类是Animation。它能够经过其 value 属性来获取当前动画的值。动画
Animation 除了能够生成 double 的值以外还能够生成如:颜色--Animation<Color>
或者大小--Animation<Size>
。ui
Animation 对象能够拥有 Listeners 和 StatusListeners 监听器,能够用 addListener()
和addStatusListener()
来添加。只要动画的的值发生变化,就会调用监听器。正常咱们在 Listeners 中调用setState() 来触发UI重建;动画开始、结束、向前移动或向后移动时会调用StatusListener。this
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();
}
复制代码
默认状况下,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);
复制代码
Curves 用来调整动画过程当中随时间的变化率,默认状况下,动画以均匀的线性模型变化。Flutter 内部也提供了一系列实现相应变化率的 Curves 对象:linea ------ 线性,decelerate ------ 减速等。
固然,也能够自定义继承 Curves 的类来定义动画的变化率,如:
class ShakeCurve extends Curve {
@override
double transform(double t) {
return math.sin(t * math.PI * 2);
}
}
复制代码
目前为止动画只是实现了自身数值的变化,并无让 Widget 动起来,这里咱们须要对动画数值进行监听,而后使用 setstatus 来更新 Widget 的属性,从而使 Widget 动起来。
添加数值监听:
Animation animation = CurvedAnimation(parent: _animationController, curve: Curves.linear);
animation.addListener((){
setState(() {
});
});
复制代码
除此以外咱们还能够监听动画的状态变动,当动画结束时咱们反转动画,当动画的反转也结束后咱们重新开始动画,这样动画就会一直这样循环下去。
状态变动监听:
animation.addStatusListener((status){
print(status);
});
复制代码
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);
}
复制代码
所谓的并行动画就是一块儿执行多个动画,在 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(),
),
),
);
}
}
复制代码
直接贴代码
///放大缩小动画
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();
},
)
],
),
],
);
}
复制代码
代码:
/// 淡入淡出
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。
到此,本文就结束了,若有不当之处敬请指正,一块儿学习探讨,谢谢🙏。