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('https://example.com/avatar.jpg'), ), ) 在目標頁面上設置與源頁面相同 tag 的 Hero Widget: Scaffold( body: Center( child: Hero( tag: 'avatar', // 與源頁面相同的 tag child: CircleAvatar( backgroundImage: NetworkImage('https://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 來加載網絡圖片。