forked from yang7229693/flutter-study
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5240303
commit cd275f2
Showing
1 changed file
with
253 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
# Flutter 动画详解(二) | ||
|
||
> 本文通过代码层面去分析Flutter动画的实现过程,介绍了Flutter中的Animation库以及Physics库。 | ||
## 1. 介绍 | ||
|
||
本文会从代码层面去介绍Flutter动画,因此不会涉及到Flutter动画的具体使用。 | ||
|
||
### 1.1 Animation库 | ||
|
||
Flutter的animation库只依赖两个库,Dart库以及physics库。animation是采用Dart编写的,所以依赖Dart库是很正常的。physics库是什么呢? | ||
|
||
> Simple one-dimensional physics simulations, such as springs, friction, and gravity, for use in user interface animations. | ||
physics库是一个简单的物理模拟的库,包含弹簧、阻尼、重力等物理效果。前篇文章介绍过Flutter动画,Flutter动画两个分类中的一个就是基于物理的动画(Physics-based animation)。所以可以猜测出animation库中有一部分代码,是实现了另一种动画--补间动画(Tween Animation)。 | ||
|
||
通过这种库的划分,也可以大致猜测出,基于物理动画的库是后续添加的。这说明了什么呢? | ||
|
||
* 补间动画是现代移动端相对基础的动画类型,这个是必须的; | ||
* 基于物理动画是在体验上的改善添加上去的,大致可以猜测出为iOS端的体验优化; | ||
|
||
### 1.2 Physics库 | ||
|
||
Flutter基于物理的动画,实际上是相当简单的。目前实现了弹簧、阻尼、重力三种物理效果,整个库的代码量也不多。详细的代码在下面的部分介绍,在此处,我们先来说下基于物理的动画库的原理。 | ||
|
||
基于物理的动画,给我们的感觉会更真实,这是因为其更符合人们日常生活的感官。例如做一个球体下落的动画,如果是匀速的下落,给人的感觉会不够真实,实际的生活经验告诉我们,球体自由下落应该是会有先慢后快的一个过程。如果让我们自己去实现这么一个动画效果,我们会怎么去处理呢? | ||
|
||
高中物理我们学习过自由落体相关的概念,其中的位移计算公式: | ||
|
||
> s = 1/2 * g * t * t | ||
从公式中我们知道,自由落体的位移跟时间不是线性关系。我们可以根据这个公式,来实时的计算出位移来。 | ||
|
||
如果是摩擦阻尼或者弹簧呢,也都有相关的物理公式,我们所谓的基于物理的动画库,也就是基于此类公式来实现的,本质上还是补间动画,只不过过程遵循物理规律比较复杂罢了。 | ||
|
||
## 2. Animation库 | ||
|
||
讲解这一部分,也考虑过将Flutter的动画原理先介绍一下。想了想,前一篇文章介绍过这些普世的动画原理,Flutter只不过是特定平台的实现,无非是实现手段的不同,因此,Flutter动画原理的解析,放到本文最后的小节部分,在代码的基础上去解释,笔者觉得更加好理解。 | ||
|
||
### 2.1 animation.dart | ||
|
||
animation.dart定义了动画的四种状态,以及核心的抽象类Animation。 | ||
|
||
#### 2.1.1 动画的四种状态 | ||
|
||
这个文件中定义了Animation的四种状态: | ||
|
||
* dismissed:动画的初始状态 | ||
* forward:从头到尾播放动画 | ||
* reverse:从尾到头播放动画 | ||
* completed:动画完成的状态 | ||
|
||
#### 2.1.2 Animation类 | ||
|
||
Animation类是Flutter动画中核心的抽象类,它包含动画的当前值和状态两个属性。定义了动画的一系列回调, | ||
|
||
* 动画过程中值变化的回调: | ||
|
||
``` | ||
void addListener(VoidCallback listener); | ||
void removeListener(VoidCallback listener); | ||
``` | ||
|
||
* 状态的回调函数: | ||
|
||
``` | ||
void addStatusListener(AnimationStatusListener listener); | ||
void removeStatusListener(AnimationStatusListener listener); | ||
``` | ||
|
||
### 2.2 curve.dart | ||
|
||
> A curve must map t=0.0 to 0.0 and t=1.0 to 1.0. | ||
看到这段英文,首先会想到什么?没错,插值器。Curve也是一个抽象类,定义了时间与数值的一个接口。 | ||
|
||
``` | ||
double transform(double t); | ||
``` | ||
|
||
例如一个线性的插值器,实现代码如下。 | ||
|
||
``` | ||
class _Linear extends Curve { | ||
const _Linear._(); | ||
@override | ||
double transform(double t) => t; | ||
} | ||
``` | ||
|
||
该文件下面定义了非常多类型的插值器,具体的实现不一一展开了。Flutter定义了一系列的插值器,封装在Curves类中,有下面13种效果。 | ||
|
||
* linear | ||
* decelerate | ||
* ease | ||
* easeIn | ||
* easeOut | ||
* easeInOut | ||
* fastOutSlowIn | ||
* bounceIn | ||
* bounceOut | ||
* bounceInOut | ||
* elasticIn | ||
* elasticOut | ||
* elasticInOut | ||
|
||
如果上面的13种还不满足需求的话,还可以使用Cubic类来进行自定义的构造。可以看出这块儿实现参考了web中的相关实现。 | ||
|
||
### 2.3 tween.dart | ||
|
||
该文件定义了一系列的估值器,Flutter通过抽象类Animatable来实现估值器。关于Animatable,我们可以先看下其定义。 | ||
|
||
> An object that can produce a value of type `T` given an [Animation<double>] | ||
as input. | ||
|
||
可以根据不同的输入,产出不同的数值。通过重载下面的函数来产生不同的估值器。 | ||
|
||
``` | ||
T transform(double t); | ||
``` | ||
|
||
它的最主要的子类是Tween,一个线性的估值器,实现如下,非常的简单,就是一个线性函数。 | ||
|
||
``` | ||
T lerp(double t) { | ||
assert(begin != null); | ||
assert(end != null); | ||
return begin + (end - begin) * t; | ||
} | ||
@override | ||
T transform(double t) { | ||
if (t == 0.0) | ||
return begin; | ||
if (t == 1.0) | ||
return end; | ||
return lerp(t); | ||
} | ||
``` | ||
|
||
在Tween的基础上实现了不同类型的估值器。 | ||
|
||
* ReverseTween | ||
* ColorTween | ||
* SizeTween | ||
* RectTween | ||
* IntTween | ||
* StepTween | ||
* ConstantTween | ||
|
||
还可以通过自定义的插值器去实现估值器,例如通过curve实现的估值器CurveTween。 | ||
|
||
### 2.4 animation_controller.dart | ||
|
||
动画的控制,就在这个文件下面实现,其中最重要的部分是AnimationController,它派生自Animation类。 | ||
|
||
AnimationController的功能有如下几种: | ||
|
||
* 播放一个动画(forwaed或者reverse),或者停止一个动画; | ||
* 设置动画的值; | ||
* 设置动画的边界值; | ||
* 创建基于物理的动画效果。 | ||
|
||
默认情况下,AnimationController是线性的产生0.0到1.0之间的值,每刷新一帧就产出一个数值。AnimationController在不使用的时候需要dispose,否则会造成资源的泄漏。 | ||
|
||
#### 2.4.1 TickerProvider | ||
|
||
提到AnimationController必须要先说一下TickerProvider。 | ||
|
||
> An interface implemented by classes that can vend Ticker objects. | ||
TickerProvider定义了可以发送Ticker对象的接口, | ||
|
||
``` | ||
Ticker createTicker(TickerCallback onTick); | ||
``` | ||
|
||
Ticker能干什么呢? | ||
|
||
> Tickers can be used by any object that wants to be notified whenever a frame triggers. | ||
它的主要作用是获取每一帧刷新的通知,作用就显而易见了,相当于给动画添加了一个动起来的引擎。 | ||
|
||
#### 2.4.2 AnimationController | ||
|
||
现在再次回到AnimationController。上面为什么要先说一下TickerProvider呢,这是因为AnimationController的构造函数中需要一个TickerProvider参数。 | ||
|
||
结合上面介绍的插值器、估值器以及Ticker回调,AnimationController大致的工作流程,我相信很多人都可以理出来了。 | ||
|
||
随着时间的流逝,插值器根据时间产生的值作为输入,提供给估值器,产生动画的实际效果值,结合Ticker的回调,渲染出当前动画值的图像。这也是补间动画的工作原理。 | ||
|
||
![补间动画](http://whysodiao.com/images/animation.jpg) | ||
|
||
AnimationController具体的源码不做分析了,可以看到Flutter的动画实现的其实是相当的原始,AnimationController需要一个触发刷新的回调,输出也是值的改变,并不像成熟平台里面的配合View去做动画。 | ||
|
||
## 3. Physics库 | ||
|
||
Physics库基本上就是插值器的实现部分,这部分比较简单 | ||
|
||
![Physics动画库](http://whysodiao.com/images/physic_lib.jpg) | ||
|
||
Simulation定义了基于物理动画的相关接口,具体有位置、速度、是否完成以及公差(Tolerance) | ||
|
||
``` | ||
double x(double time); | ||
double dx(double time); | ||
``` | ||
|
||
GravitySimulation的实现如下,其中_a加速度,_x是初始距离,_v是初始速度: | ||
|
||
``` | ||
@override | ||
double x(double time) => _x + _v * time + 0.5 * _a * time * time; | ||
@override | ||
double dx(double time) => _v + time * _a; | ||
``` | ||
|
||
相信学过高中物理的读者,对这公式不会陌生。其他几种具体实现不在此处一一展开了哈。如果扩展这个物理动画库的话,也很好去扩展,掌握一些物理公式,就可以去仿照实现了。 | ||
|
||
## 4. Ticker | ||
|
||
关于动画的驱动,在此简单的说一下,Ticker是被SchedulerBinding所驱动。SchedulerBinding则是监听着Window.onBeginFrame回调。 | ||
|
||
Window.onBeginFrame的作用是什么呢,是告诉应用该提供一个scene了,它是被硬件的VSync信号所驱动的。 | ||
|
||
具体可以查看sky_engine下面的window.dart的实现,不做展开了。 | ||
|
||
## 5. 小节 | ||
|
||
本篇文章简单的从代码的层面解析了一下Flutter的动画,更深入的Ticker这块儿,感兴趣的读者可以自行去了解,这块儿涉及到sky_engine下面的代码。 | ||
|
||
本篇文章,笔者依然试图绕过代码去讲解一些普适性的东西,但是Flutter这块儿代码实现的确实挺简单的,造成的问题是调用起来费劲。 | ||
|
||
基于物理的动画,我们要知道深层次的是物理公式,有这个基础,我们才可以制作出符合感官的动画效果。其本质也是补间动画,过程可以被计算出来。 | ||
|
||
可以说的宽泛一些,一般的动画,大部分都是补间动画,如果我们自行去设计一套动画系统,插值器、估值器、驱动部分以及动画的管理部分,这四个模块之间相互协调输出一帧一帧的动画过场。绝大部分平台的动画设计,也都逃不过这些因素,只不过实现的方式各不相同。 | ||
|
||
如果文中有错误的地方,烦请指正,笔者水平有限,再次感谢。 | ||
|
||
## 6. 后话 | ||
|
||
笔者建了一个Flutter学习相关的项目,[Github地址](https://github.com/yang7229693/flutter-study),里面包含了笔者写的关于Flutter学习相关的一些文章,会定期更新,也会上传一些学习Demo,欢迎大家关注。 | ||
|
||
## 7. 参考 | ||
|
||
1. [Animations in Flutter](https://flutter.io/animations/) | ||
2. [Tutorial: Animations in Flutter](https://flutter.io/tutorials/animation/) | ||
3. [TickerProvider class](https://docs.flutter.io/flutter/scheduler/TickerProvider-class.html) | ||
4. [Ticker class](https://docs.flutter.io/flutter/scheduler/Ticker-class.html) | ||
5. [SchedulerBinding class](https://docs.flutter.io/flutter/scheduler/SchedulerBinding-class.html) | ||
6. [onBeginFrame property](https://docs.flutter.io/flutter/dart-ui/Window/onBeginFrame.html) |