我什么時(shí)候應(yīng)該使用AnimatedBuilder或AnimatedWidget

我們知道當(dāng)你乘坐飛機(jī)飛行的時(shí)候你有很多選擇,這里我想表達(dá)的是在Flutter中選擇動(dòng)畫(huà),首先感謝你選擇使用AnimatedBuilderAnimatedWidget,等等茧泪,什么,還沒(méi)有使用聋袋?Flutter有很多不同的動(dòng)畫(huà)widget队伟,但是與商業(yè)航空公司不一樣的是,flutter中的每種類型的widget都有自己的適用場(chǎng)景幽勒。當(dāng)然嗜侮,你可以使用兩種不同的方式來(lái)完成一樣的動(dòng)畫(huà),但是使用適當(dāng)?shù)腶nimation widget來(lái)完成這項(xiàng)工作啥容,將會(huì)更加輕松锈颗。

這篇文章介紹了和其他動(dòng)畫(huà)widget對(duì)比,你為什么可能需要使用AnimatedBuilderAnimatedWidget咪惠,以及如何使用它們宜猜,假設(shè)你想向你的APP中添加動(dòng)畫(huà)。本文是該系列文章的一部分硝逢,逐步介紹了可能希望使用的各種類型的動(dòng)畫(huà)widget。你想要特定動(dòng)畫(huà)重復(fù)執(zhí)行幾次绅喉,或者想要暫停渠鸽、開(kāi)始以響應(yīng)某些事件,比如手指點(diǎn)擊柴罐,由于您的動(dòng)畫(huà)需要重復(fù)或停止徽缚、開(kāi)始,因此你將需要使用顯式動(dòng)畫(huà)革屠。

順便說(shuō)一下凿试,F(xiàn)lutter有兩大類型動(dòng)畫(huà):顯式和隱式排宰。對(duì)于顯式動(dòng)畫(huà),你需要一個(gè)animation controller那婉,對(duì)于隱式動(dòng)畫(huà)則不需要板甘。在上篇關(guān)于使用內(nèi)置顯示動(dòng)畫(huà)的文章,我們介紹了animation controller详炬,假如你想要了解更多關(guān)于此的內(nèi)容盐类,請(qǐng)先查看那篇文章。

到此呛谜,如果你確定使用顯式動(dòng)畫(huà)在跳,有很多顯式動(dòng)畫(huà)供您選擇,這些類通常命名為FooTransition隐岛,Foo是您想要設(shè)置的動(dòng)畫(huà)的屬性名稱猫妙,我建議先了解一下是否可以使用其中的一個(gè)widget來(lái)實(shí)現(xiàn)你的需求,然后再深入了解AnimatedBuilderAnimatedWidget聚凹。有很多效果很棒的widget供您選擇割坠,包括旋轉(zhuǎn)、位移元践、對(duì)齊韭脊、淡入淡出、文本樣式等单旁,另外你可以組合這些Widget沪羔,這樣就可以同時(shí)進(jìn)行旋轉(zhuǎn)和淡入淡出效果。但是象浑,如果這些內(nèi)置的Widget不能滿足你的需求蔫饰,那么就是時(shí)機(jī)使用AnimatedBuilderAnimatedWidget了。

whichanimation.png

這是用于了解使用哪種動(dòng)畫(huà)的流程圖愉豺,本文重點(diǎn)介紹底部的兩個(gè)藍(lán)色部分篓吁,AnimatedBuilder and AnimatedWidget。

特別的例子

為了使以上內(nèi)容更加具體蚪拦,讓我們來(lái)看一個(gè)具體的場(chǎng)景:我想編寫(xiě)一個(gè)帶有外星飛船的APP杖剪,這個(gè)飛船有一個(gè)光柱動(dòng)畫(huà)。


spaceship.gif

我繪制了一個(gè)漸變色的飛船光束驰贷,漸變色從正中心向外逐步黃色變?yōu)橥该魇⒑伲缓螅沂褂寐窂讲眉簦╬ath clipper)從該漸變創(chuàng)建了一個(gè)光束的形狀括袒。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      body: MyHomePage(),
    ));
  }
}

class MyHomePage extends StatelessWidget {
  final Image starsBackground = Image.asset(
    'assets/milky-way.jpg',
  );
  final Image ufo = Image.asset('assets/ufo.png');
  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: AlignmentDirectional.center,
      children: <Widget>[
        starsBackground,
        ClipPath(
          clipper: const BeamClipper(),
          child: Container(
            height: 1000,
            decoration: BoxDecoration(
              gradient: RadialGradient(
                radius: 1.5,
                colors: [
                  Colors.yellow,
                  Colors.transparent,
                ],
              ),
            ),
          ),
        ),
        ufo,
      ],
    );
  }
}

class BeamClipper extends CustomClipper<Path> {
  const BeamClipper();

  @override
  getClip(Size size) {
    return Path()
      ..lineTo(size.width / 2, size.height / 2)
      ..lineTo(size.width, size.height)
      ..lineTo(0, size.height)
      ..lineTo(size.width / 2, size.height / 2)
      ..close();
  }

  /// Return false always because we always clip the same area.
  @override
  bool shouldReclip(CustomClipper oldClipper) => false;
}

我想要?jiǎng)?chuàng)建一個(gè)光束降落的動(dòng)畫(huà)次兆,從該漸變的中心開(kāi)始,并使其重復(fù)锹锰。這意味著我需要?jiǎng)?chuàng)建顯式動(dòng)畫(huà)芥炭,不幸的是漓库,沒(méi)有內(nèi)置的顯式動(dòng)畫(huà)來(lái)為漏斗形漸變?cè)O(shè)置動(dòng)畫(huà),但是你知道我們有...AnimatedBuilderAnimatedWidget可以解決這個(gè)問(wèn)題园蝠!

AnimatedBuilder

為了制作光束動(dòng)畫(huà)渺蒿,我將把這段漸變代碼包裹在AnimatedBuilder widget中。當(dāng)AnimatedBuilder被調(diào)用的時(shí)候砰琢,包含在builder函數(shù)中漸變代碼也將被調(diào)用蘸嘶。

接下來(lái)我需要添加一個(gè)controller來(lái)驅(qū)動(dòng)動(dòng)畫(huà),controller將會(huì)提供AnimatedBuilder用來(lái)逐幀繪制所需要的值陪汽。如你在之前的文章里看到的训唱,我混入(mix in)了SingleTickerProviderStateMixin類,并在initState而不是build方法中初始化了controller實(shí)例對(duì)象挚冤,因?yàn)槲也幌攵啻蝿?chuàng)建controller--我想要它為動(dòng)畫(huà)的每一幀提供新的值况增!因?yàn)槲以?code>initState中創(chuàng)建了一個(gè)新的對(duì)象,所以我也添加了一個(gè)dispose方法训挡,用來(lái)告知Flutter澳骤,當(dāng)不再有父節(jié)點(diǎn)widget顯示在屏幕上的時(shí)候,可以銷毀controller澜薄。

然后为肮,我將controller傳遞給AnimatedBuilder,動(dòng)畫(huà)按照預(yù)期運(yùn)行啦肤京!

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  final Image starsBackground = Image.asset(
    'assets/milky-way.jpg',
  );
  final Image ufo = Image.asset('assets/ufo.png');
  AnimationController _animation;

  @override
  void initState() {
    super.initState();
    _animation = AnimationController(
      duration: const Duration(seconds: 5),
      vsync: this,
    )..repeat();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: AlignmentDirectional.center,
      children: <Widget>[
        starsBackground,
        AnimatedBuilder(
          animation: _animation,
          builder: (_, __) {
            return ClipPath(
              clipper: const BeamClipper(),
              child: Container(
                height: 1000,
                decoration: BoxDecoration(
                  gradient: RadialGradient(
                    radius: 1.5,
                    colors: [
                      Colors.yellow,
                      Colors.transparent,
                    ],
                    stops: [0, _animation.value],
                  ),
                ),
              ),
            );
          },
        ),
        ufo,
      ],
    );
  }

  @override
  void dispose() {
    _animation.dispose();
    super.dispose();
  }
}

class BeamClipper extends CustomClipper<Path> {
  const BeamClipper();

  @override
  getClip(Size size) {
    return Path()
      ..lineTo(size.width / 2, size.height / 2)
      ..lineTo(size.width, size.height)
      ..lineTo(0, size.height)
      ..lineTo(size.width / 2, size.height / 2)
      ..close();
  }

  /// Return false always because we always clip the same area.
  @override
  bool shouldReclip(CustomClipper oldClipper) => false;
}

你可能還記得在TweenAnimationBuilder一文中颊艳,我們提到使用child 參數(shù)來(lái)進(jìn)行性能優(yōu)化,我們?cè)?code>AnimatedBuilder中也可以這樣做忘分∑逭恚基本上,如果我們?cè)趧?dòng)畫(huà)中有從來(lái)沒(méi)改變過(guò)的對(duì)象妒峦,則可以提前構(gòu)建他們重斑,然后將它傳遞到AnimatedBuilder中。

在這個(gè)例子中肯骇,有一種更好的實(shí)現(xiàn)方式來(lái)做同樣的事情:給BeamClipper設(shè)置一個(gè)const構(gòu)造函數(shù)窥浪,并且僅僅設(shè)置了const。這樣只需要少量的代碼笛丙,這個(gè)對(duì)象將會(huì)在編譯期創(chuàng)建漾脂,使構(gòu)建更快速。當(dāng)然若债,有時(shí)你會(huì)編寫(xiě)一些沒(méi)有const構(gòu)造函數(shù)的代碼,這種情況對(duì)與使用可選child參數(shù)來(lái)說(shuō)是個(gè)很好的應(yīng)用場(chǎng)景拆融。

AnimatedWidget

到此蠢琳,我們創(chuàng)建了自己的動(dòng)畫(huà)啊终,但是包含AnimatedBuilder的構(gòu)建函數(shù)代碼量有點(diǎn)大,假如你的構(gòu)建方法開(kāi)始變的有點(diǎn)難以閱讀傲须,是時(shí)候重構(gòu)代碼了蓝牲。

你可以將AnimatedBuilder代碼提取到單獨(dú)的Widget中,但是這樣的話泰讽,你的構(gòu)建方法中將會(huì)嵌套另一個(gè)構(gòu)建方法例衍,看起來(lái)有點(diǎn)丑陋。取而代之的是已卸,你可以通過(guò)繼承自AnimatedWidget創(chuàng)建一個(gè)新的Widget來(lái)完成相同的動(dòng)畫(huà)佛玄。我將我的Widget命名為BeamTransition,與FooTransition顯示動(dòng)畫(huà)的命名習(xí)慣一致累澡。我將animation controller傳遞給BeamTransition梦抢,并重用了AnimatedBuilder構(gòu)造函數(shù)的主體代碼。

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  final Image starsBackground = Image.asset(
    'assets/milky-way.jpg',
  );
  final Image ufo = Image.asset('assets/ufo.png');
  AnimationController _animation;

  @override
  void initState() {
    super.initState();
    _animation = AnimationController(
      duration: const Duration(seconds: 5),
      vsync: this,
    )..repeat();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: AlignmentDirectional.center,
      children: <Widget>[
        starsBackground,
        BeamTransition(animation: _animation),
        ufo,
      ],
    );
  }

  @override
  void dispose() {
    _animation.dispose();
    super.dispose();
  }
}

class BeamTransition extends AnimatedWidget {
  BeamTransition({Key key, Animation<double> animation})
      : super(key: key, listenable: animation);
  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;
    return ClipPath(
      clipper: const BeamClipper(),
      child: Container(
        height: 1000,
        decoration: BoxDecoration(
          gradient: RadialGradient(
            radius: 1.5,
            colors: [
              Colors.yellow,
              Colors.transparent,
            ],
            stops: [0, animation.value],
          ),
        ),
      ),
    );
  }
}

就像AnimatedBuilder一樣愧哟,如果可能的話奥吩,我將添加child參數(shù)到我的widget中,以便進(jìn)行性能優(yōu)化蕊梧,因?yàn)樗梢蕴崆岸皇敲看芜M(jìn)行動(dòng)畫(huà)時(shí)進(jìn)行構(gòu)建霞赫。順帶提醒一下,在此例子中肥矢,將BeamClipper采用const構(gòu)造聲明是最好的方式端衰。

那么,我到底該用哪個(gè)吶橄抹?

我們剛剛看到了靴迫,當(dāng)你無(wú)法找到內(nèi)置顯式動(dòng)畫(huà)想要實(shí)現(xiàn)你想要的效果時(shí),AnimatedBuilderAnimatedWidget都可以用來(lái)實(shí)現(xiàn)相同效果的顯式動(dòng)畫(huà)楼誓,那么玉锌,你該用哪一個(gè)吶?這是一個(gè)個(gè)人偏好問(wèn)題疟羹,一般來(lái)說(shuō)我建議制作獨(dú)立的widget主守,每個(gè)widget負(fù)責(zé)單獨(dú)的功能--在這個(gè)例子中是動(dòng)畫(huà)。

絕大多數(shù)時(shí)榄融,我都贊成使用AnimatedWidget参淫,但是如果你創(chuàng)建animation controller的父節(jié)點(diǎn)Widget非常簡(jiǎn)單,那么為你的動(dòng)畫(huà)創(chuàng)建一個(gè)獨(dú)立的Widget可能會(huì)引入太多額外的代碼愧杯,這種情況涎才,AnimatedBuilder是你的首選。

這里有這篇文章的視頻版本,如果你更喜歡視頻耍铜,點(diǎn)擊觀看邑闺。

系列文章:

視頻 對(duì)應(yīng)文章(英文原文) 對(duì)應(yīng)文章(中文翻譯)
如何在 Flutter 中選擇合適的動(dòng)畫(huà) Widget?????在 Flutter中使用動(dòng)畫(huà)的正確選擇 How to Choose Which Flutter Animation Widget is Right for You? 【已翻譯】鏈接
隱式動(dòng)畫(huà)基礎(chǔ) Flutter animation basics with implicit animations 【已翻譯】鏈接
使用 TweenAnimationBuilder 創(chuàng)建獨(dú)特的隱式動(dòng)畫(huà) Custom Implicit Animations in Flutter…with TweenAnimationBuilder 【已翻譯】鏈接
使用內(nèi)置顯式動(dòng)畫(huà) Directional animations with built-in explicit animations 【已翻譯】鏈接
通過(guò) AnimatedBuilder 和 AnimatedWidget 創(chuàng)建一個(gè)自定義動(dòng)畫(huà) When should I useAnimatedBuilder or AnimatedWidget? 【已翻譯】鏈接
深入理解動(dòng)畫(huà) Animation deep dive 【已翻譯】鏈接
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市棕兼,隨后出現(xiàn)的幾起案子陡舅,更是在濱河造成了極大的恐慌,老刑警劉巖伴挚,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件靶衍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡茎芋,警方通過(guò)查閱死者的電腦和手機(jī)颅眶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)败徊,“玉大人帚呼,你說(shuō)我怎么就攤上這事≈灞模” “怎么了煤杀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)沪哺。 經(jīng)常有香客問(wèn)我沈自,道長(zhǎng),這世上最難降的妖魔是什么辜妓? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任枯途,我火速辦了婚禮,結(jié)果婚禮上籍滴,老公的妹妹穿的比我還像新娘酪夷。我一直安慰自己,他們只是感情好孽惰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布晚岭。 她就那樣靜靜地躺著,像睡著了一般勋功。 火紅的嫁衣襯著肌膚如雪坦报。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天狂鞋,我揣著相機(jī)與錄音片择,去河邊找鬼。 笑死骚揍,一個(gè)胖子當(dāng)著我的面吹牛字管,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼嘲叔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脐供!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起借跪,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎酌壕,沒(méi)想到半個(gè)月后掏愁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卵牍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年果港,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糊昙。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辛掠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出释牺,到底是詐尸還是另有隱情萝衩,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布没咙,位于F島的核電站猩谊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏祭刚。R本人自食惡果不足惜牌捷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涡驮。 院中可真熱鬧暗甥,春花似錦、人聲如沸捉捅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锯梁。三九已至即碗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陌凳,已是汗流浹背剥懒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留合敦,地道東北人初橘。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親保檐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耕蝉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容