前言
本篇記錄了 Flutter 動畫機制的介紹以及有別其他 UI 針對動畫所發展出的一套狀態管理機制。最後也會探討到頁面路由的各項管理機制的差別。
Animation In Flutter
第一個動畫
- 讓箱子的兩邊可以拍打
- 停止拍打時, 貓咪垂直往上跑出
動畫的各種Widget
Animation
AnimationController
Tween
AnimatedBuilder
是四個不同的組件分別執行不同的功能:
AnimatedBuilder
- 最常被用到的 widget
- 需要一個
Animation
widget 實體和一個builder function
- 當每一次動畫發生改變時就調用
builder function
來更新在我們裝置上的 widget - 該 widget 非常類似
StreamBuilder
Animation
- 記錄”要動畫”的屬性的當前值
- 記錄動畫的狀態(正在跑, 已停止, 等等)
AnimationController
- 主要負責則控制動畫的狀態 (開始, 暫停, 重新…)
- 記錄動畫要執行多久 duration of animation
- 通知
Animation
的值需要改變 (TickerProvider)
Tween - beTween
- 描述動畫的值所會跨越的範圍
Why StatefulWidget For Animation ?
為什麼用StatefulWidget而不是用Bloc來管理widget的狀態和數據呢?
Bloc
就之前的學習經驗上我們可以得知他的用途 :- 讓不同的 widget 共享相同的資訊
- 集體管理各個 widget 所需用到的數據和狀態以及業務邏輯使其與介面分離
Animation
當然可以使用Bloc
來操作但為啥沒有 :- 動畫基本上他所需要的狀態和數據並不會影響其 widget
Bloc
需要大量的模板程式碼會過於複雜, 對於不需要使用到它上述功能的 widget 來說反而更不方便StatefulWidget
在操作動畫上會更為靈活和彈性好多 (他只在乎當下該 widget 的 state)
重新渲染的盲點
動畫
- 用
State
來儲存不想隨著 widget 更新而丟棄的數據. 在整個 widget 生命中期間持續存在被使用著 - 不用
setState()
來重新渲染 widget , 而是用AnimatedBuilder
來重新渲染畫面
- 用
一般使用
- 用
Bloc
來儲存各個 widget 所可能交互使用的數據, 以StreamController
的概念加以維護控制 - 因為不用
State
來做, 所以 widget 都用StatelessWidget
就行了. 至於重新渲染方面就交給StreamBuilder
來達成
- 用
結論
- 重新渲染方面都交由 XxBuilder 來完成就行了
Animation Development Details
動畫實例開發:
1 | import 'package:flutter/material.dart'; |
AnimatedBuilder
兩個問題的探討 :
對
XxBuilder
的概念是什麼? - 就 StreamBuilder 經驗來看- 監控隨時會改變的東西
Stream/Animation
- 一有改變就呼叫
builder function
, 裡面返回新的要重新渲染的 Widget
- 監控隨時會改變的東西
為什麼需要
child
屬性??child
負責持有重新渲染會造成很大負擔的 widget 的參考- 在調用
builder function
時將child
參考傳入參數. - 在
builder function
去更新重建不昂貴的 widget
AnimationController
- vsync: this 的意義?? - 動畫運行的機制
StreamController
設定完動畫的持續時間並不會無中生有的去驅動提醒動畫要改變數值或是要更新了vsync: this
在這裡的用途就是讓當前已經mixinTickerProviderStateMixin
的 widget 擁有TickerProvider
, 再用this
傳入AnimationController
.TickerProvider
會提供Ticker
, 可以想像成一個 鉤子hook, 他用來提供該 Widget(這裡是Home widget)以外的世界,一個可以進入該 widget 來通知需要更新 frame 的管道
Tween & Animation
Tween
: 可以看成是製作動畫的前置作業- 設定動畫數值的範圍 -
begin
&end
- 設定動畫數值的範圍 -
Aniamtion
: 描述動畫的主體- 從
begin
—–>end
變動的比率有多快
- 從
Flutter Animation 的小小觀察
- 從頭至尾, 所有的動畫程式碼都圍繞在處理數值變動
- Flutter 動畫並不關心也不知道動畫最後會應用到哪一個 Widget
- 動畫並不會去綁定特定 Widget , 反之亦然.
- 動畫和 widget 沒有任何關係
API Performing Strategy
Fetching data
Repository
- 當作 App 與資料來源中間的媒介
- UI 不能也不需要跟資料來源溝通
Architecture of the App
Testing with dart
Testing 的用途
如果檔案後面沒有後綴
_test
測試時會找不到每一個 test file 都有一個
main()
都是獨立的程式
方便進行單元測試, 不用啟動整個 app
對於網路的測試
有兩個理由為什麼不直接真實的 http request :
- 做單元測試本來就是耗時的工作, 如果加上網路的延遲會造成極大的負擔
- 真實 API 的異動是很大的. 往往會造成測試的不方便
解法 :
- 使用
http
套件裡面的testing.dart
, 裡面有模仿 http request 的功能
Offline Data Storage In Flutter
init
- 該方法用來對 DB 進行初始會以及連結的設定
- 通常初始化都在建構子
- 跟 DB 有關的都是
異步 asynchronous
- 建構子不能有異步
fetchItem
query
返回的類型一定是List<Map<String, dynamic>>
Refactor Repository By Abstract Class
重構概述
利用abstract class
來重構現有App的架構
為什麼要用抽象類別在 Dart 中
- 抽象出不同類之間共同的特性變成另一個類
- 增加程式碼的重用率
Navigation In Flutter
Navigation 概述
- 主要負責決定要在裝置的螢幕上顯示甚麼頁面
route 就是 screen
route 是由 Navigator 所控制的
Navigator 提供管理 route 物件堆疊的方法
當
new
一個MaterialApp
時,Navigation
就被實例化供我們使用了
設定初始頁面
設定初始頁面的方式
home
屬性: 物件(Widget)1
2
3
4
5
6
7
8void main() {
runApp(MaterialApp(
home: MyHomePage(),
.
.
.
));
}- 不一定要設定
routes
和onGenerateRoute
屬性(通常會需要,因為隨著 App 複雜性的增高,統一管理頁面是比較恰當的) ; 反之,initialRoute
就必須要設定
- 不一定要設定
initialRoute
屬性: 路徑1
2
3
4
5
6
7
8
9
10
11
12void main() {
runApp(MaterialApp(
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (context) => HomePage(),
'/second': (context) => SecondHome()
}
.
.
.
));
}- 如果使用
initialRoute
就必須要設定routes
或是onGenerateRoute
屬性 Flutter
預設路徑是 **’/‘**,因此如果是用預設路徑可以不需要加initialRoute: '/'
1
2
3
4
5
6
7
8
9
10
11void main() {
runApp(MaterialApp(
routes: <String, WidgetBuilder>{
'/': (context) => HomePage(),
'/second': (context) => SecondHome()
}
.
.
.
));
}- 也可以不用預設路徑自己命名
- 如果使用
事實上,home
和 initialRiute
是一樣的,不過是一些設定上的差異而已
MaterialApp會依序檢查三件事情
有沒有一個 Widget 被指派到
home
屬性有沒有一個 map 被指派到
routes
屬性- Routes Table : 用 map 來記錄相對應的頁面
Routes Name PageBuilder That Produces / NewsList /details NewsDetail - 優點 : 很輕易的可以作業面的轉換
- 缺點 : 頁面間的資訊供通傳送會變得很複雜
- Routes Table : 用 map 來記錄相對應的頁面
是否有
onGenerateRoute
callback function- 將 callback function 傳到
MaterialApp
- 決定哪一個頁面要被顯示
- 將 callback function 傳到
routes
和 onGenerateRoute callback
的不同
使用home屬性
home
屬性變成 Navigator
堆疊底部的 route
1 | void main() { |
為了加入新的 route
(頁面) 在 stack 上面, 必須要先創建 MaterialPageRoute
實體. 它含有 builder function 可以 build 出畫面在 screen 上面.
1 | Navigator.push(context, new MaterialPageRoute<void>( |