本文翻譯自
原文地址:Animation deep dive
原作者:Filip Hracek
去年我錄制了Flutter動(dòng)畫系列的一集視頻視頻鏈接 罐脊,我想我也應(yīng)該給那些喜歡看文字而不是視頻的人發(fā)布相同的內(nèi)容侍咱。
Animation videos from the Flutter YouTube channel 在該系列的其他劇集中,我的同事們討論了在Flutter中制作動(dòng)畫的所有實(shí)用方法芋忿,但是在我的劇集中不是這樣子的。在這里尚骄,您將學(xué)習(xí)如何以可想象的最不務(wù)實(shí)的方式實(shí)現(xiàn)動(dòng)畫(但是潭流,此過程中您仍會(huì)學(xué)到一些東西)难菌。
讓我們以輕松而又簡(jiǎn)單的事情開始吧:
什么是運(yùn)動(dòng)
你看途乃,運(yùn)動(dòng)是一種幻想,看這里(Filip揮舞著他的手的視頻):
這是一個(gè)假象扔傅,你實(shí)際上看到的是很多快速連續(xù)顯示的靜止圖像耍共,電影就是這樣工作的。單個(gè)圖片在電影中稱之為幀(frame)--因?yàn)閿?shù)碼顯示屏的工作原理類似--因此在此處也稱之為幀猎塞。在電影中通常一秒顯示24幀试读,現(xiàn)代數(shù)碼設(shè)備每秒顯示60到120幀。
因此荠耽,如果運(yùn)動(dòng)只是假象的話钩骇,那么這些AnimationFoo
和 FooTransition
到底在做什么,當(dāng)然由于每秒最多需要構(gòu)建120幀,UI不可能每次都會(huì)重新構(gòu)建倘屹。
或者银亲,可能?
實(shí)際上纽匙,F(xiàn)lutter中的動(dòng)畫只是在每一幀上重建部件樹的一部分的一種方法务蝠,沒有特殊情況,F(xiàn)lutter能足夠快的做到這一點(diǎn)烛缔。
讓我們看一下Flutter動(dòng)畫的構(gòu)建塊之一:AnimatedBuilder
馏段,它繼承自AnimatedWidget
,在AnimatedWidget
中會(huì)創(chuàng)建_AnimatedState
践瓷,在這個(gè)State
的initState()
初始化方法中院喜,我們監(jiān)聽了Animation
(或者在這稱之為Listenable
),當(dāng)它的值發(fā)生變化時(shí)候晕翠,我們調(diào)用了...setState()
喷舀。
這個(gè)源碼調(diào)用截圖,證明了我段落內(nèi)闡述的事實(shí)淋肾,動(dòng)畫的構(gòu)造函數(shù)真的每一幀都會(huì)調(diào)用setState()
方法元咙。
所以,在Flutter中動(dòng)畫只是一連串的快速的改變Widget的狀態(tài)巫员,每秒60到120次。
我能證明上述結(jié)論甲棍。這是一個(gè)從0到光速的‘動(dòng)畫化’的動(dòng)畫简识,盡管他只是每一幀改變了Text文字,從Flutter的角度來看感猛,他只是又一種動(dòng)畫!
讓我們使用Flutter的動(dòng)畫框架從基本原理出發(fā)構(gòu)建動(dòng)畫七扰。
通常我們將會(huì)使用TweenAnimationBuilder
或者其他類似的的Widget,但是在這篇文章中陪白,我們先忽略這些颈走,轉(zhuǎn)而使用ticker
、controller
咱士、setState
立由。
Ticker
先介紹一下Ticker
是什么,99%的場(chǎng)景序厉,你并不會(huì)直接使用到ticker锐膜,但是我認(rèn)為介紹一下仍會(huì)是有幫助的--即使只是揭開它的神秘面紗。
// ticker 是一個(gè)每幀都會(huì)執(zhí)行某個(gè)函數(shù)的對(duì)象
// A ticker is an object that calls a function for every frame.
var ticker = Ticker((elapsed) => print('hello'));
ticker.start();
在這個(gè)例子中弛房,我們每幀都會(huì)打印“hello”道盏,雖然并沒有什么用。
并且,我們忘記了調(diào)用ticker.dispose()
荷逞,因此我們的ticker將會(huì)一直運(yùn)行媒咳,直到我們殺掉APP。
這也是為什么Flutter給您提供SingleTickerProviderStateMixin
种远,也就是你在之前的一些視頻中看到的名字很恰如其分的mixin涩澡。
這種mixin解決了管理ticker的麻煩,只需將它混入到你widget的state中院促,你的State就隱秘的具備了TickerProvider
能力筏养。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
}
這意味著Flutter框架可以向你的state尋求ticker能力,最重要的是AnimationController
可以向state尋求ticker常拓。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
@override
Widget build(BuildContext context) {
return Container();
}
}
AnimationController
的初始化需要ticker
參數(shù)渐溶,如果你使用了SingleTickerProviderStateMixin
或者類似的TickerProviderStateMixin
,你就可以將this
傳參給AnimationController
弄抬,那么就初始化完成了茎辐。
AnimationController
AnimationController
通常用來播放
、暫停
掂恕、翻轉(zhuǎn)
拖陆、停止
動(dòng)畫,和純粹的"tick"不同懊亡,AnimationController
可以隨時(shí)告訴我們動(dòng)畫執(zhí)行到了哪個(gè)點(diǎn)依啰,例如我們的動(dòng)畫執(zhí)行到一半嗎,執(zhí)行到了99%店枣,還是已經(jīng)執(zhí)行完成了速警?
通常你可以使用AnimationController
,或者使用Curve
對(duì)它進(jìn)行轉(zhuǎn)換鸯两,或者將它放置到Tween
中闷旧,或者可以用在像FadeTransition
、TweenAnimationBuilder
這樣便捷使用的widget中钧唐。但是出于教學(xué)目的忙灼,以上這些我們都不使用,相反钝侠,我們直接調(diào)用setState
该园。
setState
初始化AnimationController
之后,我們通過它添加一個(gè)監(jiān)聽器帅韧,在監(jiān)聽器內(nèi)調(diào)用setState
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(_update);
}
void _update() {
setState(() {
// TODO
});
}
@override
Widget build(BuildContext context) {
return Container();
}
}
現(xiàn)在爬范,我們或許應(yīng)該設(shè)置state了,保持簡(jiǎn)單的integer類型弱匪,不要忘記在build方法中實(shí)際使用state青瀑,并且在我們的監(jiān)聽中根據(jù)controller的當(dāng)前值改變state璧亮。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
AnimationController _controller;
int i = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(_update);
}
void _update() {
setState(() {
i = (_controller.value * 299792458).round();
});
}
@override
Widget build(BuildContext context) {
return Text('$i m/s');
}
}
這段代碼根據(jù)動(dòng)畫的進(jìn)度,分配一個(gè)從0到光速的值斥难。
運(yùn)行動(dòng)畫
現(xiàn)在我們只需要告訴動(dòng)畫執(zhí)行應(yīng)該多長(zhǎng)時(shí)間枝嘶,然后開始動(dòng)畫
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
AnimationController _controller;
int i = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(_update);
}
void _update() {
setState(() {
i = (_controller.value * 299792458).round();
});
}
@override
Widget build(BuildContext context) {
return Text('$i m/s');
}
}
這個(gè)widget動(dòng)畫一旦添加到屏幕上就立刻開始執(zhí)行,”動(dòng)畫“在一秒內(nèi)從0過渡到光速哑诊。
銷毀controller
不要忘記銷毀AnimationController
群扶,否則你的APP將會(huì)有內(nèi)存泄漏。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
AnimationController _controller;
int i = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
_controller.addListener(_update);
_controller.forward();
}
void _update() {
setState(() {
i = (_controller.value * 299792458).round();
});
}
@override
Widget build(BuildContext context) {
return Text('$i m/s');
}
}
只使用內(nèi)置widget镀裤,可以嗎
如你所見竞阐,獨(dú)自完成所有操作并不理想,達(dá)到同樣的功能暑劝,使用TweenAnimationBuilder
用了少的代碼骆莹,并且不用在AnimationController
和調(diào)用setState
之間費(fèi)神。
class MyPragmaticWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
tween: IntTween(begin: 0, end: 299792458),
duration: const Duration(seconds: 1),
builder: (BuildContext context, int i, Widget child) {
return Text('$i m/s');
},
);
}
}
總結(jié)
我們了解了Ticker
是什么担猛,了解了怎么給AnimationController
添加監(jiān)聽幕垦,了解了從根本上講,動(dòng)畫就是快速連續(xù)的對(duì)widget進(jìn)行rebuild傅联,你可以在做在每一幀做你想做的任何事情先改。
系列文章: