在〈AnimatedBuilder〉中,接觸了更多動畫的細節,其中也留下了一個問題,如果想套用緩動曲線怎麼辦?
AnimationController
其實是 Animation
的實現,Animation
還有其他的子類,例如 CurvedAnimation
、ReverseAnimation
等,這些子類的實例可以組成階層關係,作為 child 的 Animation
,可以取得 parent 的 value
套用進一步的處理,從而組合出各式的動畫。
通常 AnimationController
會作為最上層的 parent,然後套用其他的 Animation
,例如,想要使用緩動曲線的話,可以如下:
AnimationController animationController;
Animation curve;
@override
void initState() {
animationController = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..stop();
curve = CurvedAnimation(parent: animationController, curve: Curves.easeOut);
...
}
AnimationController
的 value
會傳入 Curves.easeOut
的 transform
方法,那麼怎麼整合進〈AnimatedBuilder〉的範例呢?可以進一步地將 curve
指定給 Tween
的 animate
方法:
Animation<Color> tween = ColorTween(begin: Colors.white, end: Colors.red).animate(curve);
animate
會取得傳入的 Animation
的 value
,進一步套用使用了 begin
、end
的運算,然後傳回 Animation
,也就是說,如果你願意的話,Animation
增加更多的套用層次。
因此單就〈AnimatedBuilder〉中的範例,可以改寫如下:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: Caterpillar(),
)
);
class Caterpillar extends StatefulWidget {
@override
State<StatefulWidget> createState() => _CaterpillarState();
}
class _CaterpillarState extends State<Caterpillar> with SingleTickerProviderStateMixin {
var start = false;
Animation<double> animationController;
Animation<double> curve;
Animation<Color> tween;
@override
void initState() {
super.initState();
// 建立 Animation 階層
animationController = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..stop();
curve = CurvedAnimation(parent: animationController, curve: Curves.easeOut);
tween = ColorTween(begin: Colors.white, end: Colors.red).animate(curve);
}
@override
void dispose() {
super.dispose();
animationController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('openhome.cc'),
),
body: Center(
child: AnimatedBuilder(
animation: animationController,
builder: (_, __) {
return Image(
image: NetworkImage('https://openhome.cc/Gossip/images/caterpillar.jpg'),
// 取得 Animation 的 value
color: tween.value,
colorBlendMode: BlendMode.colorBurn,
);
}
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() {
start = !start;
if(start) {
animationController.repeat(reverse: true);
}
else {
animationController.stop();
}
}),
tooltip: start ? 'Stop' : 'Start',
child: Icon(start ? Icons.stop : Icons.play_arrow),
),
);
}
}
讓我們更進一步地深入動畫的細節,Flutter 是怎麼完成動畫的呢?令人驚奇地,Flutter 是不斷地重建 Widget 樹,舉個來說,上例如果不使用 AnimatedBuilder
的話,該怎麼寫呢?可以利用 Animation
的 addListener
或 addStatusListener
來監聽事件,例如,最基本就是透過 addListener
來監聽每次 value
的改變,然後呼叫 setState
:
import 'package:flutter/material.dart';
...略
class _CaterpillarState extends State<Caterpillar> with SingleTickerProviderStateMixin {
var start = false;
AnimationController animationController;
Animation<double> curve;
Animation<Color> tween;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..stop();
curve = CurvedAnimation(parent: animationController, curve: Curves.easeOut);
tween = ColorTween(begin: Colors.white, end: Colors.red).animate(curve);
// 在每次 value 改變時呼叫 setState
tween.addListener(() => setState(() {
}));
}
@override
void dispose() {
super.dispose();
animationController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('openhome.cc'),
),
body: Center(
// 不使用 AnimatedBulider,直接使用 tween.value
child: Image(
image: NetworkImage('https://openhome.cc/Gossip/images/caterpillar.jpg'),
color: tween.value,
colorBlendMode: BlendMode.colorBurn,
),
),
floatingActionButton: FloatingActionButton(
...略
),
);
}
}
那麼那個 SingleTickerProviderStateMixin
是做什麼的?它是 TickerProvider
的實作,顧名思義,可以提供 Ticker
,Single 的意思是,它的 createTicker
方法只能被呼叫一次,之後只使用這唯一的 Ticker
來進行計時,可以實作個簡單的 TickerProvider
來仿效:
import 'package:flutter/material.dart';
import 'package:flutter/src/scheduler/ticker.dart';
...略
// 簡單的 TickerProvider 實作
class MyTickProvider implements TickerProvider {
Ticker _ticker;
@override
Ticker createTicker(onTick) {
assert(() {
if (_ticker == null)
return true;
throw FlutterError('MyTickProvider 的 createTicker 只能被呼叫一次');
}());
_ticker = Ticker(onTick);
return _ticker;
}
}
class _CaterpillarState extends State<Caterpillar> {
var start = false;
AnimationController animationController;
Animation<double> curve;
Animation<Color> tween;
@override
void initState() {
super.initState();
animationController = AnimationController(
duration: const Duration(seconds: 2),
// 使用 MyTickProvider
vsync: MyTickProvider(),
)..stop();
curve = CurvedAnimation(parent: animationController, curve: Curves.easeOut);
tween = ColorTween(begin: Colors.white, end: Colors.red).animate(curve);
tween.addListener(() => setState(() {
}));
}
@override
void dispose() {
super.dispose();
animationController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
...略
);
}
}
Ticker
的目的,是在每次的計時呼叫指定的回呼函式,一開始建立時 Ticker
是停止狀態,可呼叫 start
來啟動,Ticker
建立時指定的回呼函式,必須接受 Duration
,在 AnimationController
內部會管理 Ticker
,回呼函式會用來變動 value
,若要簡單的示意會像是:
var value = 0;
Ticker((_) {
print(value);
value++;
}).start();
這會不斷地顯示 value
的遞增。基本上,你只需要知道 Ticker
的存在,透過 AnimationController
來控制 value
就可以了。
AnimationController
內部只會使用一個 Ticker
,會呼叫 TickerProvider
的 createTicker
一次,因此使用 SingleTickerProviderStateMixin
就可以了。
如果要操作多個 AnimationController
,可以使用 TickerProviderStateMixin
,你可以多次呼叫它的 createTicker
,在內部,TickerProviderStateMixin
會使用 Set
來管理建立的 Ticker
。