主本文主要說明動畫的基本原理和簡單的動畫的實例,若有不當之處敬請指正。git
閱讀本文大約須要 6 分鐘github
給UI界面設計合理的動畫,可讓用戶以爲更加流暢、直觀,提升用戶的交互使用感覺,改善用戶體驗。markdown
在 Flutter 中動畫分爲兩類:基於補間 (Tween) 的和基於物理 (Physics) 的;less
補間動畫是介於二者之間的簡稱,在補間動畫中定義起點和終點、時間點以及定義時間變化和速度的曲線,而後由系統計算如何從開始點到結束點。ide
物理動畫是運動被模擬爲與真實世界的行爲類似,好比拋一件物體,它落在什麼地方取決於這個物體的重量,拋出去的速度以及這個物體與地面的高度,相似數學中的拋物線運動軌跡。函數
在 Flutter 中想要實現動畫效果離不開幾個核心的角色:Animation(動畫對象),AnimationController(動畫控制器),Tweens(插值器),Curves(動畫曲線);學習
在 Flutter 中動畫自己和UI渲染沒有任何關係,Animation是一個抽象類,它擁有其當前值和狀態(完成或中止),Flutter 中的動畫系統就是基於 Animation
對象的。其中比較經常使用的就是Animation類是Animation。它能夠經過其 value 屬性來獲取當前動畫的值。動畫
Animation 除了能夠生成 double 的值以外還能夠生成如:顏色--Animation<Color>
或者大小--Animation<Size>
。ui
Animation 對象能夠擁有 Listeners 和 StatusListeners 監聽器,能夠用 addListener()
和addStatusListener()
來添加。只要動畫的的值發生變化,就會調用監聽器。正常咱們在 Listeners 中調用setState() 來觸發UI重建;動畫開始、結束、向前移動或向後移動時會調用StatusListener。this
AnimationController 是一個特殊的 Animation
對象,在屏幕刷新的每一幀,就會生成一個新的值。默認狀況下,AnimationController 會在特定的時間內線性的生成0.0到1.0的數字。AnimationController派生於 Animation<double>
,所以能夠在須要Animation對象的任何地方使用。不但如此,AnimationController還具備控制動畫的其餘方法,好比 forward()
方法能夠啓動動畫。
AnimationController({
double value,
this.duration,
this.reverseDuration,
this.debugLabel,
this.lowerBound = 0.0,
this.upperBound = 1.0,
this.animationBehavior = AnimationBehavior.normal,
@required TickerProvider vsync,
})
複製代碼
建立 AnimationController 必須需傳入 vsync
,傳入 vsunc
是爲了防止動畫的UI不在當前屏幕時,不須要繪製,從而防止消耗沒必要要的資源。經過將 SingleTickerProviderStateMixin
混入到類定義中,就能夠將 statefu l對象做爲 vsync
的值。
除了 vsync
還能夠傳入正向動畫執行的時間 duration
以及反向動畫執行時間 reverseDuration
等。
經常使用函數:
序號 | 方法 | 介紹 |
---|---|---|
1 | forward() | 開始播放動畫 |
2 | stop() | 中止動畫 |
3 | reset() | 重製動畫 |
4 | reverse() | 反向播放動畫,必須處於正向動畫播放完成的狀態以後纔有用 |
5 | dispose() | 釋放動畫佔用資源 |
6 | repeat() | 循環播放動畫 |
注意:動畫完成時釋放控制器(調用 dispose()
方法)以防止內存泄漏
@override
void dispose() {
animationController.dispose();
super.dispose();
}
複製代碼
默認狀況下,AnimationController對象的範圍從0.0到1.0。若是您須要不一樣的範圍或不一樣的數據類型,則可使用Tween來配置動畫以生成不一樣的範圍或數據類型的值。好比,能夠生產從0-100的數字:
final Tween doubleTween = new Tween<double>(begin: 0.0, end: 100.0);
複製代碼
Tween是一個無狀態(stateless)對象,繼承自Animatable<T>
,而不是繼承自 Animation<T>
。Tween 須要兩個值,分別是:begin 和 end。Tween的惟一職責就是定義從輸入範圍到輸出範圍的映射。
Animatable與Animation類似,不是必須輸出double值,也能夠是顏色,好比,從白色到黑色:
final Tween colorTween = new ColorTween(begin: Colors.withe, end: Colors.black);
複製代碼
Tween 能夠經過 animate() 方法傳入 controller 對象建立 Animation 對象。以下
AnimationController _animationController = AnimationController(animationBehavior:AnimationBehavior.normal,vsync: this);
Tween<double> _tween = Tween<double>(begin: 0.0, end: 100.0)..animate(_animationController);
複製代碼
Curves 用來調整動畫過程當中隨時間的變化率,默認狀況下,動畫以均勻的線性模型變化。Flutter 內部也提供了一系列實現相應變化率的 Curves 對象:linea ------ 線性,decelerate ------ 減速等。
固然,也能夠自定義繼承 Curves 的類來定義動畫的變化率,如:
class ShakeCurve extends Curve {
@override
double transform(double t) {
return math.sin(t * math.PI * 2);
}
}
複製代碼
目前爲止動畫只是實現了自身數值的變化,並無讓 Widget 動起來,這裏咱們須要對動畫數值進行監聽,而後使用 setstatus 來更新 Widget 的屬性,從而使 Widget 動起來。
添加數值監聽:
Animation animation = CurvedAnimation(parent: _animationController, curve: Curves.linear);
animation.addListener((){
setState(() {
});
});
複製代碼
除此以外咱們還能夠監聽動畫的狀態變動,當動畫結束時咱們反轉動畫,當動畫的反轉也結束後咱們重新開始動畫,這樣動畫就會一直這樣循環下去。
狀態變動監聽:
animation.addStatusListener((status){
print(status);
});
複製代碼
AnimatedWidget 類容許您從 setState()
調用中的動畫代碼中分離出 widget 代碼。AnimatedWidget 不須要維護一個 State 對象來保存動畫。
如下代碼爲官方文檔自定義 AnimatedLogo
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
class AnimatedLogo extends AnimatedWidget {
AnimatedLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: animation.value,
width: animation.value,
child: new FlutterLogo(),
),
);
}
}
class LogoApp extends StatefulWidget {
_LogoAppState createState() => new _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = new Tween(begin: 0.0, end: 300.0).animate(controller);
controller.forward();
}
Widget build(BuildContext context) {
return new AnimatedLogo(animation: animation);
}
dispose() {
controller.dispose();
super.dispose();
}
}
複製代碼
AnimatedWidget
爲何不須要維護一個 State
對象來保存動畫呢?
從 AnimatedWidget
源碼中看一看出 AnimatedWidget 是繼承自 StatefulWidget
類,在 AnimatedWidget
中,建立 state
是建立了 _AnimatedState
,接着看 _AnimatedState
類部分源碼:
abstract class AnimatedWidget extends StatefulWidget{
@override
_AnimatedState createState() => _AnimatedState();
}
複製代碼
在 _AnimatedState
類的 initState
方法添加了監聽 _handleChange
,並在 didUpdateWidget
和 dispose
方法中移除了,_handleChange
裏面只有一行代碼就是 setState
方法:
_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);
}
複製代碼
所謂的並行動畫就是一塊兒執行多個動畫,在 Flutter 中能夠在同一個動畫控制器上使用多個Tween,而後每一個Tween管理動畫中的不一樣效果,從而實現多個動畫同時執行。
final AnimationController controller =
new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
final Animation<double> sizeAnimation =
new Tween(begin: 0.0, end: 300.0).animate(controller);
final Animation<double> opacityAnimation =
new Tween(begin: 0.1, end: 1.0).animate(controller);
複製代碼
能夠經過sizeAnimation.value
來獲取大小,經過opacityAnimation.value
來獲取不透明度,但AnimatedWidget的構造函數只接受一個動畫對象。 爲了解決這個問題,能夠建立了本身的Tween對象並顯式計算了這些值。
其build
方法.evaluate()
在父級的動畫對象上調用Tween函數以計算所需的size
和opacity
值。
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
class AnimatedLogo extends AnimatedWidget {
// The Tweens are static because they don't change.
static final _opacityTween = new Tween<double>(begin: 0.1, end: 1.0);
static final _sizeTween = new Tween<double>(begin: 0.0, end: 300.0);
AnimatedLogo({Key key, Animation<double> animation})
: super(key: key, listenable: animation);
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return new Center(
child: new Opacity(
opacity: _opacityTween.evaluate(animation),
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
child: new FlutterLogo(),
),
),
);
}
}
複製代碼
直接貼代碼
///放大縮小動畫
Widget scale() {
return Column(
children: <Widget>[
Container(
height: 170,
child: Center(
child: Container(
width: _scaleAnimation.value,
height: _scaleAnimation.value,
child: new FlutterLogo(),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
color: Colors.blue,
child: Text(
"放大",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_scaleController.forward();
},
),
RaisedButton(
color: Colors.red,
child: Text(
"縮小",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_scaleController.reverse();
},
)
],
),
],
);
}
複製代碼
代碼:
/// 淡入淡出
Widget alpha() {
return Column(
children: <Widget>[
Container(
height: 170,
child: Center(
child: Container(
height: 100,
width: 100,
child: Opacity(
opacity: _alphaAnimation.value,
child: FlutterLogo(),
),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
color: Colors.blue,
child: Text(
"淡入",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_alphaController.forward();
},
),
RaisedButton(
color: Colors.red,
child: Text(
"淡出",
style: TextStyle(color: Colors.white),
),
onPressed: () {
_alphaController.reverse();
},
)
],
),
],
);
}
複製代碼
注意,一個 Widget 使用多個animationController 須要修改混入SingleTickerProviderStateMixin 爲 TickerProviderStateMixin。
完整代碼奉上GitHub地址:fluter_demo ,歡迎star和fork。
到此,本文就結束了,若有不當之處敬請指正,一塊兒學習探討,謝謝🙏。