# 動畫
在任何系統的UI框架中,動畫實現的原理都是相同的,即:在一段時間內,快速地多次改變UI外觀,由于人眼會產生視覺暫留,最終看到的就是一個“連續”的動畫,這和電影的原理是一樣的,而UI的一次改變稱為一個動畫幀,對應一次屏幕刷新,而決定動畫流暢度的一個重要指標就是幀率FPS(Frame Per Second),指每秒的動畫幀數。很明顯,幀率越高則動畫就會越流暢。一般情況下,對于人眼來說,動畫幀率超過16FPS,就比較流暢了,超過32FPS就會非常的細膩平滑,而超過32FPS基本就感受不到差別了。由于動畫的每一幀都是要改變UI輸出,所以在一個時間段內連續的改變UI輸出是比較耗資源的,對設備的軟硬件系統要求都較高,所以在UI系統中,動畫的平均幀率是重要的性能指標,而在Flutter中,理想情況下是可以實現60FPS的,這和原生應用動畫基本是持平的。
### Flutter中動畫抽象
為了方便開發者創建動畫,不同的UI系統對動畫都進行了一些抽象,比如在Android中可以通過XML來描述一個動畫然后設置給View。Flutter中也對動畫進行了抽象,主要涉及Tween、Animation、Curve、Controller這些角色。
### Animation
Animation對象本身和UI渲染沒有任何關系。Animation是一個抽象類,它用于保存動畫的插值和狀態;其中一個比較常用的Animation類是Animation。Animation對象是一個在一段時間內依次生成一個區間(Tween)之間值的類。Animation對象的輸出值可以是線性的、曲線的、一個步進函數或者任何其他曲線函數。 根據Animation對象的控制方式,動畫可以反向運行,甚至可以在中間切換方向。Animation還可以生成除double之外的其他類型值,如:Animation\\ 或 Animation\\。可以通過Animation對象的`value`屬性獲取動畫的當前值。
#### 動畫通知
我們可以通過Animation來監聽動畫的幀和狀態變化:
1. `addListener()`可以給Animation添加幀監聽器,在每一幀都會被調用。幀監聽器中最常見的行為是改變狀態后調用setState()來觸發UI重建。
2. `addStatusListener()`可以給Animation添加“動畫狀態改變”監聽器;動畫開始、結束、正向或反向(見AnimationStatus定義)時會調用StatusListener。
在后面的章節中我們將會舉例說明。
### Curve
動畫過程可以是勻速的、加速的或者先加速后減速等。Flutter中通過Curve(曲線)來描述動畫過程,Curve可以是線性的(Curves.linear),也可以是非線性的。
CurvedAnimation 將動畫過程定義為一個非線性曲線.
```
final CurvedAnimation curve =
new CurvedAnimation(parent: controller, curve: Curves.easeIn);
```
**注:** [Curves](https://docs.flutter.io/flutter/animation/Curves-class.html) 類類定義了許多常用的曲線,也可以創建自己的,例如:
```
class ShakeCurve extends Curve {
@override
double transform(double t) {
return math.sin(t * math.PI * 2);
}
}
```
CurvedAnimation和AnimationController(下面介紹)都是Animation類型。CurvedAnimation可以通過包裝AnimationController和Curve生成一個新的動畫對象 。
### AnimationController
AnimationController用于控制動畫,它包含動畫的啟動`forward()`、停止`stop()` 、反向播放 `reverse()`等方法。AnimationController會在動畫的每一幀,就會生成一個新的值。默認情況下,AnimationController在給定的時間段內線性的生成從0.0到1.0(默認區間)的數字。 例如,下面代碼創建一個Animation對象,但不會啟動它運行:
```
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
```
AnimationController生成數字的區間可以通過`lowerBound`和`upperBound`來指定,如:
```
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 2000),
lowerBound: 10.0,
upperBound: 20.0,
vsync: this
);
```
AnimationController派生自Animation,因此可以在需要Animation對象的任何地方使用。 但是,AnimationController具有控制動畫的其他方法,例如`forward()`方法可以啟動動畫。數字的產生與屏幕刷新有關,因此每秒鐘通常會產生60個數字(即60fps),在動畫的每一幀,生成新的數字后,每個Animation對象會調用其Listener對象回調,等動畫狀態發生改變時(如動畫結束)會調用StatusListeners監聽器。
duration表示動畫執行的時長,通過它我們可以控制動畫的速度。
> **注意**: 在某些情況下,動畫值可能會超出AnimationController的0.0-1.0的范圍。例如,`fling()`函數允許您提供速度(velocity)、力量(force)等,因此可以在0.0到1.0范圍之外。 CurvedAnimation生成的值也可以超出0.0到1.0的范圍。根據選擇的曲線,CurvedAnimation的輸出可以具有比輸入更大的范圍。例如,Curves.elasticIn等彈性曲線會生成大于或小于默認范圍的值。
#### Ticker
當創建一個AnimationController時,需要傳遞一個`vsync`參數,它接收一個TickerProvider類型的對象,它的主要職責是創建Ticker,定義如下:
```
abstract class TickerProvider {
//通過一個回調創建一個Ticker
Ticker createTicker(TickerCallback onTick);
}
```
Flutter應用在啟動時都會綁定一個SchedulerBinding,通過SchedulerBinding可以給每一次屏幕刷新添加回調,而Ticker就是通過SchedulerBinding來添加屏幕刷新回調,這樣一來,每次屏幕刷新都會調用TickerCallback。使用Ticker(而不是Timer)來驅動動畫會防止屏幕外動畫(動畫的UI不在當前屏幕時,如鎖屏時)消耗不必要的資源,因為Flutter中屏幕刷新時會通知到綁定的SchedulerBinding,而Ticker是受SchedulerBinding驅動的,由于鎖屏后屏幕會停止刷新,所以Ticker就不會再觸發。
通過將SingleTickerProviderStateMixin添加到State的定義中,然后將State對象作為`vsync`的值,這在后面的例子中可以見到。
### Tween
默認情況下,AnimationController對象值的范圍是0.0到1.0。如果我們需要不同的范圍或不同的數據類型,則可以使用Tween來配置動畫以生成不同的范圍或數據類型的值。例如,像下面示例,Tween生成從-200.0到0.0的值:
```
final Tween doubleTween = new Tween<double>(begin: -200.0, end: 0.0);
```
Tween構造函數需要`begin`和`end`兩個參數。Tween的唯一職責就是定義從輸入范圍到輸出范圍的映射。輸入范圍通常為0.0到1.0,但這不是必須的,我們可以自定義需要的范圍。
Tween繼承自Animatable,而不是繼承自Animation。Animatable與Animation相似,不是必須輸出double值。例如,ColorTween指定兩種顏色之間的過渡。
```
final Tween colorTween =
new ColorTween(begin: Colors.transparent, end: Colors.black54);
```
Tween對象不存儲任何狀態,相反,它提供了`evaluate(Animation<double> animation)`方法,它可以獲取動畫當前值。 Animation對象的當前值可以通過`value()`方法取到。`evaluate`函數還執行一些其它處理,例如分別確保在動畫值為0.0和1.0時返回開始和結束狀態。
#### Tween.animate
要使用Tween對象,需要調用其`animate()`方法,然后傳入一個控制器對象。例如,以下代碼在500毫秒內生成從0到255的整數值。
```
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(controller);
```
注意`animate()`返回的是一個Animation,而不是一個Animatable。
以下示例構建了一個控制器、一條曲線和一個Tween:
```
final AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 500), vsync: this);
final Animation curve =
new CurvedAnimation(parent: controller, curve: Curves.easeOut);
Animation<int> alpha = new IntTween(begin: 0, end: 255).animate(curve);
```
- 緣起
- 起步
- 移動開發技術簡介
- Flutter簡介
- 搭建Flutter開發環境
- 常見配置問題
- Dart語言簡介
- 第一個Flutter應用
- 計數器示例
- 路由管理
- 包管理
- 資源管理
- 調試Flutter APP
- Dart線程模型及異常捕獲
- 基礎Widgets
- Widget簡介
- 文本、字體樣式
- 按鈕
- 圖片和Icon
- 單選框和復選框
- 輸入框和表單
- 布局類Widgets
- 布局類Widgets簡介
- 線性布局Row、Column
- 彈性布局Flex
- 流式布局Wrap、Flow
- 層疊布局Stack、Positioned
- 容器類Widgets
- Padding
- 布局限制類容器ConstrainedBox、SizeBox
- 裝飾容器DecoratedBox
- 變換Transform
- Container容器
- Scaffold、TabBar、底部導航
- 可滾動Widgets
- 可滾動Widgets簡介
- SingleChildScrollView
- ListView
- GridView
- CustomScrollView
- 滾動監聽及控制ScrollController
- 功能型Widgets
- 導航返回攔截-WillPopScope
- 數據共享-InheritedWidget
- 主題-Theme
- 事件處理與通知
- 原始指針事件處理
- 手勢識別
- 全局事件總線
- 通知Notification
- 動畫
- Flutter動畫簡介
- 動畫結構
- 自定義路由過渡動畫
- Hero動畫
- 交錯動畫
- 自定義Widget
- 自定義Widget方法簡介
- 通過組合現有Widget實現
- 實例:TurnBox
- CustomPaint與Canvas
- 實例:圓形漸變進度條(自繪)
- 文件操作與網絡請求
- 文件操作
- Http請求-HttpClient
- Http請求-Dio package
- 實例:Http分塊下載
- WebSocket
- 使用Socket API
- Json轉Model
- 包與插件
- 開發package
- 插件開發:平臺通道簡介
- 插件開發:實現Android端API
- 插件開發:實現IOS端API
- 系統能力調用
- 國際化
- 讓App支持多語言
- 實現Localizations
- 使用Intl包
- Flutter核心原理
- Flutter UI系統
- Element和BuildContext
- RenderObject與RenderBox
- Flutter從啟動到顯示