0%

【文章翻譯】Animation deep dive

【文章內容使用 Gemini 1.5 Pro 自動產生】

動畫深探

去年我錄製了 Flutter 動畫系列中的其中一集,我想我會將相同的內容發佈給那些喜歡文字勝於視頻的人。

在這個系列的其他集中,我的同事們討論了在 Flutter 中建立動畫的所有實際方法。在我的那一集裡不是這樣。在這裡,您將學習如何以最不務實的方式實現動畫。(但同時您也會學到一些東西。)

讓我們從簡單輕鬆的東西開始:

什麼是運動?

你看,運動是一種錯覺。看看這個:

Filip 揮手的视频。

這是一個謊言。您實際上看到的是許多靜止的圖片以快速連續的方式顯示。這就是電影運作的方式。在電影中,這些單獨的圖片稱為幀,因為數位螢幕的運作方式類似,所以這裡也稱為幀。電影通常每秒顯示 24 幀。現代數位設備每秒顯示 60 到 120 幀。

因此,如果運動是謊言,那麼所有這些 AnimationFoo 和 FooTransition Widgets 到底在做什麼?當然,因為幀需要每秒構建高達 120 次,所以 UI 不能每次都重新構建。

還是可以?

事實上,Flutter 中的動畫只不過是在每一幀上重新構建 Widget 樹的一部分的方法。沒有特殊情況。Flutter 足夠快可以做到。

讓我們看看 Flutter 動畫的基礎構建塊之一:AnimatedBuilder。此 Widget 是一個 AnimatedWidget,它由 _AnimatedState 支援。在 State 的 initState() 方法中,我們正在監聽動畫(或者,如這裡所稱的 Listenable),當它更改其值時,我們會… 呼叫 setState()。

這段令人困惑的螢幕錄影只是顯示,我在前一段中所說的是真的。Animated Builder 確實會在每一幀上呼叫 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 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。

http://creativecommons.org/licenses/by/4.0/