在〈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。

