上一篇文章Flutter 動(dòng)畫(huà)詳解系列概述了下Flutter中的動(dòng)畫(huà)類型,及如何選擇恰當(dāng)?shù)膭?dòng)畫(huà)創(chuàng)建方式,接下來(lái)我們來(lái)看下最簡(jiǎn)單的動(dòng)畫(huà),隱式動(dòng)畫(huà)。
系統(tǒng)的隱式動(dòng)畫(huà)Widget
在 Flutter 中的 Widgets 中有一部已經(jīng)實(shí)現(xiàn)隱式動(dòng)畫(huà)Widget。如下圖列出部分:
首先我們來(lái)看一段未使用動(dòng)畫(huà)的代碼:
bool _bigger = false;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Container(
width: _bigger ? 100 : 500,
height: 100,
color: Colors.red,
),
),
RaisedButton(
onPressed: () => setState(() {
_bigger = !_bigger;
}),
child: Icon(Icons.star),
),
],
),
);
}
未加動(dòng)畫(huà)效果時(shí),矩形的形變會(huì)顯得十分生硬,如果使用AnimatedContainer
替換Container
,增加一個(gè)動(dòng)畫(huà)的過(guò)渡效果:
Center(
child: AnimatedContainer(
width: _bigger ? 100 : 500,
height: 100,
color: Colors.red,
duration: Duration(seconds: 1),
),
),
整個(gè)過(guò)渡過(guò)程顯得比較自然順暢,我們通過(guò)新舊值之間的值進(jìn)行動(dòng)畫(huà)處理的過(guò)程稱為插值。每當(dāng)舊值和新值發(fā)生變化時(shí),AnimatedContainer便會(huì)處理其屬性插值。
同樣我們也可以通過(guò)插值來(lái)修改AnimatedContainer的其它屬性,包括decoration
的漸變色:
AnimatedContainer(
decoration: BoxDecoration(
gradient: RadialGradient(
colors: [Colors.purple, Colors.transparent],
stops: [ _bigger ? 0.2 : 0.5, 1.0])
),
),
上述代碼很簡(jiǎn)單的演示如何使用 隱式動(dòng)畫(huà)Widget 來(lái)實(shí)現(xiàn)動(dòng)畫(huà)效果,非常的方便簡(jiǎn)單,但這也意味著可靈活性較差,在隱式動(dòng)畫(huà)Widget中,我們控制動(dòng)畫(huà)效果只能控制動(dòng)畫(huà)時(shí)長(zhǎng)(Duration)和動(dòng)畫(huà)的曲線(Curve,具體的曲線效果可以參考 系統(tǒng)自帶的曲線效果)。
AnimatedContainer(
width: _bigger ? 100 : 500,
child: Image.asset('assets/star.png'),
duration: Duration(seconds: 1),
curve: Curves.easeInOutQuint,
),
另外除了系統(tǒng)自帶的曲線效果外,我們還可以通過(guò)繼承Curve
來(lái)實(shí)現(xiàn)自定義的曲線效果,如下實(shí)現(xiàn)了正弦曲線。
class SineCurve extends Curve {
final double count;
SineCurve({this.count = 1});
@override
double transformInternal(double t) {
return sin(count * 2 * pi * t) * 0.5 + 0.5;
}
}
小結(jié)
在Flutter中,系統(tǒng)已經(jīng)提供了隱式動(dòng)畫(huà)的Widget,這些Widget是普通Widget的動(dòng)畫(huà)版本,我們可以通過(guò) duration
、curve
來(lái)控制動(dòng)畫(huà)效果。
還有我們不一定需要通過(guò)StatefulWidget中使用setState來(lái)生成動(dòng)畫(huà)效果,我們也可以使用StreamBuilder和FutureBuilder來(lái)觸發(fā)動(dòng)畫(huà)。
FutureBuilder(
future: future,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
double width;
switch(snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
width = 0;
break;
case ConnectionState.done:
width = 500;
break;
}
return AnimatedContainer(
width: width,
height: 100,
color: Colors.red,
duration: Duration(seconds: 1),
);
}
),
以上,如果Flutter框架提供給你的隱式動(dòng)畫(huà)Widget不能滿足你的需求,那么進(jìn)一步的話可以試試使用TweenAnimationBuilder
來(lái)自定義創(chuàng)建隱式動(dòng)畫(huà)。
自定義隱式動(dòng)畫(huà)TweenAnimationBuilder
使用TweenAnimationBuilder,該 Widget 使用的時(shí)候我們需要傳遞 duration 參數(shù)動(dòng)畫(huà)時(shí)間、tween 參數(shù)動(dòng)畫(huà)要設(shè)置的值的范圍(補(bǔ)間)、重要的還有 builder 參數(shù),builder函數(shù)的參數(shù)包含context、補(bǔ)間參數(shù)tween的類型、還有child
,讓我們看一個(gè)簡(jiǎn)單的例子,紅色的矩形框旋轉(zhuǎn)360度:
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 2 * pi),
duration: Duration(seconds: 2),
builder: (BuildContext context, double angle, Widget child) {
return Transform.rotate(
angle: angle,
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
);
},
),
讓我們?cè)賮?lái)看個(gè)例子,使用ColorFilered
Wideget做一個(gè)圖片渲染的效果。
TweenAnimationBuilder(
tween: ColorTween(begin: Colors.white, end: Colors.red),
duration: Duration(seconds: 2),
builder: (_, Color color, __) {
return ColorFiltered(
child: Image.asset('assets/sun.png'),
colorFilter: ColorFilter.mode(color, BlendMode.modulate),
);
},
)
通過(guò)Tween補(bǔ)間參數(shù)設(shè)置了從白色到紅色的過(guò)渡,由顏色和圖片的混合,另外如何補(bǔ)間參數(shù)可變的,所以如果補(bǔ)間參數(shù)是不變的話可以將參數(shù)聲明為靜態(tài)常量來(lái)使用。
static final colorTween = ColorTween(begin: Colors.white, end: Colors.red);
Center(
child: TweenAnimationBuilder<Color>(
tween: colorTween,
duration: Duration(seconds: 2),
builder: (_, Color color, __) {
return ColorFiltered(
child: Image.asset('assets/sun.png'),
colorFilter: ColorFilter.mode(color, BlendMode.modulate),
);
},
),
),
動(dòng)態(tài)修改 Tween 參數(shù)
上面的例子中我們并沒(méi)有調(diào)用setState,僅僅展示了動(dòng)畫(huà)從Tween的初始值到終值的簡(jiǎn)單動(dòng)畫(huà)效果,除此之外,我們還可以通過(guò)動(dòng)態(tài)的修改Tween來(lái)實(shí)現(xiàn)動(dòng)畫(huà)效果:
class OngoingAnimationByModifyingEndTweenValue extends StatefulWidget {
@override
_OngoingAnimationState createState() => _OngoingAnimationState();
}
class _OngoingAnimationState extends State<OngoingAnimationByModifyingEndTweenValue> {
double _newValue = .4;
Color _newColor = Colors.white;
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
starsBackground,
Column(
children: <Widget>[
Center(
child: TweenAnimationBuilder(
tween: ColorTween(begin: Colors.white, end: _newColor),
duration: Duration(seconds: 2),
builder: (_, Color color, __) {
return ColorFiltered(
child: Image.asset('assets/sun.png'),
colorFilter: ColorFilter.mode(color, BlendMode.modulate),
);
},
),
),
Slider.adaptive(
value: _newValue,
onChanged: (double value) {
setState(() {
_newValue = value;
_newColor = Color.lerp(Colors.white, Colors.red, value);
});
},
),
],
),
],
);
}
}
首先我們聲明一個(gè)_newColor
作為 Tween 的終值,通過(guò)滑動(dòng) Slider
Widget,我們改變_newColor
。調(diào)用setState每次的滑動(dòng)都會(huì)使得動(dòng)畫(huà)更新。
另外,需要明確的一點(diǎn)事,TweenAnimationBuilder 的動(dòng)畫(huà)屬性值是從當(dāng)前值向最新的終值轉(zhuǎn)變的。如上面的例子,當(dāng)我們拖動(dòng) Slider 時(shí),顏色變化相對(duì)于之前的顏色,而不是每次從最初的白色開(kāi)始動(dòng)畫(huà)漸變。
TweenAnimationBuilder 總會(huì)將當(dāng)前顏色到終值顏色平滑的動(dòng)畫(huà)過(guò)渡。所以如果改變的不是終值顏色而是開(kāi)始顏色,動(dòng)畫(huà)效果是不會(huì)有區(qū)別的。
補(bǔ)充說(shuō)明
除了上訴介紹的TweenAnimationBuilder參數(shù)外,我們還需要注意的參數(shù)還有:
- curve,動(dòng)畫(huà)曲線,在上一篇文章Flutter 動(dòng)畫(huà)詳解系列有過(guò)介紹。
- 動(dòng)畫(huà)完成的回調(diào)
onEnd :
,我們可以在動(dòng)畫(huà)完成時(shí)完成指定的操作,如動(dòng)畫(huà)完成后顯示另一個(gè)Widget。 - child參數(shù),child參數(shù)的設(shè)置其實(shí)也是一個(gè)潛在的性能優(yōu)化項(xiàng),正確的設(shè)置child,對(duì)動(dòng)畫(huà)性能的提升也是一大幫助。例子中雖然顏色發(fā)生了變化,但圖片本身保持不變,但是當(dāng)前代碼是每次build都會(huì)重新創(chuàng)建Image Widget。針對(duì)此類的優(yōu)化方式,我們可以提前創(chuàng)建圖片,將圖片作為參數(shù)傳入,這樣 Flutter 就知道每幀渲染時(shí)變化的是顏色,而不是圖片本身。 當(dāng)然因?yàn)槔颖旧砗?jiǎn)單,所以此優(yōu)化不會(huì)有明顯的效果,但當(dāng)實(shí)現(xiàn)復(fù)雜動(dòng)畫(huà)效果時(shí),慎重考慮child的實(shí)現(xiàn),將會(huì)對(duì)你的動(dòng)畫(huà)性能帶來(lái)一定的幫助。
總結(jié)
OK,以上就是對(duì) Flutter 中的隱式動(dòng)畫(huà)的介紹了,包括系統(tǒng)自帶的AnimatedFoo
和TweenAnimationBuilder
都有了一定的涉及。包括如何不通過(guò)使用StatefulWidget
來(lái)實(shí)現(xiàn)動(dòng)畫(huà)效果、如何改變tween
的終值產(chǎn)生順滑的動(dòng)畫(huà)效果、如何提升TweenAnimationBuilder
的動(dòng)畫(huà)性能時(shí),我們可以設(shè)置tween參數(shù)時(shí)可以考慮設(shè)置為靜態(tài)常量,設(shè)置child參數(shù)時(shí)可以考慮提前創(chuàng)建好child,作為參數(shù)傳遞。