參考來源:https://flutterchina.club/animations/
動畫類型
補(bǔ)間(Tween)動畫
:在補(bǔ)間動畫中,定義了開始點(diǎn)和結(jié)束點(diǎn)、時間線以及定義轉(zhuǎn)換時間和速度的曲線,然后由框架自動計(jì)算如何從開始點(diǎn)過渡到結(jié)束點(diǎn)。基于物理的動畫
:在基于物理的動畫中,運(yùn)動被模擬為與真實(shí)世界的行為相似。例如,當(dāng)你擲球時,它在何處落地,取決于拋球速度有多快、球有多重、距離地面有多遠(yuǎn)。 類似地,將連接在彈簧上的球落下(并彈起)與連接到繩子上的球放下的方式也是不同。
常見的動畫模式
- 動畫列表或網(wǎng)格(AnimatedList 示例
- 共享元素轉(zhuǎn)換(Hero 動畫
- 交錯動畫
基本的動畫概念和類
Animation對象:
- Animation對象是Flutter動畫庫中的一個核心類,它生成指導(dǎo)動畫的值。
- Animation對象知道動畫的當(dāng)前狀態(tài)(例如,它是開始、停止還是向前或向后移動),但它不知道屏幕上顯示的內(nèi)容。
- Flutter中的Animation對象是一個在一段時間內(nèi)依次生成一個區(qū)間之間值的類。Animation對象的輸出可以是線性的、曲線的、一個步進(jìn)函數(shù)或者任何其他可以設(shè)計(jì)的映射。 根據(jù)Animation對象的控制方式,動畫可以反向運(yùn)行,甚至可以在中間切換方向。
- Animation還可以生成除double之外的其他類型值,如:Animation<Color> 或 Animation<Size>
- Animation對象有狀態(tài),可以通過訪問其value屬性獲取動畫的當(dāng)前值
- Animation對象本身和UI渲染沒有任何關(guān)系
Interval
在[begin]之前為0.0的曲線,然后根據(jù)[curve曲線]在[end]時從0.0到1.0,然后是1.0。
可以使用[Interval]來延遲動畫。例如,使用[Interval]將[begin]設(shè)置為0.5,將[end]設(shè)置為1.0的[Interval]使用[Interval],這6秒的動畫將本質(zhì)上變成三秒鐘后開始的動畫。
CurvedAnimation:將動畫過程定義為一個非線性曲線,屬于Animation<double>類型
final CurvedAnimation curve =
new CurvedAnimation(parent: controller, curve: Curves.easeIn);
注: Curves 類類定義了許多常用的曲線,也可以創(chuàng)建自己的,例如:
class ShakeCurve extends Curve {
@override
double transform(double t) {
return math.sin(t * math.PI * 2);
}
}
AnimationController:
- AnimationController是一個特殊的Animation對象,在屏幕刷新的每一幀,就會生成一個新的值,默認(rèn)情況下,AnimationController在給定的時間段內(nèi)會線性的生成從0.0到1.0的數(shù)字
- 屬于Animation<double>類型
- 具有控制動畫的方法,例如,.forward()方法可以啟動動畫
- 當(dāng)創(chuàng)建一個AnimationController時,需要傳遞一個vsync參數(shù),存在vsync時會防止屏幕外動畫(動畫的UI不在當(dāng)前屏幕時)消耗不必要的資源。
通過將SingleTickerProviderStateMixin添加到類定義中,可以將stateful對象作為vsync的值。如果要使用自定義的State對象作為vsync時,請包含TickerProviderStateMixin。
// 下面代碼創(chuàng)建一個Animation對象,但不會啟動它運(yùn)行:
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
Tween:
- 默認(rèn)情況下,AnimationController對象的范圍從0.0到1.0,使用Tween使動畫可以生成不同的范圍或數(shù)據(jù)類型的值。
- Tween是一個無狀態(tài)(stateless)對象,需要begin和end值,Tween的唯一作用就是定義從輸入范圍到輸出范圍的映射。
- Tween繼承自Animatable<T>,而不是繼承自Animation<T>。Animatable與Animation相似,不是必須輸出double值。例如,ColorTween指定兩種顏色之間的過渡。
- evaluate(Animation<double> animation)方法將映射函數(shù)應(yīng)用于動畫當(dāng)前值,Animation對象的當(dāng)前值可以通過value()方法取到。
- 要使用Tween對象,請調(diào)用其animate()方法,傳入一個控制器對象(Tween.animate),注意animate()返回的是一個Animation,而不是一個Animatable。
// 以下示例,Tween生成從-200.0到0.0的值
final Tween doubleTween = new Tween<double>(begin: -200.0, end: 0.0);
// ColorTween指定兩種顏色之間的過渡
final Tween colorTween =
new ColorTween(begin: Colors.transparent, end: Colors.black54);
// 以下代碼在500毫秒內(nèi)生成從0到255的整數(shù)值
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(controller);
// 以下示例構(gòu)建了一個控制器、一條曲線和一個Tween:
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
new CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(curve);
動畫通知
- 一個Animation對象可以擁有Listeners和StatusListeners監(jiān)聽器,可以用addListener()和addStatusListener()來添加
- 只要動畫的值發(fā)生變化,就會調(diào)用監(jiān)聽器
- 動畫開始、結(jié)束、向前移動或向后移動(如AnimationStatus所定義)時會調(diào)用StatusListener
- 一個Listener最常見的行為是調(diào)用setState()來觸發(fā)UI重建。
動畫示例
要使用Animation<>對象進(jìn)行渲染,請將Animation對象存儲為Widget的成員,然后使用其value值來決定如何繪制
考慮下面的應(yīng)用程序,它繪制Flutter logo時沒有動畫:
import 'package:flutter/material.dart';
class LogoApp extends StatefulWidget {
_LogoAppState createState() => new _LogoAppState();
}
class _LogoAppState extends State<LogoApp> {
Widget build(BuildContext context) {
return new Center(
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: 300.0,
width: 300.0,
child: new FlutterLogo(),
),
);
}
}
void main() {
runApp(new LogoApp());
}
修改以上代碼,通過一個逐漸放大的動畫顯示logo。定義AnimationController時,必須傳入一個vsync對象。
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
class LogoApp extends StatefulWidget {
_LogoAppState createState() => new _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = new Tween(begin: 0.0, end: 300.0).animate(controller)
..addListener(() {
setState(() {
// the state that has changed here is the animation object’s value
});
});
controller.forward();
}
Widget build(BuildContext context) {
return new Center(
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: animation.value,
width: animation.value,
child: new FlutterLogo(),
),
);
}
dispose() {
controller.dispose();
super.dispose();
}
}
void main() {
runApp(new LogoApp());
}
該addListener()函數(shù)調(diào)用了setState(),所以每次動畫生成一個新的數(shù)字時,當(dāng)前幀被標(biāo)記為臟(dirty),這會導(dǎo)致widget的build()方法再次被調(diào)用。 在build()中,改變container大小,因?yàn)樗母叨群蛯挾痊F(xiàn)在使用的是animation.value。動畫完成時釋放控制器(調(diào)用dispose()方法)以防止內(nèi)存泄漏。
用AnimatedWidget簡化
- 使用AnimatedWidget助手類(而不是addListener()和setState())來給widget添加動畫
- 使用AnimatedWidget創(chuàng)建一個可重用動畫的widget。要從widget中分離出動畫過渡,請使用AnimatedBuilder。
- Flutter API提供的關(guān)于AnimatedWidget的示例包括:AnimatedBuilder、AnimatedModalBarrier、DecoratedBoxTransition、FadeTransition、PositionedTransition、RelativePositionedTransition、RotationTransition、ScaleTransition、SizeTransition、SlideTransition。
- AnimatedWidget類允許您從setState()調(diào)用中的動畫代碼中分離出widget代碼。AnimatedWidget不需要維護(hù)一個State對象來保存動畫。
在下面的重構(gòu)示例中,LogoApp現(xiàn)在繼承自AnimatedWidget而不是StatefulWidget。AnimatedWidget在繪制時使用動畫的當(dāng)前值。LogoApp仍然管理著AnimationController和Tween。
// Demonstrate a simple animation with AnimatedWidget
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();
}
}
void main() {
runApp(new LogoApp());
}
LogoApp將Animation對象傳遞給基類并用animation.value設(shè)置容器的高度和寬度,因此它的工作原理與之前完全相同。
監(jiān)視動畫的過程
- 使用addStatusListener來處理動畫狀態(tài)更改的通知,例如啟動、停止或反轉(zhuǎn)方向。
知道動畫何時改變狀態(tài)通常很有用的,如完成、前進(jìn)或倒退。你可以通過addStatusListener()來得到這個通知。
// 以下代碼用來監(jiān)聽動態(tài)狀態(tài)更改并打印更新:
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)
..addStatusListener((state) => print("$state"));
controller.forward();
}
//...
}
運(yùn)行此代碼將輸出以下內(nèi)容:
AnimationStatus.forward
AnimationStatus.completed
接下來,使用addStatusListener()在開始或結(jié)束時反轉(zhuǎn)動畫。這產(chǎn)生了循環(huán)效果:
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);
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
controller.forward();
}
//...
}
用AnimatedBuilder重構(gòu)
- AnimatedBuilder用于將widget與動畫分離
- AnimatedBuilder了解如何渲染過渡.
- AnimatedBuilder 不知道如何渲染widget,也不知道如何管理Animation對象。
- 使用AnimatedBuilder將動畫描述為另一個widget的build方法的一部分。如果你只是想用可復(fù)用的動畫定義一個widget,請使用AnimatedWidget。
- Flutter API中AnimatedBuilder的示例包括: BottomSheet、ExpansionTile、 PopupMenu、ProgressIndicator、RefreshIndicator、Scaffold、SnackBar、TabBar、TextField。
- 與AnimatedWidget類似,AnimatedBuilder自動監(jiān)聽來自Animation對象的通知,并根據(jù)需要將該控件樹標(biāo)記為臟(dirty),因此不需要手動調(diào)用addListener()。
從widget樹的底部開始,渲染logo的代碼直接明了:
class LogoWidget extends StatelessWidget {
// Leave out the height and width so it fills the animating parent
build(BuildContext context) {
return new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
child: new FlutterLogo(),
);
}
}
class GrowTransition extends StatelessWidget {
GrowTransition({this.child, this.animation});
final Widget child;
final Animation<double> animation;
Widget build(BuildContext context) {
return new Center(
child: new AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return new Container(
height: animation.value, width: animation.value, child: child);
},
child: child),
);
}
}
class LogoApp extends StatefulWidget {
_LogoAppState createState() => new _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with TickerProviderStateMixin {
Animation animation;
AnimationController controller;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
final CurvedAnimation curve =
new CurvedAnimation(parent: controller, curve: Curves.easeIn);
animation = new Tween(begin: 0.0, end: 300.0).animate(curve);
controller.forward();
}
Widget build(BuildContext context) {
return new GrowTransition(child: new LogoWidget(), animation: animation);
}
dispose() {
controller.dispose();
super.dispose();
}
}
void main() {
runApp(new LogoApp());
}
initState()方法創(chuàng)建一個AnimationController和一個Tween,然后通過animate()綁定它們。魔術(shù)發(fā)生在build()方法中,該方法返回一個帶有LogoWidget作為子對象的GrowTransition對象,以及一個用于驅(qū)動過渡的動畫對象。
并行動畫
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(),
),
),
);
}
}
class LogoApp extends StatefulWidget {
_LogoAppState createState() => new _LogoAppState();
}
class _LogoAppState extends State<LogoApp> with TickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
initState() {
super.initState();
controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn);
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
controller.forward();
}
Widget build(BuildContext context) {
return new AnimatedLogo(animation: animation);
}
dispose() {
controller.dispose();
super.dispose();
}
}
void main() {
runApp(new LogoApp());
}
AnimatedWidget的構(gòu)造函數(shù)只接受一個動畫對象。 為了解決這個問題,該示例創(chuàng)建了自己的Tween對象并顯式計(jì)算了這些值。其build方法.evaluate()在父級的動畫對象上調(diào)用Tween函數(shù)以計(jì)算所需的size和opacity值。