【文章內容使用 Gemini 1.5 Pro 自動產生】
動畫深探
去年我錄製了 Flutter 動畫系列中的其中一集,我想我會將相同的內容發佈給那些喜歡文字勝於視頻的人。
在這個系列的其他集中,我的同事們討論了在 Flutter 中建立動畫的所有實際方法。在我的那一集裡不是這樣。在這裡,您將學習如何以最不務實的方式實現動畫。(但同時您也會學到一些東西。)
讓我們從簡單輕鬆的東西開始:
什麼是運動?
你看,運動是一種錯覺。看看這個:
這是一個謊言。您實際上看到的是許多靜止的圖片以快速連續的方式顯示。這就是電影運作的方式。在電影中,這些單獨的圖片稱為幀,因為數位螢幕的運作方式類似,所以這裡也稱為幀。電影通常每秒顯示 24 幀。現代數位設備每秒顯示 60 到 120 幀。
因此,如果運動是謊言,那麼所有這些 AnimationFoo 和 FooTransition Widgets 到底在做什麼?當然,因為幀需要每秒構建高達 120 次,所以 UI 不能每次都重新構建。
還是可以?
事實上,Flutter 中的動畫只不過是在每一幀上重新構建 Widget 樹的一部分的方法。沒有特殊情況。Flutter 足夠快可以做到。
讓我們看看 Flutter 動畫的基礎構建塊之一:AnimatedBuilder。此 Widget 是一個 AnimatedWidget,它由 _AnimatedState 支援。在 State 的 initState() 方法中,我們正在監聽動畫(或者,如這裡所稱的 Listenable),當它更改其值時,我們會… 呼叫 setState()。
這就是了。Flutter 中的動畫只不過是快速連續地更改某個 Widget 的狀態,每秒 60 到 120 次。
我可以證明。這是一個從零到光速「動畫」的動畫。雖然它在每一幀上都更改文字,但從 Flutter 的角度來看,它只不過是另一個動畫。
讓我們使用 Flutter 的動畫框架從頭開始建立這個動畫。
通常,我們會使用 TweenAnimationBuilder Widget 或類似的東西,但在本文中,我們將忽略所有這些,而使用計時器、控制器和 setState。
計時器
讓我們先談談計時器。99% 的時間,您不會直接使用計時器。但是,我認為談談它仍然很有幫助,即使只是為了揭開它神秘的面紗。
計時器是一個針對每一幀呼叫函數的物件。
var ticker = Ticker((elapsed) => print('hello'));
ticker.start();
在本例中,我們會在每一幀中列印「hello」。誠然,這並不十分有用。
此外,我們忘記呼叫 ticker.dispose(),所以現在我們的計時器將永遠持續下去,直到我們關閉應用程式。
這就是為什麼 Flutter 會提供 SingleTickerProviderStateMixin,這個恰如其分的 Mixin 您在之前的幾個視頻中都有看到。
這個 Mixin 負責管理計時器的麻煩。只需將它添加到您的 Widget 的狀態中,現在您的狀態就默默地成為了 TickerProvider。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
}
這意味著 Flutter 框架可以向您的狀態請求計時器。最重要的是,AnimationController 可以向狀態請求計時器。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
@override
Widget build(BuildContext context) {
return Container();
}
}
AnimationController 需要計時器才能運作。如果您使用 SingleTickerProviderStateMixin 或其近親 TickerProviderStateMixin,您可以直接將其提供給 AnimationController,這樣就完成了。
AnimationController
AnimationController 是您通常用於播放、暫停、反轉和停止動畫的工具。與純粹的「滴答」事件不同的是,AnimationController 在任何時候都會告訴我們動畫的 進展 到達了哪一點。例如,我們是否已經走了一半?我們是否已經走到了 99%?我們是否已經完成動畫?
通常,您會使用 AnimationController,然後使用 Curve 進行轉換,再透過 Tween 處理,最後在其中一個方便的 Widget(如 FadeTransition 或 TweenAnimationBuilder)中使用它。但是,出於教學目的,我們不會這樣做。相反,我們將直接呼叫 setState。
setState
在我們初始化 AnimationController 之後,我們可以為它添加一個監聽器。在這個監聽器中,我們會呼叫 setState。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(_update);
}
void _update() {
setState(() {
// TODO
});
}
@override
Widget build(BuildContext context) {
return Container();
}
}
現在,我們可能應該有一個要設定的狀態。讓我們使用一個整數來保持簡單。而且不要忘記在我們的 build 方法中實際使用狀態,並根據控制器的當前值在我們的監聽器中更改狀態。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
AnimationController _controller;
int i = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(_update);
}
void _update() {
setState(() {
i = (_controller.value * 299792458).round();
});
}
@override
Widget build(BuildContext context) {
return Text('$i m/s');
}
}
此程式碼根據動畫的進度將值從零分配到光速。
執行動畫
現在,我們只需要告訴動畫它應該花多長時間完成,並啟動動畫。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
AnimationController _controller;
int i = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
_controller.addListener(_update);
_controller.forward();
}
void _update() {
setState(() {
i = (_controller.value * 299792458).round();
});
}
@override
Widget build(BuildContext context) {
return Text('$i m/s');
}
}
Widget 在被添加到螢幕上的那一刻就會開始動畫。它會在 1 秒內從零到光速「動畫」。
釋放控制器
哦,別忘了釋放 AnimationController。否則您的應用程式中就會有記憶體洩漏。
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin<MyWidget> {
AnimationController _controller;
int i = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
_controller.addListener(_update);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _update() {
setState(() {
i = (_controller.value * 299792458).round();
});
}
@override
Widget build(BuildContext context) {
return Text('$i m/s');
}
}
也許直接使用內建的 Widget?
正如您所見,自己做所有事情並不好。使用 TweenAnimationBuilder 可以用更少的程式碼行實現相同的功能,而且不需要同時管理 AnimationController 和呼叫 setState。
class MyPragmaticWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
tween: IntTween(begin: 0, end: 299792458),
duration: const Duration(seconds: 1),
builder: (BuildContext context, int i, Widget child) {
return Text('$i m/s');
},
);
}
}
總結
我們看到了計時器到底是什麼。我們看到了如何手動監聽 AnimationController。而且,我們看到了動畫在基本層面上只是快速連續地重新構建 Widget。您可以在任何幀上做任何您想做的事情。
本文是系列文章的一部分!請查看這裡的其他文章:
- Flutter 動畫基礎與隱式動畫
- Flutter 中的自訂隱式動畫…使用 TweenAnimationBuilder
- 使用內建顯式動畫的定向動畫
- 什麼時候應該使用 AnimatedBuilder 或 AnimatedWidget
- 動畫深探(本文!)