本文經過拆解 Flutter 中動畫的實現方式以及原理來介紹動畫實現的整個過程。git
動畫在各個平臺的實現原理都基本相同,是在一段時間內一系列連續變化畫面的幀構成的。在 Flutter 中,動畫的過程又被量化成一段值區間,咱們能夠利用這些值設置控件的各個屬性來實現動畫,其內部由四個關鍵的部分來實現這一過程。github
tweens 可爲動畫提供起始值和結束值。默認狀況下,Flutter 中的動畫將任何給定時刻的值映射到介於 0.0 和 1.0 之間的 double 值。 咱們可使用如下 Tween 將其間值的範圍定義爲從 -200.0變爲 0.0:微信
tween = Tween<double>(begin: -200, end: 0);
複製代碼
咱們也能夠將值設置爲相應須要改變的對象值,好比將起始值設置爲紅色,結束值設置爲藍色,那麼 tweens 產生的動畫即是由紅漸漸的變成藍色。以下:markdown
colorTween = ColorTween(begin: Colors.red, end: Colors.blue);
複製代碼
Curves 用來調整動畫過程當中隨時間的變化率,默認狀況下,動畫以均勻的線性模型變化。讀者能夠經過自定義繼承 Curves 的類來定義動畫的變化率,好比設置爲加速、減速或者先加速後減速等曲線模型。Flutter 內部也提供了一系列實現相應變化率的 Curves 對象:async
幾個表明的動畫曲線模型圖以下:ide
Flutter 中的動畫以屏幕頻繁的重繪而實現,即每秒 60 幀。Ticker 能夠被應用在 Flutter 每一個對象中,當對象實現了 Ticker 的功能後,每次動畫幀改變便會通知該對象。這裏,開發者們不須要爲對象手動實現 Ticker,flutter 提供了 TickerProvider 類能夠幫助咱們快速實現該功能。例如,在有狀態控件下使用動畫時,一般須要在 State 對象下混入 TickerProviderStateMixin。函數
class _MyAnimationState extends State<MyAnimation> with TickerProviderStateMixin {
}
複製代碼
Flutter 中動畫的實現還有一個很是重要的類 AnimationController,即動畫控制器。很明顯,咱們用它來控制動畫,即動畫的啓動、暫停等。其接受兩個參數,第一個是 vsync,爲 Ticker 對象,其做用是當接受到來自 tweens 和 curves 的新值後通知對應對象,第二個 duration 參數爲動畫持續的時長。學習
// 混入 SingleTickerProviderStateMixin 使對象實現 Ticker 功能
class _AnimatedContainerState extends State<AnimatedContainer> with SingleTickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
// 建立 AnimationController 動畫
_controller = AnimationController(
// 傳入 Ticker 對象
vsync: this,
// 傳入 動畫持續時間
duration: new Duration(milliseconds: 1000),
);
startAnimation();
}
Future<void> startAnimation() async {
// 調用 AnimationController 的 forward 方法啓動動畫
await _controller.forward();
}
@override
Widget build(BuildContext context) {
return Container(
width: _controller.value;
child: //...
);
}
}
複製代碼
AnimationController 繼承自 Animation,具備一系列控制動畫的方法,如可用 forward()
方法來啓動動畫,可用 repeat()
方法使動畫重複執行,也能夠經過其 value 屬性獲得當前值。動畫
咱們能夠經過在 CurvedAnimation 傳入 AnimationController 和 Curve 對象建立一個 Animation 對象,以下:ui
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
final Animation<double> animation = CurvedAnimation(
parent: controller,
curve: Curves.ease,
);
複製代碼
也能夠經過調用 tween 的 animate 方法傳入 controller 對象建立 Animation 對象,以下:
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);
複製代碼
Animation 是一個抽象類,其中保存了動畫的過程值(value)和狀態,下面是四種狀態類型。
enum AnimationStatus {
/// 動畫處於中止狀態
dismissed,
/// 動畫從頭至尾執行
forward,
/// 動畫從尾到頭執行
reverse,
/// 動畫已執行完成
completed,
}
複製代碼
AnimationController 是它的一個實現類。其內部經過範型機制可實現對各種型對象的動畫,好比 Animation<double>
、Animation<Color>
、Animation<Size>
等。其另外一個實現類 CurvedAnimation,能夠用來與 Curves 結合實現各種曲線模型函數的動畫。
Animation 另外一個實現方法是調用 tween 對象的 animate 方法傳入 Animation 對象建立另外一個 Animation 對象,該方法可經過將使動畫值定義在 tween 區間內,以下:
AnimationController controller = AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);
複製代碼
Animation 對象能夠有設置兩種監聽器,分別是幀監聽器和狀態監聽器。使用 addListener()
添加幀監聽器,使用addStatusListener()
添加狀態監聽器。
只要動畫的值發生變化,就會觸發幀監聽器的回調。 一般,咱們在其內部調用 setState()
來重建組件來實現動畫效果,以下:
animation = new CurvedAnimation(
parent: animationController, curve: Curves.elasticOut)
animation.addListener(() => this.setState(() {}))
複製代碼
動畫開始,結束,前進或後退時會觸發 StatusListener 的回調,以下:
animation = new CurvedAnimation(
parent: animationController, curve: Curves.elasticOut)
animation.addStatusListener((AnimationStatus status) {});
複製代碼
咱們已經知道了 Flutter 控制動畫的四大要素,其中涉及的各個概念能夠幫助咱們設計出各類各樣的動畫效果,但難免也多了一些須要重複編寫的模版代碼,好比,在 Animation 的幀監聽器設置的監聽器回調裏,幾乎全部場景中咱們都只是調用 setState()
,再好比 State 對象每次都須要咱們手動地混入 SingleTickerProviderStateMixin
等等這類狀況。Flutter 爲了提升開發者的開發效率,提供了 AnimatedWidget 抽象類來封裝這部分模版代碼,其源碼很是簡單,以下:
abstract class AnimatedWidget extends StatefulWidget {
/// Creates a widget that rebuilds when the given listenable changes.
///
/// The [listenable] argument is required.
const AnimatedWidget({
Key key,
@required this.listenable
}) : assert(listenable != null),
super(key: key);
/// The [Listenable] to which this widget is listening.
///
/// Commonly an [Animation] or a [ChangeNotifier].
final Listenable listenable;
/// Override this method to build widgets that depend on the state of the
/// listenable (e.g., the current value of the animation).
@protected
Widget build(BuildContext context);
/// Subclasses typically do not override this method.
@override
_AnimatedState createState() => _AnimatedState();
}
class _AnimatedState extends State<AnimatedWidget> {
@override
void initState() {
super.initState();
widget.listenable.addListener(_handleChange);
}
@override
void didUpdateWidget(AnimatedWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.listenable != oldWidget.listenable) {
oldWidget.listenable.removeListener(_handleChange);
widget.listenable.addListener(_handleChange);
}
}
@override
void dispose() {
widget.listenable.removeListener(_handleChange);
super.dispose();
}
void _handleChange() {
setState(() {
// The listenable's state is our build state, and it changed already.
});
}
@override
Widget build(BuildContext context) => widget.build(context);
}
複製代碼
AnimatedWidget 做爲一個抽象類可供咱們實現一個咱們本身的具體類,其接受一個 Listenable 對象做爲參數,並須要重寫 build
方法。咱們上一節中屢次提到的 Animation 繼承自 Listenable。下面的這個這個組件就是我本身實現的動畫組件:??
class Sun extends AnimatedWidget {
Sun({Key key, Animation<Color> animation})
: super(key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<Color> animation = listenable;
var maxWidth = MediaQuery.of(context).size.width;
var margin = (maxWidth * .3) / 3;
return new AspectRatio(
aspectRatio: 1.0,
child: new Container(
margin: EdgeInsets.symmetric(horizontal: margin),
constraints: BoxConstraints(
maxWidth: maxWidth,
),
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: animation.value,
)));
}
}
複製代碼
咱們能夠經過傳入已經定義好的 Animation 對象來使用該組件:??
class AnimateWidgetState extends State<AnimateWidget> {
AnimationController _animationController;
ColorTween _colorTween;
...
@override
Widget build(BuildContext context) {
return Scaffold(
body:
Column(
children: <Widget>[
Sun(animation: _colorTween.animate(_animationController)),
],
),
);
}
}
複製代碼
這樣咱們就封裝了本身的一個動畫組件,另外,Flutter 內部爲咱們提供了多個已經封裝好的動畫組件,利用好這些組件能夠大大地提升咱們的開發效率:
利用動畫組件咱們已經能夠方便地封裝出一系列控件動畫了,可是這種實現方式均須要咱們本身提供 Animation 對象,而後經過提供的接口方法來啓動咱們的動畫,控件的屬性由 Animation 對象提供並在動畫過程當中改變而達到動畫的效果。爲了使動畫使用起來更加方便,Flutter 幫助了開發者從另外一個角度以更簡單的方式實現了動畫效果——隱式動畫組件(ImplicitlyAnimatedWidget)。
經過隱式動畫組件,咱們不須要手動實現插值器、曲線等對象,開發者甚至也不須要使用 AnimationController 來啓動動畫,它的實現方式更貼近對組件自己的操做,咱們能夠直接經過 setState()
的方法改變隱式動畫組件的屬性值,其內部自行爲咱們實現動畫過程的過渡效果,即隱藏了全部動畫實現的細節。Flutter 內部爲咱們提供了多個實用的隱式動畫組件,咱們本節分別介紹 AnimatedContainer 和 AnimatedOpacity 這兩個最經常使用的隱式動畫組件。
AnimatedContainer 是咱們最常使用到的隱式動畫組件之一,從名字能夠看出這個控件是以動畫形式而成的 Contianer 控件,它們都是頁面中渲染一個空的容器而且使用方法也很是類似。咱們能夠用下面的方式使用 Contianer 控件:
var height = 40.0
...
Container(
width: 60.0,
height: height,
color: Color(0xff14ff65),
),
複製代碼
上面的代碼中,咱們將 Container 的高度設置爲 height 變量,即爲 40.0,當咱們使用一個 Button 按鈕觸發改變 height 值的事件而且重繪界面時,Container 的高度會隨之改變:
onPressed: (){
setState(() {
height = 320.0;
});
},
複製代碼
但這種變化很明顯僅是屬性的改變並非一個平滑的過渡動畫,然而一樣的事件發生在 AnimatedContainer 控件上,便會有一個漸變的效果:
AnimatedContainer(
duration: Duration(seconds: 5),
width: 60.0,
height: height,
color: Color(0xff14ff65),
)
複製代碼
使用 AnimatedContainer 後,咱們再次觸發 height 變量改變後,頁面中的 AnimatedContainer 便會平滑的過渡到相應的高度,其 duration 屬性用於設置動畫過渡的時間,這裏,咱們設置爲 5 秒??。
咱們能夠用相同的方式爲 Container 的 Color、width 等各類屬性設置動畫,同時也能夠經過爲其設置 alignment 屬性來設置其內部子控件的位置。
在 Flutter 中,另外一種經常使用的動畫是控件透明度的過渡動畫,其對應的隱式動畫組件爲 AnimatedOpacity。它的用法與 Opacity 類似,內部持有的 opacity 屬性能夠設置爲 0.0~1.0 中的任意浮點數,分別對應徹底透明與徹底不透明,使用下面的方式,咱們即可以設置了一個半透明的 Opacity 控件:
Opacity(
opacity: 0.5,
child: Text("hello"),
)
複製代碼
咱們以相同的方法使用 AnimatedOpacity:
double opacity = 1.0;
...
AnimatedOpacity(
opacity: opacity,
duration: Duration(seconds: 1),
child: Text("hello"),
)
複製代碼
它也接受 duration 屬性來設置過渡時間,經過改變 opacity 變量的值能夠實現透明度變化的動畫效果:
setState(() {
opacity = 0.0;
});
複製代碼
咱們已經在以前的部分中介紹了 Flutter 中的三棵重要的樹及它們在組件渲染中的做用了。在元素樹中,每一個 Element 對象持有控件樹中 Widget 組件的狀態信息,這裏咱們將它稱爲 State 對象,Widget 刷新重建時,Element 會對比本身所對應 Widget 是否更新而作出相應屏幕渲染上的改變。
在各個隱式動畫組件中,其動畫信息便儲存在 Element 所持有的 State 對象中,Widget 每次刷新都會引發 Element 對其從新引用,當對應的 Widget 類型改變則其 Element 會連帶 State 對象天然而然的須要從新渲染,然而當 Widget 類型不變,則 Element 不須要重建,只須要改變 State 對象儲存的動畫信息便可。這樣一種連續更新屬性的過程便實現了更爲咱們所方便使用的隱式動畫。
實現自定義的隱式動畫組件,咱們須要使用到兩個類:ImplicitlyAnimatedWidget 和 AnimatedWidgetBaseState。
ImplicitlyAnimatedWidget 是全部隱式動畫組件的父類,繼承自 StatefulWidget,而且僅須要接受動畫曲線 curve 與動畫過渡時長 duration 兩個參數:
const ImplicitlyAnimatedWidget({
Key key,
this.curve = Curves.linear,
@required this.duration
})
複製代碼
在咱們自定義的隱式動畫組件能夠擴充他的參數類型知足咱們的需求。
AnimatedWidgetBaseState 即 ImplicitlyAnimatedWidget 這個有狀態組件所對應的 State 對象類,咱們自定義的隱式動畫組件所對應的 State 也必須繼承該類,其內部須要重寫 forEachTween
方法。
下面就是我本身定義的隱式動畫組件:
class MyAnimatedWidget extends ImplicitlyAnimatedWidget {
MyAnimatedWidget({
Key key,
this.param, //致使動畫的參數
Curve curve = Curves.linear,
@required Duration duration,
}) :super(key: key, curve: curve, duration: duration);
final double param;
@override
_MyAnimatedWidgetState createState() => _MyAnimatedWidgetState();
}
class _MyAnimatedWidgetState extends AnimatedWidgetBaseState<MyAnimatedWidget> {
Tween<double> _param; // State 內部保存的當前狀態信息,類型爲 Tween
@override
void forEachTween(TweenVisitor<dynamic> visitor) {
_param = visitor(_param, widget.param, (value) => Tween<double>(begin: value));
}
@override
Widget build(BuildContext context) {
//return a widget built on a parameter
}
}
複製代碼
上面代碼中,咱們在父類的基礎之上拓展了 param 參數,其是咱們在動畫過程當中須要關注的動畫屬性值。咱們還須要重點關注 _MyAnimatedWidgetState 類中 forEachTween
方法,它是隱式動畫實現的核心方法,其用於每次更新組件的動畫屬性,接受一個 TweenVisitor
對象 visitor 做爲參數。visitor 同時接受是那個參數,第一個爲一個插值器對象 Tween<T>
,其是應用在屬性中的插值器當前補間值,第二個參數爲一個 T
類型的值,即新的目標屬性值,第三個參數爲一個回調函數,用於配置給定的 value 值做爲新的插值器開始值。TweenVisitor 函數返回一個 Tween<T>
對象,咱們將其賦值給組件中當前的插值器對象做爲下次調用 forEachTween
方法時的當前值。
筆者水平有限,若是文中有錯誤的地方,請留言指正。
歡迎一塊兒交流學習,聯繫方式:
個人博客原文:meandni.com/2019/07/01/…
Github:github.com/MeandNi
微信:yangjk128