Flutter Component動(dòng)畫(huà)的顯和隱最佳實(shí)踐
動(dòng)畫(huà)選擇決策樹(shù)
Flutter中包含大量的動(dòng)畫(huà)組件和自定義動(dòng)畫(huà)方式,所以,在合適的場(chǎng)景下選擇合適的動(dòng)畫(huà)實(shí)現(xiàn)方式就成了決定代碼質(zhì)量好壞的一個(gè)重要因素。
Flutter中的動(dòng)畫(huà)從廣義上來(lái)講可以分為兩類(lèi),一類(lèi)是基于繪制的動(dòng)畫(huà)(Drawing-based animations),另一類(lèi)是基于代碼的動(dòng)畫(huà)(Code-based animations)。
下面這個(gè)決策樹(shù),是Flutter動(dòng)畫(huà)選擇的總綱,這里梳理了不同的動(dòng)畫(huà)的作用場(chǎng)景和功能,我們來(lái)看下它具體的實(shí)現(xiàn)。
首先,我們需要區(qū)分是使用CustomPainter,或者是使用Lottie、Flare這種第三方庫(kù),這一類(lèi)的動(dòng)畫(huà)很容易區(qū)分——如果你第一感覺(jué),這個(gè)動(dòng)畫(huà)我做不了,那它大概率就是了。
接下來(lái),就是區(qū)分是使用「顯示動(dòng)畫(huà)」還是「隱式動(dòng)畫(huà)」。
簡(jiǎn)單的說(shuō),它們的區(qū)別如下:
- 隱式動(dòng)畫(huà):不用循環(huán)播放、不用隨時(shí)中斷、不用多個(gè)動(dòng)畫(huà)協(xié)同,它實(shí)現(xiàn)的是一種狀態(tài)到另一種狀態(tài)的改變
- 顯示動(dòng)畫(huà):需要自己控制動(dòng)畫(huà)過(guò)程
最后,就是看現(xiàn)有組件是否滿(mǎn)足需求,如果不行,那么就需要自定義相應(yīng)的動(dòng)畫(huà)。
這就是整個(gè)動(dòng)畫(huà)決策樹(shù)的執(zhí)行過(guò)程。它們的開(kāi)發(fā)難度,如下所示。
下面我們就具體來(lái)分析下不同的動(dòng)畫(huà)實(shí)現(xiàn)。本文首先介紹顯示動(dòng)畫(huà)和隱式動(dòng)畫(huà)。
Implicit Animations——隱式動(dòng)畫(huà)
在Flutter中,很多常用組件都有其自帶的隱式動(dòng)畫(huà)版本,例如下圖所示的這些組件。
這些組件在Flutter中被稱(chēng)之為隱式動(dòng)畫(huà)Widget,下面以AnimatedContainer為例,來(lái)看下Implicit Animations的使用。
隱式動(dòng)畫(huà)有一個(gè)特點(diǎn),那就是它們都是以「Animated」開(kāi)頭。
基本使用
AnimatedContainer的使用非常簡(jiǎn)單,甚至和普通的Container沒(méi)有太大的區(qū)別,代碼如下所示。
AnimatedContainer( margin: EdgeInsets.only(top: 20), width: size, height: size, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(radius), ), curve: Curves.easeIn, duration: Duration(milliseconds: 300), ),
當(dāng)通過(guò)setState函數(shù)改變AnimatedContainer中的屬性時(shí),AnimatedContainer會(huì)經(jīng)過(guò)一段動(dòng)畫(huà)效果,然后再完成相應(yīng)的改變。在隱式動(dòng)畫(huà)中,你依然可以定義Curve和Duration等參數(shù),但是你無(wú)法控制動(dòng)畫(huà),即動(dòng)畫(huà)的執(zhí)行和結(jié)束,是由屬性改變來(lái)驅(qū)動(dòng)的。
使用場(chǎng)景
Implicit Animations可以非常方便的使Widget具有動(dòng)畫(huà)效果而不需要寫(xiě)很多額外的動(dòng)畫(huà)代碼,結(jié)合FutureBuilder或者StreamBuilder,甚至不用寫(xiě)setState,下面這個(gè)例子就演示了如何將Implicit Animations和FutureBuilder結(jié)合起來(lái)使用,代碼如下所示。
FutureBuilder( future: future, builder: (context, snapshot) { var width = .0; switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.waiting: case ConnectionState.active: width = .0; break; case ConnectionState.done: width = 100.0; break; } return AnimatedContainer( width: width, duration: Duration(seconds: 1), curve: Curves.easeIn, child: Image.asset('images/logo.png'), ); }, ),
通過(guò)FutureBuilder的各種狀態(tài)回調(diào),就可以設(shè)置不同的Widget,并在FutureBuilder完成并顯示正常的Widget時(shí),產(chǎn)生一個(gè)動(dòng)畫(huà)效果,而不是非常生硬的出現(xiàn)。
TweenAnimationBuilder
TweenAnimationBuilder是自定義隱式動(dòng)畫(huà)的方式,借助它,你可以給一個(gè)指定的Widget作用一個(gè)動(dòng)畫(huà)效果,一個(gè)簡(jiǎn)單的示例代碼如下所示。
TweenAnimationBuilder( tween: Tween<double>(begin: 0, end: 48), onEnd: (){} duration: Duration(seconds: 1), builder: (BuildContext context, double size, Widget child) { return IconButton( iconSize: size, color: Colors.blue, icon: child, ); }, child: Icon(Icons.aspect_ratio), )
借助TweenAnimationBuilder,就可以將一個(gè)指定的Tween作用于builder中的Widget,builder中的第二個(gè)參數(shù),就是Tween所指定的參數(shù)的類(lèi)型,通過(guò)TweenAnimationBuilder,就可以在Widget參數(shù)變化的時(shí)候產(chǎn)生動(dòng)畫(huà)效果。
TweenAnimationBuilder的builder中如果有不變的Child Widget,可以放在TweenAnimationBuilder的child屬性中,因?yàn)閎uilder在產(chǎn)生動(dòng)畫(huà)時(shí)會(huì)重建,所有不變的Widget,都可以放在TweenAnimationBuilder的child中,再通過(guò)builder的第三個(gè)參數(shù)來(lái)傳遞這個(gè)Widget,以避免重建。
通常我們?cè)陂_(kāi)發(fā)中,會(huì)借助Transform來(lái)完成動(dòng)畫(huà)效果,在builder中,根據(jù)Tween返回的數(shù)值,使用不同的Transform來(lái)修改動(dòng)畫(huà)狀態(tài)。
TweenAnimationBuilder中的begin,只在第一次使用,后面更新時(shí),只看end的值,例如10-30,修改end為50,實(shí)際變化是30-50。如果不傳begin,那么默認(rèn)和end相等。
Explicit Animations——顯示動(dòng)畫(huà)
與隱式動(dòng)畫(huà)不同,顯示動(dòng)畫(huà)給了開(kāi)發(fā)者對(duì)動(dòng)畫(huà)過(guò)程的完全掌控,開(kāi)發(fā)者可以根據(jù)自己的需要來(lái)控制動(dòng)畫(huà),F(xiàn)lutter中內(nèi)置了很多顯示動(dòng)畫(huà),如下所示。
顯示動(dòng)畫(huà)也有一個(gè)很明顯的特點(diǎn),那就是它們都以「Transition」結(jié)尾。
基本使用
以RotationTransition為例,下面來(lái)演示下如何使用Flutter中的顯示動(dòng)畫(huà)。
顯示動(dòng)畫(huà)是通過(guò)AnimationController來(lái)進(jìn)行驅(qū)動(dòng)的,所以,使用顯示動(dòng)畫(huà)的第一步,就是需要?jiǎng)?chuàng)建AnimationController。有了AnimationController之后,就可以通過(guò)控制AnimationController的狀態(tài)來(lái)控制動(dòng)畫(huà)的驅(qū)動(dòng)過(guò)程,整個(gè)代碼如下所示。
AnimationController controller; @override void initState() { super.initState(); controller = AnimationController(vsync: this, duration: Duration(seconds: 2))..repeat(); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Center( child: GestureDetector( onTap: () { if (controller.isAnimating) { controller.stop(); } else { controller.repeat(); } }, child: RotationTransition( turns: controller, child: FlutterLogo( size: 100, ), ), ), ); }
與隱式動(dòng)畫(huà)相比,顯式動(dòng)畫(huà)通過(guò)AnimationController來(lái)獲取動(dòng)畫(huà)的行進(jìn)狀態(tài)和參數(shù),從而讓調(diào)用者能夠控制動(dòng)畫(huà)的行進(jìn)過(guò)程。
顯式動(dòng)畫(huà)可以實(shí)現(xiàn)隱式動(dòng)畫(huà)的所有功能,但是比隱式動(dòng)畫(huà)多了管理動(dòng)畫(huà)生命周期的工作
當(dāng)Flutter內(nèi)置顯示動(dòng)畫(huà)不能滿(mǎn)足開(kāi)發(fā)者的需求時(shí),F(xiàn)lutter提供了AnimatedBuilder和AnimatedWidget來(lái)讓開(kāi)發(fā)者對(duì)顯示動(dòng)畫(huà)進(jìn)行自定義。
AnimatedWidget
前面提到的都是Flutter中使用動(dòng)畫(huà)的最基本方式,但實(shí)際上,F(xiàn)lutter提供了很多關(guān)于動(dòng)畫(huà)的封裝組件,可以讓開(kāi)發(fā)者更加方便的使用動(dòng)畫(huà),這就是AnimatedWidget。AnimatedWidget也有很多實(shí)現(xiàn)類(lèi),如圖所示。
AnimatedWidget是實(shí)現(xiàn)自定義顯示動(dòng)畫(huà)的另一種方式,它可以將一些動(dòng)畫(huà)的邏輯以Widget的形式封裝起來(lái),從而讓build函數(shù)中的代碼邏輯更加清晰,下面是AnimatedWidget的示例代碼。
@override Widget build(BuildContext context) { return Stack( children: <Widget>[ AnimWidget(animation: controller), Center(child: FlutterLogo(size: 100)), ], ); } class AnimWidget extends AnimatedWidget { const AnimWidget({ Key? key, required Animation<double> animation, }) : super(key: key, listenable: animation); @override Widget build(BuildContext context) { Animation<double> animation = listenable as Animation<double>; return Container( decoration: BoxDecoration( gradient: RadialGradient( colors: const [Colors.red, Colors.transparent], stops: [0, animation.value], ), ), ); } }
那么這種方式和之前直接使用AnimationController和Tween有什么區(qū)別呢?細(xì)心的讀者可能已經(jīng)發(fā)現(xiàn)了,AnimatedWidget不需要自己去監(jiān)聽(tīng)動(dòng)畫(huà)的回調(diào),也不需要通過(guò)setState來(lái)刷新動(dòng)畫(huà),這些操作,AnimatedWidget已經(jīng)封裝好了,這就是AnimatedWidget的作用。
AnimatedBuilder
AnimatedBuilder是一個(gè)特殊的AnimatedWidget,它可以直接指定一個(gè)動(dòng)畫(huà)作用于Widget上,而不需要重新創(chuàng)建一個(gè)自定義的AnimatedWidget,它可以幫助開(kāi)發(fā)者處理動(dòng)畫(huà)的監(jiān)聽(tīng),當(dāng)一個(gè)Widget Tree中有一些需要?jiǎng)赢?huà)的Widget,也有一些不需要?jiǎng)赢?huà)的Widget時(shí),用AnimatedBuilder可以很方便的避免非動(dòng)畫(huà)Widget的重繪,所以說(shuō),AnimatedBuilder可以更加方便的給一個(gè)Widget增加動(dòng)畫(huà)效果。
AnimatedBuilder與其它的顯示動(dòng)畫(huà)一樣,也是通過(guò)AnimationController驅(qū)動(dòng)的,借助AnimatedBuilder,開(kāi)發(fā)者可以根據(jù)需要,自己創(chuàng)建Animation并控制它,下面的代碼演示了如何通過(guò)控制RadialGradient的stop屬性來(lái)控制RadialGradient的顯示大小,從而形成動(dòng)畫(huà)效果,代碼如下所示。
@override Widget build(BuildContext context) { return AnimatedBuilder( animation: controller, builder: (context, widget) { return Stack( children: <Widget>[ Container( decoration: BoxDecoration( gradient: RadialGradient( colors: [Colors.red, Colors.transparent], stops: [0, controller.value], ), ), ), Center(child: FlutterLogo(size: 100)) ], ); }, ); }
上面的代碼演示了如何使用AnimatedBuilder,實(shí)際上非常簡(jiǎn)單,與使用內(nèi)置的顯示動(dòng)畫(huà)的過(guò)程基本一致。
在使用AnimatedBuilder的過(guò)程中,需要盡可能多的將需要?jiǎng)赢?huà)的部分和不需要?jiǎng)赢?huà)的部分區(qū)分開(kāi)來(lái),這樣可以避免多余的重繪,從而提高動(dòng)畫(huà)性能,例如上面的代碼,可以將FlutterLogo和Stack放置在最外層,這樣只需要讓RadialGradient產(chǎn)生動(dòng)畫(huà)就可以了,代碼如下所示。
@override Widget build(BuildContext context) { return Stack( children: <Widget>[ AnimatedBuilder( animation: controller, builder: (context, widget) { return Container( decoration: BoxDecoration( gradient: RadialGradient( colors: [Colors.red, Colors.transparent], stops: [0, controller.value], ), ), ); }, ), Center(child: FlutterLogo(size: 100)) ], ); }
AnimatedBuilder接收了一個(gè)animation,在child中,可以直接使用這個(gè)animation的值,其它都和普通的AnimatedWidget類(lèi)似。
實(shí)際上,AnimatedBuilder就是AnimatedWidget的子類(lèi),所以在本質(zhì)上,這兩種實(shí)現(xiàn)自定義顯示動(dòng)畫(huà)的方式想相同的,開(kāi)發(fā)者可以根據(jù)自己的喜好來(lái)選擇相應(yīng)的方式來(lái)創(chuàng)建自己的顯示動(dòng)畫(huà)。
AnimateWidget負(fù)責(zé)組件的抽離,可以看出組件中雜糅了動(dòng)畫(huà)邏輯。而AnimatedBuilder恰好相反,它不在意組件是什么,只是將動(dòng)畫(huà)抽離達(dá)到復(fù)用簡(jiǎn)單。
Flutter中的顯示動(dòng)畫(huà)和隱式動(dòng)畫(huà),幾乎可以解決大部分我們平時(shí)在開(kāi)發(fā)中遇到的動(dòng)畫(huà)場(chǎng)景,借助動(dòng)畫(huà)選擇決策樹(shù),我們可以對(duì)動(dòng)畫(huà)的選擇了如指掌,剩下的工作,就是對(duì)動(dòng)畫(huà)進(jìn)行拆解,分而治之。
以上就是Flutter Component動(dòng)畫(huà)的顯和隱最佳實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于Flutter Component動(dòng)畫(huà)顯隱的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Flutter數(shù)字切換動(dòng)畫(huà)實(shí)現(xiàn)示例詳解
- 詳解如何使用Flutter動(dòng)畫(huà)魔法使UI元素活起來(lái)
- Flutter?文字中劃線(xiàn)動(dòng)畫(huà)StrikeThroughTextAnimation
- Flutter封裝組動(dòng)畫(huà)混合動(dòng)畫(huà)AnimatedGroup示例詳解
- Flutter在項(xiàng)目中使用動(dòng)畫(huà)不使用包實(shí)現(xiàn)詳解
- Flutter繪制3.4邊形及多邊形漸變動(dòng)畫(huà)實(shí)現(xiàn)示例
- Flutter Flar動(dòng)畫(huà)使用實(shí)戰(zhàn)示例
相關(guān)文章
Android開(kāi)發(fā)之自定義刮刮卡實(shí)現(xiàn)代碼
本篇文章主要介紹了Android開(kāi)發(fā)之自定義刮刮卡實(shí)現(xiàn)代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07Android開(kāi)發(fā)手冊(cè)自定義Switch開(kāi)關(guān)按鈕控件
這篇文章主要為大家介紹了Android開(kāi)發(fā)手冊(cè)自定義Switch開(kāi)關(guān)按鈕控件的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android自定義Animation實(shí)現(xiàn)View搖擺效果
這篇文章主要為大家詳細(xì)介紹了Android自定義Animation實(shí)現(xiàn)View搖擺效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01基于Android實(shí)現(xiàn)一個(gè)常用的布局吸頂效果
這篇文章給大家介紹一個(gè)布局吸頂效果,一般出現(xiàn)在內(nèi)容較長(zhǎng)頁(yè)面還嵌套著分類(lèi)頁(yè)面的情況,比如電商的詳情頁(yè)嵌套分類(lèi),在頁(yè)面滑動(dòng)到tab的時(shí)候我們希望tab還能保留在頁(yè)面頂部而不被頂上去,文中有詳細(xì)的代碼示例,需要的朋友可以參考下2023-09-09Android ScrollView無(wú)法填充滿(mǎn)屏幕的解決辦法
這篇文章主要介紹了Android ScrollView無(wú)法填充滿(mǎn)屏幕的解決辦法的相關(guān)資料,這里提供實(shí)例和解決辦法,需要的朋友可以參考下2017-07-07Flutter倒計(jì)時(shí)/計(jì)時(shí)器的實(shí)現(xiàn)代碼
這篇文章主要介紹了Flutter倒計(jì)時(shí)/計(jì)時(shí)器的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03android使用service和activity獲取屏幕尺寸的方法
這篇文章主要介紹了android使用service和activity獲取屏幕尺寸的方法,實(shí)例分析了基于service和activity兩種方法獲取屏幕尺寸的相關(guān)技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-08-08Android開(kāi)發(fā)筆記之:Dialog的使用詳解
本篇文章是對(duì)Android中Dialog的使用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05