主本文主要說明動畫的基本原理和簡單的動畫的例項,如有不當之處敬請指正。
閱讀本文大約需要 6 分鐘
背景
給UI介面設計合理的動畫,可以讓使用者覺得更加流暢、直觀,提高使用者的互動使用感受,改善使用者體驗。
在 Flutter 中動畫分為兩類:基於補間 (Tween) 的和基於物理 (Physics) 的;
補間動畫是介於兩者之間的簡稱,在補間動畫中定義起點和終點、時間點以及定義時間變化和速度的曲線,然後由系統計算如何從開始點到結束點。
物理動畫是運動被模擬為與真實世界的行為相似,比如拋一件物體,它落在什麼地方取決於這個物體的重量,丟擲去的速度以及這個物體與地面的高度,類似數學中的拋物線運動軌跡。
介紹
在 Flutter 中想要實現動畫效果離不開幾個核心的角色:Animation(動畫物件),AnimationController(動畫控制器),Tweens(插值器),Curves(動畫曲線);
1、Animation
在 Flutter 中動畫本身和UI渲染沒有任何關係,Animation是一個抽象類,它擁有其當前值和狀態(完成或停止),Flutter 中的動畫系統就是基於 Animation
物件的。其中比較常用的就是Animation類是Animation。它可以通過其 value 屬性來獲取當前動畫的值。
Animation 除了可以生成 double 的值之外還可以生成如:顏色--Animation<Color>
或者大小--Animation<Size>
。
Animation 物件可以擁有 Listeners 和 StatusListeners 監聽器,可以用 addListener()
和addStatusListener()
來新增。只要動畫的的值發生變化,就會呼叫監聽器。正常我們在 Listeners 中呼叫setState() 來觸發UI重建;動畫開始、結束、向前移動或向後移動時會呼叫StatusListener。
2、AnimationController
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();
}
複製程式碼
3、Tween
預設情況下,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);
複製程式碼
4、CurvedAnimation
Curves 用來調整動畫過程中隨時間的變化率,預設情況下,動畫以均勻的線性模型變化。Flutter 內部也提供了一系列實現相應變化率的 Curves 物件:linea ------ 線性,decelerate ------ 減速等。
當然,也可以自定義繼承 Curves 的類來定義動畫的變化率,如:
class ShakeCurve extends Curve {
@override
double transform(double t) {
return math.sin(t * math.PI * 2);
}
}
複製程式碼
5、新增監聽
目前為止動畫只是實現了自身數值的變化,並沒有讓 Widget 動起來,這裡我們需要對動畫數值進行監聽,然後使用 setstatus 來更新 Widget 的屬性,從而使 Widget 動起來。
新增數值監聽:
Animation animation = CurvedAnimation(parent: _animationController, curve: Curves.linear);
animation.addListener((){
setState(() {
});
});
複製程式碼
除此之外我們還可以監聽動畫的狀態變更,當動畫結束時我們反轉動畫,當動畫的反轉也結束後我們從新開始動畫,這樣動畫就會一直這樣迴圈下去。
狀態變更監聽:
animation.addStatusListener((status){
print(status);
});
複製程式碼
6、AnimatedWidget
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);
}
複製程式碼
7、並行動畫
所謂的並行動畫就是一起執行多個動畫,在 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(),
),
),
);
}
}
複製程式碼
例項
效果圖
1、縮放動畫
直接貼程式碼
///放大縮小動畫
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();
},
)
],
),
],
);
}
複製程式碼
2、淡入淡出動畫
程式碼:
/// 淡入淡出
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。
到此,本文就結束了,如有不當之處敬請指正,一起學習探討,謝謝?。