动画实现的方式
Flutter 中, 我们可以简单的把调用 this.setState() 理解为渲染一帧. 那么只要我们不停的在调用这个方法的同时更新位置信息, 就能实现平移动画了, 其他动画也是如此.
而 Flutter 也是这么做的.
这个方式说起来很简单, 但是想要将它封装成一个使用简单的框架, 却很不容易.
Flutter 的实现
在 Flutter 中有两大基类 Animatable 和 Animation
Animatable
这个控制的是动画的类型的类. 例如平移动画我们关心的是 x,y. 那么 Animatable 就需要控制 x,y 的变化. 颜色动画我们关心的是色值得变化, 那么 Animatable 就需要控制色值.
贝塞尔曲线运动, 我们关心的是路径是按照贝塞尔方程式来生成 x y, 所以 Animatable 要有按照贝塞尔方程式的方式改变 x,y.
Animation
这个是控制动画运动过程的类, 不关心动画的类型. 例如动画开始, 停止, 反转, 还有各种 ease 得效果.
并不关心你是平移, 缩放还是贝塞尔曲线动画. 因为所有的动画这些状态都是一样的.
假如我们想实现一个从 0 平移到 200 位置的动画该怎么做呢?
按照 Flutter 的实现方式我们要先要实现一个对应的 Animatable. 当然 flutter 已经为我门预制了很多类, Tween 这个类就可已实现.
很多说 Tween 是补间动画, 自认为很是不准确. 这里的 Tween 其实是一个一元一次函数的实现. 简单的说就是单个维度的渐变动画.
如果要实现多维度的动画, 就需要自己实现 Animatable.
- T lerp(double t) {
- assert(begin != null);
- assert(end != null);
- return begin + (end - begin) * t;
- }
- T transform(double t) {
- if (t == 0.0)
- return begin;
- if (t == 1.0)
- return end;
- return lerp(t);
- }
有了 Animatable, 我们还需要一个 Animation, 要不然怎么开始动画?
当然 Animation Flutter 也为我们预制了. AnimationController 就是一个.
各种类都有了就开始写代码
- class _SimpleRouteState extends State<SimpleRoute> with SingleTickerProviderStateMixin{
- ...
- AnimationController _controller;
- @override
- void initState() {
- _controller = AnimationController(
- duration : Duration(seconds: 1) ,
- vsync: this
- );
- ...
- }
- // 点击按钮 开始动画
- _offsetAnim(bool isForward){
- Animation<double> animation = Tween(
- begin:0.0 ,
- end: 200.0).animate(_controller);
- animation.addListener((){
- this.setState((){
- this.left = animation.value;
- });
- });
- if(isForward){
- _controller.forward();
- }else{
- _controller.reverse();
- }
- }
- }
上面的代码先创建一个 Animatable, 然后调用 animate(), 传入 Animation
Animation 开始动画.
每刷一帧都会执行一次
- this.setState((){
- this.left = animation.value;
- });
动画就实现了
动画实现原理
带着几个问题分析:
1.AnimationController 在动画中扮演一个什么角色?
2. 调用 forward 之后, 为什么动画就会开始?
3. 是谁驱动动画一直执行, 难道有 for 循环吗?
AnimationController 的角色
Overview
我的理解: AnimationController 将动画描述成一个可以量化的过程, 这个量化的值就是从 0.0 到 1.0 的过程 (采用默认的下限值和上限值).
从 0.0 到 1.0 就是 forward , 从 1.0 到 0.0 就是 reverse. 而这个值就是_value 这个变量.
如何实现从 0.0 到 1.0 的过程?
通过 Ticker 来接受 GPU 的垂直同步信号, 在每次接受到信号后更新这个值.
- // 收到垂直信号后的回调处理方法
- void _tick(Duration elapsed) {
- _lastElapsedDuration = elapsed;
- final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
- assert(elapsedInSeconds>= 0.0);
- _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
- if (_simulation.isDone(elapsedInSeconds)) {
- _status = (_direction == _AnimationDirection.forward) ?
- AnimationStatus.completed :
- AnimationStatus.dismissed;
- stop(canceled: false);
- }
- notifyListeners();
- _checkStatusChanged();
- }
这也就是为什么构建必须要传入 vsync 的原因, AnimationController 用他来创建一个 Ticker 的.
forward 之后做了啥?
先停止当前正在进行的动画, 然后调用 Ticker 的 start(), 开始接受垂直同步的回调, 然后再回调中根据流失的时间, 来计算当前的 value 值.
从而达到控制动画进程的目的.
- TickerFuture forward({ double from }) {
- ...
- // 如果 from 没有值, 就默认是动画到 1.0(upperBound 的默认值是 1.0)
- _direction = _AnimationDirection.forward;
- if (from != null)
- value = from;
- return _animateToInternal(upperBound);
- }
- // 构建一个 Simulation
- TickerFuture _animateToInternal(double target, { Duration duration, Curve curve = Curves.linear, AnimationBehavior animationBehavior }) {
- ...
- if (simulationDuration == null) {
- ...
- //
- final double range = upperBound - lowerBound;
- final double remainingFraction = range.isFinite ? (target - _value).abs() / range : 1.0;
- simulationDuration = this.duration * remainingFraction;
- } else if (target == value) {
- // Already at target, don't animate.
- simulationDuration = Duration.zero;
- }
- stop();
- ...
- return _startSimulation(_InterpolationSimulation(_value, target, simulationDuration, curve, scale));
- }
- // 开启_ticker value 开始从 0.0 到 1.0 变化.
- TickerFuture _startSimulation(Simulation simulation) {
- ...
- _value = simulation.x(0.0).clamp(lowerBound, upperBound);
- final TickerFuture result = _ticker.start();
- ...
- return result;
- }
由此可见 AnimationController 在动画中扮演着控制动画过程的角色, 通过维护着一个从 0.0 到 1.0 的变量来控制动画的进行.
Tween 的角色
Overview
配合 AnimationController, 将一个变量从 begin 变化到 end 的过程. 这个变化的过程是一个简单的一元一次函数.
t 的取值范围是 [0,1]. 若 t 是均匀变化的, 就是线性的从 begin 到 end.
- T lerp(double t) {
- return begin + (end - begin) * t;
- }
若这个变量是多个维度, 例如是一个 Rect, 有四个变量, 那就要从写这个方法了. 可以参见 RectTween.
Animation<T> animate(Animation parent) 这个方法做了啥?
这个就需要了解一个 Flutter 的私有类 _AnimatedEvaluation 它的父类是 Animation.
它起到了连接 Tween 和 AnimationController 的作用, 具体体现在它的 get value 的实现
- @override
- T get value => _evaluatable.evaluate(parent);
这个_evaluatable 就是构建_AnimatedEvaluation 传入的 Tween, 这句话会调用 Tween 的 evaluate(), 最终会调用上面的 lerp();lerp 参数 t 就是 AnimationController 维护的那个从 0.0 到 1.0 的变量.
当你每次在 setState() 后调用 animation.value, 就会走上面这个个方法, 得到的就是当前时间点的动画的值. 这样, 整个动画就被驱动起来了. 所以 Flutter 动画里面不存在 for 循环.
总结
* 如何用 flutter 实现一个贝塞尔曲线运动?
原理: 继承 Animatable 实现一个_BezierTween, 并重写他的 transform(); 这里直接将贝塞尔曲线方程式带进去即可.
当然重写 Tween 的 lerp 方法也是可行的.
bei.jpeg
i2lhs-vedko.gif
实现源码:
- class _Point{
- const _Point({this.x , this.y});
- final double x;
- final double y;
- }
- class _BezierTween extends Animatable<_Point>{
- _BezierTween({
- this.p0,
- this.p1,
- this.p2
- }):assert(p0 != null),
- assert(p1 != null),
- assert(p2 != null);
- final _Point p0; // 起始点
- final _Point p1; // 途径点
- final _Point p2; // 终点
- @override
- transform(double t) {
- double x = (1-t) * (1-t) * p0.x + 2 * t * (1-t) * p1.x + t * t * p2.x;
- double y = (1-t) * (1-t) * p0.y + 2 * t * (1-t) * p1.y + t * t * p2.y;
- return _Point(
- x:x ,
- y:y
- );
- }
- }
使用
- @override
- void initState() {
- super.initState();
- _controller = AnimationController(duration:Duration(seconds: 2) , vsync: this);
- _p0 = _Point( x:30,y:30);
- _p1 = _Point( x:30,y:200);
- _p2 = _Point( x:200,y:200);
- _animation = _BezierTween(p0: _p0 , p1: _p1 , p2: _p2).animate(_controller);
- _animation.addListener((){
- this.setState((){});
- });
- }
来源: http://www.jianshu.com/p/9b56412d9097