Flutter 動畫實現指南

語言: CN / TW / HK

前言

動畫是提升使用者體驗的一個重要方式,一個恰當元件的動畫或頁面切換的動畫可以緩解因等待資料載入的情緒問題,也能增加使用者的好感。

Animation、AnimationController與Listener

示例

在 Flutter 中,動畫(Animation)是指一種隨著時間推移逐步改變的視覺效果。為了控制動畫的變化和執行,我們需要用到三個重要的類:Animation、AnimationController 和 Listener。

  1. Animation:Animation 是一個抽象類,它定義了動畫的執行過程和動畫的值如何計算。具體的動畫效果可以通過繼承 Animation 類來實現。例如,Flutter 中自帶的一些動畫類包括 Tween、Curve、Interval 等。Animation 知道當前動畫的狀態(比如,動畫是否開始、停止、前進或者後退,以及動畫的當前值),但卻不知道這些狀態究竟應用在哪個元件物件上。Animation 僅僅是用來提供動畫資料,而不負責動畫的渲染
  2. AnimationController:AnimationController 用於控制動畫的開始、結束和狀態的管理。它可以設定動畫的時間、速度、是否重複等等。當我們建立一個 AnimationController 物件時,需要指定動畫的時間長度和 TickerProvider 物件。TickerProvider 負責提供時鐘訊號,以便 AnimationController 知道何時更新動畫。
  3. Listener:Listener 是用來監聽動畫變化的。它接收一個 Animation 物件,並在每次動畫值改變時被呼叫。Listener 可以用於更新介面上的元素,以反映當前動畫的狀態。

我們來舉一個具體的例子:實現一個旋轉動畫。

首先,我們需要建立一個 AnimationController 物件來管理動畫的執行過程和狀態。我們可以設定動畫的時間長度、是否重複、速度等引數。

AnimationController _controller = AnimationController( duration: Duration(seconds: 2), vsync: this, // TickerProvider 物件 ); 然後,我們需要建立一個 Animation 物件來定義動畫的值如何計算。在本例中,我們可以使用一個 Tween 類來實現旋轉動畫。 Animation<double> _animation = Tween<double>( begin: 0, end: 1, ).animate(_controller); 接著,我們需要建立一個 Listener 來監聽動畫的變化,並根據當前動畫的值來更新介面上的元素。在本例中,我們可以通過設定一個 Transform.rotate 來實現元素的旋轉動畫。 Listener( child: Transform.rotate( angle: _animation.value * 2 * pi, child: Container( width: 100, height: 100, color: Colors.blue, ), ), onPointerDown: (event) { _controller.repeat(); }, ) 最後在頁面銷燬的時候,記得釋放資源。 @override void dispose() { controller.dispose(); // 釋放資源 super.dispose(); }

在上面的程式碼中,我們將 Transform.rotate 放在 Listener 中,並將 Animation 的值乘以 2π,以實現元素的旋轉動畫效果。當用戶點選介面上的元素時,我們通過 _controller.repeat() 來觸發動畫的重複播放。

注意點

在使用 Animation、AnimationController 和 Listener 時,需要注意以下幾點:

  1. AnimationController 的 dispose 方法需要在 Widget 生命週期結束時呼叫,以便釋放資源。通常可以在 State 物件的 dispose 方法中呼叫 dispose 方法,例如在 StatefulWidget 中重寫 dispose 方法,並在 dispose 方法中呼叫 AnimationController 的 dispose 方法。
  2. AnimationController 的 forward、reverse、reset 等方法需要謹慎使用。如果在呼叫這些方法時沒有正確處理動畫的狀態,可能會導致動畫播放不正確,例如動畫重複播放、動畫播放速度過快等問題。
  3. 在使用 Listener 監聽動畫時,需要注意監聽的動畫值是否正確。通常可以在監聽器中列印動畫值,以便檢查動畫值是否正確。
  4. 使用 AnimationController 的 animateTo、animateBy 等方法時,需要注意設定動畫的持續時間、曲線等引數,以控制動畫的播放效果。
  5. 在使用 Animation 時,需要注意設定動畫的值範圍。如果動畫的值範圍超過了實際需要的範圍,可能會導致動畫播放不正確,例如動畫值超過了 1.0 或小於了 0.0 等問題。

AnimatedWidget 與 AnimatedBuilder

示例

1、AnimatedWidget 是一個抽象類,它繼承自 StatefulWidget,並且包含一個 Animation 物件。通過繼承 AnimatedWidget 類並重寫 build 方法,我們可以方便地根據動畫的值來更新介面上的元素。AnimatedWidget 會自動處理動畫的監聽和介面更新,無需手動新增 Listener

例如,我們可以建立一個繼承 AnimatedWidget 的類來實現旋轉動畫: ``` class RotateWidget extends AnimatedWidget { RotateWidget({ Key key, Animation animation, }) : super(key: key, listenable: animation);

@override Widget build(BuildContext context) { final Animation animation = listenable; return Transform.rotate( angle: animation.value * 2 * pi, child: Container( width: 100, height: 100, color: Colors.blue, ), ); } } ``` 在上面的程式碼中,我們建立了一個 RotateWidget 類,繼承自 AnimatedWidget,它包含一個 Animation 型別的引數 animation,用於定義旋轉動畫。在 build 方法中,我們直接根據 animation 的值來更新介面上的元素,無需手動新增 Listener。

2、AnimatedBuilder 是另一個常用的動畫類,它通過 builder 方法來建立一個 Widget,並接收一個 Animation 物件作為引數。在 builder 方法中,我們可以根據動畫的值來建立和更新 Widget。與 AnimatedWidget 不同的是,AnimatedBuilder 可以自定義 Widget 的構建過程,更加靈活。

例如,我們可以使用 AnimatedBuilder 來實現一個縮放動畫: AnimatedBuilder( animation: _controller, builder: (BuildContext context, Widget child) { return Transform.scale( scale: _controller.value, child: Container( width: 100, height: 100, color: Colors.blue, ), ); }, ) 在上面的程式碼中,我們建立了一個 AnimatedBuilder,它接收一個 AnimationController 物件作為 animation 引數,並使用 builder 方法來構建一個縮放動畫。在 builder 方法中,我們根據 _controller.value 的值來更新元素的縮放比例。

注意點

在使用 AnimatedWidget 和 AnimatedBuilder 時,需要注意以下幾點:

  1. AnimatedWidget 和 AnimatedBuilder 的作用相似,都可以用於構建動畫。但是,它們的使用方式略有不同。AnimatedWidget 是一個 StatelessWidget,通過監聽 Animation 的值來自動更新 Widget 的狀態,因此使用 AnimatedWidget 可以讓程式碼更加簡潔。而 AnimatedBuilder 則是一個 Widget Builder,需要手動在 builder 方法中建立需要更新的 Widget。
  2. 在建立 AnimationController 時,需要將 AnimationController 的 vsync 引數設定為 TickerProvider,以便在 Widget 狀態改變時同步更新動畫。通常,可以使用 State 物件來作為 TickerProvider,例如使用 StatefulWidget 建立一個 State 物件,並將其作為 vsync 引數傳遞給 AnimationController。
  3. AnimatedWidget 和 AnimatedBuilder 中的 Animation 需要設定監聽器,以便在動畫值改變時更新 Widget 狀態。在監聽器中,通常需要呼叫 setState 方法,以便告訴 Flutter 框架更新 Widget 狀態。
  4. 在使用 AnimatedWidget 和 AnimatedBuilder 時,需要注意記憶體的使用。如果動畫比較複雜或者需要持續執行,可能會消耗大量記憶體。因此,應該儘量避免在 Widget 樹中建立過多的 AnimatedWidget 或 AnimatedBuilder。
  5. 當使用 AnimatedBuilder 時,需要注意 Widget 的建立次數。由於 AnimatedBuilder 是一個 Widget Builder,每次動畫值發生變化時都會呼叫 builder 方法,因此如果 builder 方法中建立的 Widget 比較複雜,可能會導致 Widget 的建立次數過多,影響效能。因此,應該儘可能保持 builder 方法中建立的 Widget 簡單,以提高效能。

hero動畫

示例

Hero 動畫是 Flutter 中一種常用的過渡動畫,它可以實現在兩個不同頁面之間平滑的元素過渡動畫。Hero 動畫通常用於展示從一個頁面到另一個頁面的過渡效果,例如在瀏覽商品列表頁面和商品詳情頁面之間的過渡動畫效果。

使用 Hero 動畫需要兩個步驟:在源頁面上設定 Hero Widget 和 tag,然後在目標頁面上設定與源頁面相同 tag 的 Hero Widget。Flutter 框架會自動根據 tag 匹配源頁面和目標頁面的 Hero Widget,並在過渡時自動執行動畫。

下面是一個使用 Hero 動畫的例子:

在源頁面上設定 Hero Widget 和 tag: Hero( tag: 'avatar', // 定義 Hero Widget 的 tag child: CircleAvatar( backgroundImage: NetworkImage('http://example.com/avatar.jpg'), ), ) 在目標頁面上設定與源頁面相同 tag 的 Hero Widget: Scaffold( body: Center( child: Hero( tag: 'avatar', // 與源頁面相同的 tag child: CircleAvatar( backgroundImage: NetworkImage('http://example.com/avatar.jpg'), ), ), ), ) 在上面的程式碼中,我們在源頁面上建立了一個 Hero Widget,並設定了 tag 為 'avatar'。在目標頁面中,我們也建立了一個與源頁面相同 tag 的 Hero Widget,並在 Scaffold 中使用它來實現元素的過渡動畫。

注意點

在使用 Hero 動畫時,需要注意以下幾點: 1. Hero Widget 的 tag 必須在源頁面和目標頁面中保持一致。如果 tag 不一致,Flutter 無法匹配源頁面和目標頁面的 Hero Widget,從而無法執行過渡動畫。 1. Hero Widget 的內容應該儘可能相似。如果兩個 Hero Widget 的內容差異過大,可能會導致過渡動畫效果不佳。例如,在源頁面中使用的是一個圓形的頭像,而在目標頁面中使用的是一個方形的頭像,這會導致在過渡時頭像的形狀會發生變化。 1. Hero Widget 可以包含子 Widget。如果源頁面和目標頁面的 Hero Widget 中包含子 Widget,那麼這些子 Widget 的佈局和位置也應該儘可能相似,以確保過渡動畫效果平滑。 1. Hero Widget 應該儘可能放置在頁面層級較高的位置。如果 Hero Widget 被覆蓋在其他 Widget 之下,可能會導致過渡動畫效果不佳。因此,通常將 Hero Widget 放置在頁面的最上層,以確保過渡動畫效果正常。 1. 如果 Hero Widget 中包含的是網路圖片等資源,建議提前載入這些資源,以免在過渡時出現載入延遲的情況。可以使用 Flutter 中提供的 Image.network 或者 CachedNetworkImage 等 Widget 來載入網路圖片。