
簡(jiǎn)述
在前面的兩篇博客中,我們了解了在Flutter中動(dòng)畫(huà)如何簡(jiǎn)單的使用動(dòng)畫(huà),了解Tween相關(guān)使用方法.但是在很多場(chǎng)景下,我們使用的并不是單單一種動(dòng)畫(huà),而是多種動(dòng)畫(huà)一起執(zhí)行或者順序執(zhí)行,那么在應(yīng)對(duì)這樣的場(chǎng)景我們?cè)撛趺崔k呢? 今天,我們就聊一聊如何在Flutter中實(shí)現(xiàn)這種并行動(dòng)畫(huà)或者串行動(dòng)畫(huà)呢?接下來(lái)我們就一起看看這兩種形式的動(dòng)畫(huà)是如何實(shí)現(xiàn)的.
并行動(dòng)畫(huà)
對(duì)于并行動(dòng)畫(huà)這種多個(gè)動(dòng)畫(huà)同時(shí)執(zhí)行,我們需要讓各個(gè)動(dòng)畫(huà)Animation的動(dòng)畫(huà)控制器AnimationController保持一致就可以了. 這個(gè)在上一篇Tween已經(jīng)進(jìn)行了對(duì)應(yīng)說(shuō)明演示.這里就直接把代碼拿來(lái)了.
首先創(chuàng)建動(dòng)畫(huà)并保持動(dòng)畫(huà)控制器的統(tǒng)一性.示例如下代碼所示.
_animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
_animation = Tween<double>(begin: 0, end: 50).animate(_animationController)
..addListener(() {
setState(() {});
});
_colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_animationController)
..addListener(() {
setState(() {});
});
然后再構(gòu)建build方法中直接使用_animation和_colorAnimation的動(dòng)畫(huà)值就好,具體代碼如下所示.
Container(
width: 200,
height: 50,
color: _colorAnimation.value,
margin: EdgeInsets.only(top: _animation.value),
),
串行動(dòng)畫(huà)
相對(duì)于并行動(dòng)畫(huà)而言,串行動(dòng)畫(huà)寫(xiě)起來(lái)就比較復(fù)雜的多,串行動(dòng)畫(huà)的實(shí)現(xiàn)方案總共有三種,分別是 監(jiān)聽(tīng)狀態(tài)法, Interval時(shí)間間隔法, TweenSequence動(dòng)畫(huà)序列法.下面我們就分別來(lái)看看三種方法的實(shí)現(xiàn)以及區(qū)別.
監(jiān)聽(tīng)狀態(tài)法
狀態(tài)監(jiān)聽(tīng)法主要通過(guò)AnimationController監(jiān)聽(tīng)動(dòng)畫(huà)的completed狀態(tài),然后再去執(zhí)行下一個(gè)動(dòng)畫(huà),如此往復(fù),直到所有動(dòng)畫(huà)完成.
例如現(xiàn)在我們需要實(shí)現(xiàn)先執(zhí)行組件在0.3秒鐘往下偏移50個(gè)單位,然后再執(zhí)行在0.6s中組件的顏色由 橘色 變?yōu)?紅色.
首先,我們先聲明位移動(dòng)畫(huà)控制器和顏色動(dòng)畫(huà)控制器以及位移動(dòng)畫(huà)和顏色動(dòng)畫(huà),代碼如下所示.
AnimationController _animationController;
Animation<double> _animation;
AnimationController _colorAnimationController;
Animation<Color> _colorAnimation;
然后,我們創(chuàng)建位移、顏色的動(dòng)畫(huà)控制器和動(dòng)畫(huà),具體代碼如下所示.
_animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
_animation = Tween<double>(begin: 0, end: 50).animate(_animationController)
..addListener(() {
setState(() {});
});
_colorAnimationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
_colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_colorAnimationController)
..addListener(() {
setState(() {});
});
最后,我們只需要監(jiān)聽(tīng)位移動(dòng)畫(huà)完成狀態(tài)之后執(zhí)行顏色動(dòng)畫(huà)即可,具體代碼如下所示.
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_colorAnimationController.forward();
};
});
整體Demo代碼如下所示.
class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animation;
AnimationController _colorAnimationController;
Animation<Color> _colorAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(duration: Duration(milliseconds: 300), vsync: this);
_animation = Tween<double>(begin: 0, end: 50).animate(_animationController)
..addListener(() {
setState(() {});
});
_colorAnimationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
_colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(_colorAnimationController)
..addListener(() {
setState(() {});
});
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_colorAnimationController.forward();
};
});
}
void startEasyAnimation() {
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: _colorAnimation.value,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"點(diǎn)擊執(zhí)行最簡(jiǎn)單動(dòng)畫(huà)",
style: TextStyle(color: Colors.black38),
),
),
],
),
),
);
}
}
Interval時(shí)間間隔法
上面的狀態(tài)監(jiān)聽(tīng)需要一個(gè)動(dòng)畫(huà)過(guò)程就寫(xiě)一個(gè)Controller,而且基本上還要每一個(gè)Controller都監(jiān)聽(tīng)執(zhí)行完成然后再去啟動(dòng)下一個(gè)Controller.如果一個(gè)動(dòng)畫(huà)過(guò)程有十幾個(gè),自己想想都是腦瓜子嗡嗡的.所以接下來(lái)我們就來(lái)介紹第二種方案 - Interval時(shí)間間隔法 .
Interval時(shí)間間隔法 的整體思路是一個(gè)動(dòng)畫(huà)Controller控制所有動(dòng)畫(huà)的執(zhí)行.然后每一個(gè)動(dòng)畫(huà)只需要確認(rèn)自己在整個(gè)動(dòng)畫(huà)的時(shí)間比重即可.
首先,聲明一個(gè)動(dòng)畫(huà)Controller和多個(gè)動(dòng)畫(huà).
AnimationController _animationController;
Animation<double> _animation;
Animation<Color> _colorAnimation;
然后初始化AnimationController,AnimationController的動(dòng)畫(huà)時(shí)間(duration)要設(shè)置成所有動(dòng)畫(huà)的總時(shí)長(zhǎng),例如這里我設(shè)定為600毫秒(_animation時(shí)長(zhǎng):300毫秒,_colorAnimation時(shí)長(zhǎng):300毫秒).
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
接下來(lái)就是初始化兩個(gè)Animation,Tween對(duì)象調(diào)用animate()函數(shù)不再是直接傳入上面的AnimationController,而是傳入一個(gè) CurvedAnimation 對(duì)象. CurvedAnimation構(gòu)建過(guò)程中需要傳入兩個(gè)參數(shù)一個(gè)是 parent ,用于指定AnimationController. 另外一個(gè)是 curve,用于指定動(dòng)畫(huà)曲線函數(shù).我們可以使用常用的動(dòng)畫(huà)曲線函數(shù),也可以自己生成,這里我們就自己生成.指定動(dòng)畫(huà)執(zhí)行的時(shí)間區(qū)間.
// CurvedAnimation的構(gòu)建方法
CurvedAnimation({
required this.parent,
required this.curve,
this.reverseCurve,
}) : assert(parent != null),
assert(curve != null) {
_updateCurveDirection(parent.status);
parent.addStatusListener(_updateCurveDirection);
}
由于兩個(gè)動(dòng)畫(huà)時(shí)間長(zhǎng)度是對(duì)分的,每一個(gè)都是300毫秒,所以 curve 參數(shù)中的值就分別是 Interval(0.0, 0.5) 、Interval(0.5, 1.0),兩個(gè)Animation的初始化過(guò)程如下所示.
_animation = Tween<double>(begin: 0, end: 50).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(0.0, 0.5),
),
)..addListener(() {
setState(() {});
});
_colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(0.5, 1.0),
),
)..addListener(() {
setState(() {});
});
整體Demo代碼如下所示.
class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animation;
Animation<Color> _colorAnimation;
@override
void initState() {
super.initState();
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
_animation = Tween<double>(begin: 0, end: 50).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(0.0, 0.5),
),
)..addListener(() {
setState(() {});
});
_colorAnimation = ColorTween(begin: Colors.orangeAccent, end: Colors.redAccent).animate(
CurvedAnimation(
parent: _animationController,
curve: Interval(0.5, 1.0),
),
)..addListener(() {
setState(() {});
});
}
void startEasyAnimation() {
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: _colorAnimation.value,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"點(diǎn)擊執(zhí)行最簡(jiǎn)單動(dòng)畫(huà)",
style: TextStyle(color: Colors.black38),
),
),
],
),
),
);
}
}
TweenSequence動(dòng)畫(huà)序列法
上面的兩種方案雖然能解決動(dòng)畫(huà)組的問(wèn)題,但是都太過(guò)于繁瑣,那么有沒(méi)有一種比較優(yōu)雅的方案呢?這就需要使用到 TweenSequence 和 TweenSequenceItem 這兩個(gè)類(lèi)了. 其中 TweenSequence 是動(dòng)畫(huà)組類(lèi),TweenSequenceItem 則是用來(lái)定義每一個(gè)動(dòng)畫(huà)的具體實(shí)現(xiàn)的類(lèi).但是TweenSequence 和 TweenSequenceItem也不是盡善盡美的,它最大的問(wèn)題就是前后變化的屬性值類(lèi)型必須是一致的.
下面,我們?nèi)匀灰愿淖僊argin為例, 先讓視圖組件往上移動(dòng)50,再讓視圖 來(lái)看看如何使用 TweenSequence 和 TweenSequenceItem 來(lái)實(shí)現(xiàn)這個(gè)動(dòng)畫(huà).
首先,我們聲明一個(gè) 動(dòng)畫(huà)控制器AnimationController 和 動(dòng)畫(huà)Animation.
AnimationController _animationController;
Animation<double> _animation;
仍然以兩者的動(dòng)畫(huà)總時(shí)長(zhǎng)為600毫秒,所以我們需要實(shí)現(xiàn)AnimationController,如下所示.
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
然后,我們通過(guò) TweenSequence 和 TweenSequenceItem 這兩個(gè)類(lèi)對(duì) 動(dòng)畫(huà)Animation 進(jìn)行實(shí)現(xiàn).
實(shí)現(xiàn)兩個(gè) TweenSequenceItem, TweenSequenceItem中的 <font color=red>weight</font> 屬性是來(lái)設(shè)定動(dòng)畫(huà)執(zhí)行的時(shí)間權(quán)重.也就是在整個(gè)動(dòng)畫(huà)過(guò)程,當(dāng)前動(dòng)畫(huà)執(zhí)行時(shí)長(zhǎng)占總時(shí)長(zhǎng)的比例.例如下面 第一個(gè)動(dòng)畫(huà)插值占的時(shí)間比例為 50/(50 + 100). 第二個(gè)動(dòng)畫(huà)插值占的時(shí)間比例為 100/(50 + 100) .
TweenSequenceItem downMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 1.0, end: 50.0),
weight: 50,
);
TweenSequenceItem upMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 50.0, end: 100.0),
weight: 100,
);
然后創(chuàng)建一個(gè)動(dòng)畫(huà)插值組,把上面兩個(gè)動(dòng)畫(huà)插值放入組中.
TweenSequence tweenSequence = TweenSequence<double>([
downMarginItem,
upMarginItem,
]);
最后,生成動(dòng)畫(huà)就OK了.
_animation = tweenSequence.animate(_animationController);
_animation.addListener(() {
setState(() {});
});
當(dāng)然了,上面的三步可以縮寫(xiě)成一步代碼來(lái)實(shí)現(xiàn),我只是進(jìn)行了分解代碼來(lái)說(shuō)明每一個(gè)代碼.
// 縮寫(xiě)代碼
_animation = TweenSequence<double>([
TweenSequenceItem<double>(
tween: Tween(begin: 0.0, end: 50.0),
weight: 50,
),
TweenSequenceItem<double>(
tween: Tween(begin: 50.0, end: 100.0),
weight: 100,
),
]).animate(_animationController)
..addListener(() {
setState(() {});
});
整體代碼如下所示.
class _FlutterAnimationWidgetState extends State<FlutterAnimationWidget> with TickerProviderStateMixin {
AnimationController _animationController;
Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(duration: Duration(milliseconds: 600), vsync: this);
TweenSequenceItem downMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 1.0, end: 50.0),
weight: 50,
);
TweenSequenceItem upMarginItem = TweenSequenceItem<double>(
tween: Tween(begin: 50.0, end: 100.0),
weight: 100,
);
TweenSequence tweenSequence = TweenSequence<double>([
downMarginItem,
upMarginItem,
]);
_animation = tweenSequence.animate(_animationController);
_animation.addListener(() {
setState(() {});
});
}
void startEasyAnimation() {
_animationController.forward();
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 50,
color: Colors.orangeAccent,
margin: EdgeInsets.only(top: _animation.value),
),
FlatButton(
onPressed: startEasyAnimation,
child: Text(
"點(diǎn)擊執(zhí)行最簡(jiǎn)單動(dòng)畫(huà)",
style: TextStyle(color: Colors.black38),
),
),
],
),
),
);
}
}
動(dòng)畫(huà)組實(shí)現(xiàn)總結(jié)
上面三種實(shí)現(xiàn)動(dòng)畫(huà)組基本上已經(jīng)說(shuō)完了,接下來(lái)我們就來(lái)對(duì)比其不同點(diǎn).
| 特性 | 監(jiān)聽(tīng)狀態(tài)法 | Interval時(shí)間間隔法 | TweenSequence動(dòng)畫(huà)序列法 |
|---|---|---|---|
| 代碼簡(jiǎn)潔度 | ???? | ?????? | ?????????? |
| 動(dòng)畫(huà)是否可交織 | ? | ? | ? |
| 動(dòng)畫(huà)屬性是否可以多變 | ? | ? | ? |
動(dòng)畫(huà)是否可交織 : 動(dòng)畫(huà)可否交織主要是說(shuō)兩個(gè)動(dòng)畫(huà)之間是否需要上一個(gè)動(dòng)畫(huà)完全執(zhí)行完成之后,下一個(gè)動(dòng)畫(huà)才能執(zhí)行.
動(dòng)畫(huà)屬性是否可以多變 : 動(dòng)畫(huà)屬性多變是指當(dāng)前動(dòng)畫(huà)過(guò)程中可變化的屬性是否可以有多個(gè),例如同時(shí)變化尺寸和顏色等等.
結(jié)語(yǔ)
OK,如何使用Flutter實(shí)現(xiàn)串行動(dòng)畫(huà)和并行動(dòng)畫(huà)就說(shuō)道這里,下一篇就說(shuō)一下在轉(zhuǎn)場(chǎng)動(dòng)畫(huà)比較常見(jiàn)的飛入飛出動(dòng)畫(huà) - Hero動(dòng)畫(huà),歡迎持續(xù)關(guān)注騷棟,有任何問(wèn)題歡迎聯(lián)系騷棟.
