【Flutter 專題】71 圖解基本隱式動畫 Widget

  • 2019 年 12 月 30 日
  • 筆記

和尚前段時間自定義 ACEStepper 步進器時,在 ACEStep 中嘗試過 AnimatedCrossFade 用於在兩個 Widget 切換過度,簡單實用,今天和尚重點學習一下並嘗試相關隱式動畫 Widget

AnimatedCrossFade 淡入淡齣動畫

源碼分析

const AnimatedCrossFade({      Key key,      @required this.firstChild,                  // 首個展示 Widget      @required this.secondChild,                 // 第二展示 Widget      this.firstCurve = Curves.linear,            // 首個 Widget 展示動畫      this.secondCurve = Curves.linear,           // 第二 Widget 展示動畫      this.sizeCurve = Curves.linear,             // 切換時尺寸動畫      this.alignment = Alignment.topCenter,       // 對齊方式      @required this.crossFadeState,              // 切換狀態(是否切換)      @required this.duration,                    // 切換動畫時長      this.reverseDuration,                       // 切換反向動畫時長      this.layoutBuilder = defaultLayoutBuilder,  // Widget 布局構造器  })  

分析源碼可知,AnimatedCrossFade 可以在指定時間內從一個 Widget 到另一個 Widget 的平滑過渡或反向過渡;其中切換狀態和時長是必要屬性;

案例嘗試

  1. 和尚嘗試一個基本的動畫過程,兩個方塊之間進行切換;
return GestureDetector(      onTap: () { setState(() => isChanged = !isChanged); },      child: Container(          child: AnimatedCrossFade(              firstChild: Container(width: 100, height: 100, color: Colors.purpleAccent.withOpacity(0.4)),              secondChild: Container(width: 200, height: 200, color: Colors.blueGrey.withOpacity(0.4)),              duration: Duration(milliseconds: 1500),              crossFadeState: isChanged ? CrossFadeState.showSecond : CrossFadeState.showFirst)));  
  1. reverseDuration 為切換反向動畫時長;
reverseDuration: Duration(milliseconds: 500),  
  1. firstCurve / secondCurve 為兩個 Widget 切換時動畫效果;動畫效果有多種,和尚不在此贅述;
firstCurve: Curves.fastOutSlowIn,  secondCurve: Curves.easeInExpo,  
  1. alignment 為尺寸動畫切換時對齊位置,當兩個 Widget 大小不同時效果明顯,和尚嘗試了兩種位置進行對比;
alignment: Alignment.bottomRight,    alignment: Alignment.center,  
  1. sizeCurve 為尺寸切換動畫,當兩個 Widget 大小不同時效果明顯;
sizeCurve: Curves.easeInExpo,    sizeCurve: Curves.fastOutSlowIn,  
  1. layoutBuilder 為布局構造器,這個是和尚認為最值得研究的地方,構造器並不陌生,但在這裡的作用卻比較特殊,通過 Stack 將兩個 Widget 層級疊放,底部 Widget 默認尺寸位置以上層 Widget 為基準,默認 Position 邊距均為 0.0;我們可以自定義調整動畫起始位置;
// 默認  static Widget defaultLayoutBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey) {    return Stack(        overflow: Overflow.visible,        children: <Widget>[          Positioned(key: bottomChildKey, left: 0.0, top: 0.0, right: 0.0, child: bottomChild),          Positioned(key: topChildKey, child: topChild)        ]);  }  // 調整 Position 位置  layoutBuilder: (topChild, topChildKey, bottomChild, bottomChildKey) {    return Stack(children: <Widget>[      Positioned(key: bottomChildKey, left: 50.0, top: 50.0, right: 50.0, bottom: 50.0, child: bottomChild),      Positioned(key: topChildKey, child: topChild)    ]);  }  

[AnimatedCrossFade 源碼]()

AnimatedSwitcher 切換動畫

源碼分析

const AnimatedSwitcher({      Key key,      this.child,      @required this.duration,                // 切換動畫時長      this.reverseDuration,                   // 反向切換動畫時長      this.switchInCurve = Curves.linear,     // 切換顯示時動畫曲線      this.switchOutCurve = Curves.linear,    // 切換隱藏時動畫曲線      this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder,  // Widget 動畫構造器      this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder,          // Widget 布局構造器  })  

分析源碼可知,AnimatedSwitcher 更加靈活,可自由設置切換動畫之間顯示隱藏動畫效果;當 child Widget 內容或 Key 有變更時,old child 會執行隱藏動畫,new child 會執行展現動畫;

案例嘗試

  1. 和尚嘗試切換兩個基本的方塊,但剛開始切換動畫時長和反向切換動畫時長沒有效果,兩個 Widget 只有參數更新,動畫效果未執行;和尚嘗試加入 Key 區分之後正常;
return GestureDetector(      onTap: () => setState(() => isChanged = !isChanged),      child: AnimatedSwitcher(          duration: Duration(milliseconds: 500),          reverseDuration: Duration(milliseconds: 1500),          child: isChanged              ? Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4),  width: 100, height: 100)              : Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120)));  
  1. 和尚在切換過程中嘗試不同的顯示隱藏動畫效果;
switchInCurve: Curves.easeInCubic,  switchOutCurve: Curves.fastLinearToSlowEaseIn,    switchInCurve: Curves.easeInExpo,  switchOutCurve: Curves.fastOutSlowIn,  
  1. transitionBuilder 為動畫構造器,可以自定義動畫效果;和尚嘗試了兩種簡單的縮放動畫和平移動畫,暫未嘗試複雜動畫;且動畫屬性與顯示隱藏的 switchInCurve / switchOutCurve 動畫曲線共同展示效果;
// 縮放動畫效果  return GestureDetector(      onTap: () => setState(() => isChanged = !isChanged),      child: AnimatedSwitcher(          duration: Duration(milliseconds: 500),          reverseDuration: Duration(milliseconds: 1500),          switchInCurve: Curves.easeInCubic,          switchOutCurve: Curves.fastLinearToSlowEaseIn,          child: isChanged              ? Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4), width: 100, height: 100)              : Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120),          transitionBuilder: (Widget child, Animation<double> animation) {            return ScaleTransition(scale: animation, child: child);          }));    // 平移動畫效果  return GestureDetector(      onTap: () => setState(() => isChanged = !isChanged),      child: AnimatedSwitcher(          duration: Duration(milliseconds: 500),          reverseDuration: Duration(milliseconds: 1500),          switchInCurve: Curves.easeInExpo,          switchOutCurve: Curves.fastOutSlowIn,          child: isChanged              ? Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4), width: 100, height: 100)              : Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120),          transitionBuilder: (Widget child, Animation<double> animation) {            return SlideTransition(child: child, position: Tween<Offset>(begin: Offset(1, 0), end: Offset(0, 0)).animate(animation));          }));  
  1. childold/new Widget 一般是以 Stack 層級存儲,在動畫過程中兩個 Widget 均要展示,可以通過 layoutBuilder 布局構造器進行自定義;和尚嘗試調整對齊方式和只展示 current Widget 動畫效果;
// 調整層級對齊方式  layoutBuilder: (Widget currentChild, List<Widget> previousChildren) {    return Stack(children: <Widget>[      ...previousChildren,      if (currentChild != null) currentChild    ], alignment: Alignment.bottomRight);  }    // 只展示當前 Widget 動畫效果  layoutBuilder: (Widget currentChild, List<Widget> previousChildren) {      return currentChild;  }  
  1. AnimatedSwitcher 可以設置多個 Widget 平滑切換,相對於 AnimatedCrossFade 可擴展性更高;和尚嘗試三個 Widget 平移切換;
return GestureDetector(      onTap: () => setState(() {            ++index;            index = index % 3;          }),      child: AnimatedSwitcher(          duration: Duration(milliseconds: 500),          child: _animatedItemWid(index),          transitionBuilder: (Widget child, Animation<double> animation) {            return SlideTransition(child: child, position: Tween<Offset>(begin: Offset(1, 0), end: Offset(0, 0)).animate(animation));          }));    Widget _animatedItemWid(index) {    switch (index) {      case 0:        return Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4), width: 100, height: 100);        break;      case 1:        return Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120);        break;      case 2:        return Container(key: UniqueKey(), color: Colors.orange.withOpacity(0.4), width: 120, height: 140);        break;    }  }  

[AnimatedSwitcher 源碼]()


Flutter 還提供了很多靈活的隱式動畫 Widget,和尚認為這兩類最靈活,使用場景最多;和尚對隱式動畫研究還不夠深入,如有錯誤請多多指導!

來源:阿策小和尚