【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 的平滑過渡或反向過渡;其中切換狀態和時長是必要屬性;
案例嘗試
- 和尚嘗試一個基本的動畫過程,兩個方塊之間進行切換;
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)));

- reverseDuration 為切換反向動畫時長;
reverseDuration: Duration(milliseconds: 500),

- firstCurve / secondCurve 為兩個 Widget 切換時動畫效果;動畫效果有多種,和尚不在此贅述;
firstCurve: Curves.fastOutSlowIn, secondCurve: Curves.easeInExpo,

- alignment 為尺寸動畫切換時對齊位置,當兩個 Widget 大小不同時效果明顯,和尚嘗試了兩種位置進行對比;
alignment: Alignment.bottomRight, alignment: Alignment.center,

- sizeCurve 為尺寸切換動畫,當兩個 Widget 大小不同時效果明顯;
sizeCurve: Curves.easeInExpo, sizeCurve: Curves.fastOutSlowIn,

- 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 會執行展現動畫;
案例嘗試
- 和尚嘗試切換兩個基本的方塊,但剛開始切換動畫時長和反向切換動畫時長沒有效果,兩個 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)));

- 和尚在切換過程中嘗試不同的顯示隱藏動畫效果;
switchInCurve: Curves.easeInCubic, switchOutCurve: Curves.fastLinearToSlowEaseIn, switchInCurve: Curves.easeInExpo, switchOutCurve: Curves.fastOutSlowIn,

- 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)); }));

- child 中 old/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; }

- 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,和尚認為這兩類最靈活,使用場景最多;和尚對隱式動畫研究還不夠深入,如有錯誤請多多指導!
來源:阿策小和尚