本篇算是一篇学习笔记,主要对《Flutter实战》这本书上关于动画的章节做了一个简单的梳理,并且对上面的demo也做了统一整理,本篇博文的demo源码可以点击此处下载.运行页面如下:
这个demo程序列举了Tween,ColorTween,ReverseTween,AnimatedWidget等有关动画的的基础应用。动画效果读者可以下载源码运行。
本篇就以上述demo中Tween的动画为例来分析下Flutter的相关知识。该例子实现了一张图片的从小到大然后再从大到小的循环缩放。先来看看具体的代码:
如上图所示为了实现图片循环缩放的效果需要如下三个对象:
1、一个动画对象Animation
2、一个Tween对象,该对象用来控制图片变化的值,比如本例中让图片大小从0放大到300.
3、一个AnimationController对象,用来控制动画,比如控制动画的时间,开始和结束,比如本例中就是让缩放动画3秒中之内把图片的大小从0变成300。
通过controller.forward()方法启动动画后,通过动画的监听对象里调用 setState(() => {});就可以告知Flutter对页面重新build.然后将当前时间[0,300]区间的某个value设置给图片,这个值我们可以通过animation.value拿到,这个value值得是动画在此时此刻的某一值,代码如下所示:
Image.asset(
"images/beauty1.jpg",
width: animation.value,//图片的宽
height: animation.value//图片的高
)
因为是循环动画,所以需要知道动画的状态,我们可以通过两种方法拿到。第一种如下:
animation.addListener(() {
setState(() => {});
if(animation.isCompleted){
controller.reverse();//放大过后缩小
}else if(animation.isDismissed){
controller.forward();//继续下一轮
}
});
除了addListener之外,Flutter也提供了状态监听对象,也即是第二种:
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();//从大到小,然后从小到大循环
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
从上面的例子可以知道Animation拥有动画的当前值value和状态status两个值,我们可以将此value设置给一个Widget的某个属性,比如本例子的宽高。这个value是泛型的,可以是数字,也可以是其他值。比如颜色值(参考例子源码中的ColorTween的 简单使用)等等。其实也可以看出Animation和UI是无关的。这点倒是跟Android的属性动画有些类似,因为属性动画的对象也不仅仅局限于Android某个具体的View对象,可以是其他对象(关于属性动画的原理可参考博主此类博客)
之所以能实现上述动画的循环播放,是因为我们在监听了动画的状态,在动画结束的时候也就是status=completed执行reverse()方法。然后reverse方法执行结束后后status==dimissed状态。继续forward。这样就完成了图片从小到大再从大到小的播放过程。那么Flutter动画都的状态都有什么意思呢?
状态 | 含义 |
---|---|
dismissed | 动画在开始点停止或者说是恢复初始状态 |
forward | 动画正在正向执行 |
reverse | 动画正在方向执行 |
completed | 动态在终点停止 |
其中dismissed和completed都表明动画已经停止,但是一个是正向停止,一个是反向停止或者说是恢复了初始状态。
AnimatedWidget的简单使用
实现图片放大缩小核心是拿到Animation.value值赋值给Widget的宽和高,但是实现widget刷新的代码却是如下这一句:
//动画监听
animation.addListener(() {
//告知
setState(() => {});
});
但是如果每次使用动画的时候都要加上这段代码,未免显得太不优雅.所以Flutter提供了AnimatedWidget这个组件。代码改动如下:
从代码中可以看到,我们直接把前文初始化好的animation对象传给上图中的_AnimatedWidget对象即可。不需要额外的在添加addListener这段代码了。同样的可以实现图片放大缩小的效果。为什么呢?有兴趣的话可以看看AnimatedWidget的源码,原理很简单,这里就大致说一下:
如图所示,AnimatedWidget有如下特点:
1)持有了一个Listenable引用,而Animation继承了Listenable
2)在_AnimatedState的inState()方法中自动添加了Listener(上图_handleChange方法)
3)AnimatedWidget方法定义了一个抽象方法build,在动画执行的时候通过handleChange执行setState()方法引起重绘,自动回调了AnimatedWidget的build方法。
4)注意AnimatedWidget是一个Stateful的Widget
但是直接使用AnimatedWidget这有一个问题:就是引起了不必要的重绘。比如下面代码:
我们只需要重绘Image这个对象,但是因为AnimatedWidget的机制:随着动画的执行会不断调用build方法,那么上图中黄色矩形框里的对象也都需要重新创建,而这个创建是不必要的。如果放在更复杂的布局里面性能就更不高了。那么有什么可以改变这种不太优雅的方式吗?AnimatedBuilder粉末登场。
AnimatedBuilder的简单使用
先来看看AnimatedBuilder是神马玩意:
可以看出AnimatedBuilder直接继承了AnimatedWidget,且扩展了一个child对象,这个对象正是我们需要使用动画的对象。另外AnimatedBuilder重写了父类的build方法,见上图。
所以高效率的图片放大缩小的动画,如果使用AnimatedBuilder的话,就是如下所示:
到此为止,Flutter动画的简单使用讲解完毕。还有很多复杂的动画逻辑,因为博主还没来研究,就不再多说。如有不当之处,欢迎批评指正。
最后再耗费高级灵石开启一次本篇博文的源代码传送门。感兴趣的可以下载下来运行一下看看具体动画效果。