发表日期:2018-12 文章编辑:小灯 浏览次数:2470
Flutter中,我们可以简单的把调用this.setState()理解为渲染一帧。那么只要我们不停的在调用这个方法的同时更新位置信息,就能实现平移动画了,其他动画也是如此。
而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 将动画描述成一个可以量化的过程,这个量化的值就是从0.0到1.0的过程(采用默认的下限值和上限值)。
从0.0到1.0就是forward , 从1.0到0.0就是reverse。而这个值就是_value这个变量。
通过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的。
先停止当前正在进行的动画,然后调用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的变量来控制动画的进行。
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方法也是可行的。
实现源码:
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((){}); }); }
日期:2018-10 浏览次数:7128
日期:2018-12 浏览次数:4197
日期:2018-07 浏览次数:4755
日期:2018-12 浏览次数:4043
日期:2018-09 浏览次数:5377
日期:2018-12 浏览次数:9790
日期:2018-11 浏览次数:4691
日期:2018-07 浏览次数:4461
日期:2018-05 浏览次数:4740
日期:2018-12 浏览次数:4195
日期:2018-10 浏览次数:5014
日期:2018-12 浏览次数:6090
日期:2018-11 浏览次数:4339
日期:2018-08 浏览次数:4458
日期:2018-11 浏览次数:12482
日期:2018-09 浏览次数:5447
日期:2018-12 浏览次数:4718
日期:2018-10 浏览次数:4056
日期:2018-11 浏览次数:4409
日期:2018-12 浏览次数:5935
日期:2018-06 浏览次数:3876
日期:2018-08 浏览次数:5318
日期:2018-10 浏览次数:4347
日期:2018-12 浏览次数:4353
日期:2018-07 浏览次数:4233
日期:2018-12 浏览次数:4384
日期:2018-06 浏览次数:4262
日期:2018-11 浏览次数:4243
日期:2018-12 浏览次数:4135
日期:2018-12 浏览次数:5150
Copyright ? 2013-2018 Tadeng NetWork Technology Co., LTD. All Rights Reserved.