前言:
這是我參與8月更文挑戰的第 6 天,活動詳情檢視:8月更文挑戰。為應掘金的八月更文挑戰
,我準備在本月挑選 31
個以前沒有介紹過的元件,進行全面分析和屬性介紹。這些文章將來會作為 Flutter 元件集錄
的重要素材。希望可以堅持下去,你的支援將是我最大的動力~
- 1.【Flutter 元件集錄】NotificationListener| 8月更文挑戰
- 2.【Flutter 元件集錄】Dismissible| 8月更文挑戰
- 3.【Flutter 元件集錄】Switch 是怎樣煉成的| 8月更文挑戰
- 4.【Flutter 元件集錄】Scrollbar| 8月更文挑戰
- 5.【Flutter 元件集錄】ClipPath| 8月更文挑戰
- 6.【Flutter 元件集錄】CupertinoActivityIndicator| 8月更文挑戰
[本文]
一、CupertinoActivityIndicator 的使用
可能看到 CupertinoActivityIndicator
元件,有人會嗤之以鼻:不就是個 iOS 風格的菊花轉
嗎,用起來這麼簡單的物件,有什麼好說的啊,看來你也要水文章了。 在我心目中 CupertinoActivityIndicator
是一個 教科書
級別的元件,它融匯了非常多元件相關的知識要點
,比如動畫
、繪製
、State 生命週期回撥的使用
,是非常值得去學習、分析、品味的。
1. CupertinoActivityIndicator 的屬性
CupertinoActivityIndicator
的使用確實非常簡單,普通構造中只有兩個引數:
屬性名 | 型別 | 預設值 | 用途 |
---|---|---|---|
animating | bool | true | 表示是否進行動畫 |
radius | double | 10 | 表示指示器半徑 |
如下是 CupertinoActivityIndicator
兩個屬性使用的小案例,左側半徑 15
,且animating
置為 true
,所以在不停旋轉,進行 loading
展示。右側半徑 20
,且animating
置為 false
,則表現為靜態。
class CupertinoActivityIndicatorDemo extends StatelessWidget {
const CupertinoActivityIndicatorDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 40,
children:[
CupertinoActivityIndicator(
animating: true,
radius: 15,
),
CupertinoActivityIndicator(
animating: false,
radius: 20,
),
]
);
}
}
複製程式碼
2.CupertinoActivityIndicator 的 partiallyRevealed 構造
除了普通構造外,還有一個 partiallyRevealed 構造
,從下面的定義中可以看出,屬性只有半徑 radius
和進度 progress
。而且 animating
固定為 false
,表示這個構造是指定進度的 靜態
效果。
下面是 progress
從 0 ~ 1
間隔 0.1
個效果:
class CupertinoActivityIndicatorDemo extends StatelessWidget {
const CupertinoActivityIndicatorDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 20,
children: List.generate(
10,
(index) => CupertinoActivityIndicator.partiallyRevealed(
progress: 0.1 * index,
radius: 15,
),
).toList());
}
}
複製程式碼
二、CupertinoActivityIndicator 原始碼知識點
1. CupertinoActivityIndicator 元件原始碼介紹
CupertinoActivityIndicator
繼承自 StatefulWidget
,表示它有內部狀態更新的需求。其中定義了三個成員,用於元件資訊配置,這三個屬性在上面的使用中也介紹了作用。作為一個 StatefulWidget
,其元件構建的邏輯將交由對應的狀態類進行,這裡是 _CupertinoActivityIndicatorState
。
從 _CupertinoActivityIndicatorState
的類結構中可以看出,元件的構建依賴於 SizedBox
和 CustomPaint
。並覆寫了三個 State
生命週期的回撥方法。
2. 動畫的處理
CupertinoActivityIndicator
既然可以進行 loading
旋轉,那必然需要進行動畫處理。如下, _CupertinoActivityIndicatorState
混入 SingleTickerProviderStateMixin
,在 initState
中例項化 AnimationController
,這裡可以看出當 widget.animating
為 true
,動畫器控制器會立刻 repeat
重複執行,週期為 1s
。
在 dispose
回撥中將 _controller
釋放。
@override
void dispose() {
_controller.dispose();
super.dispose();
}
複製程式碼
3. didUpdateWidget 回撥
可能很多人不是很清楚這個回撥的作用。當元件重建
時,狀態類
是不會重新初始化
的,而是會回撥 didUpdateWidget
來對比新舊兩個 Widget
的配置資訊進行響應邏輯處理。明面上使 元件重建
的方式非常多,比如 setState
、ValueListenableBuilder
、FutrueBuilder
等,本質上基本都是 setState
。
@override
void didUpdateWidget(CupertinoActivityIndicator oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.animating != oldWidget.animating) {
if (widget.animating)
_controller.repeat();
else
_controller.stop();
}
}
複製程式碼
如果不處理 didUpdateWidget
會有什麼後果?比如通過 Switch
來開關 CupertinoActivityIndicator
的 animating
屬性, CupertinoActivityIndicator
重建時,如果沒有 didUpdateWidget
處理,狀態類是無法感知 widget 配置資訊變化的,也就無法完成是否動畫的切換。
4. 繪製的處理
在 build
方法中,使用 SizedBox
元件進行尺寸的限定,通過 _CupertinoActivityIndicatorPainter
進行繪製。
在很久以前,對於那時還只會 setState
觸發畫板重繪,我一直對這種方式有疑問,因為 setState
更新畫板會讓畫板物件重新建立,這對於繪製動畫來說是很不友好的,因為觸發的頻率非常高。直到我看懂 CupertinoActivityIndicator
的原始碼,才對畫板重繪有了全新的認知。這也為 《Flutter 繪製指南 - 妙筆生花》掃清了最後障礙。
都是看到 CupertinoActivityIndicator
並沒有使用 setState
,卻可以執行動畫來更新內部狀態,這是讓人很興奮的。經過一點點測試發現祕密在於 super(repaint: position)
。畫板可以通過一個 Listenable
物件觸發重繪,而不會觸發任何元件的構建。至於其更深層的實現原理,在 《Flutter 繪製探索》專欄中有詳細的原始碼分析。
具體的繪製邏輯也很簡單,就是遍歷旋轉繪製
圓角矩形而已。
4. CupertinoActivityIndicator 的顏色
從原始碼中可以看出 CupertinoActivityIndicator
的顏色是固定的,使用者無法直接設定。但在 暗/亮
模式下,顏色會有差異,如下:
對於 activeColor
會根據 暗/亮
模式進行處理。如下,在暗色模式下,會略顯白色。如果我們想要自己定義的元件支援 暗/亮
模式,也可以效仿一下,進行處理。
三、CupertinoActivityIndicator 的注意點
有一個注意點。比如,我通過 Wrap
包裹 CupertinoActivityIndicator
和另一個 CustomPaint
,通過 BoxPainter
畫一個方塊。
class CupertinoActivityIndicatorDemo extends StatelessWidget {
const CupertinoActivityIndicatorDemo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 20,
children: [
CupertinoActivityIndicator(
animating: true,
radius: 15,
),
CustomPaint(
size: Size(50, 50),
painter: BoxPainter(),
)
],
);
}
}
class BoxPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
print('-------BoxPainter----------');
canvas.drawRect(Offset.zero & size, Paint());
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
複製程式碼
通過日誌可以發現 BoxPainter
會隨著 CupertinoActivityIndicator
的動畫進行重繪。也能有人會非常疑惑,明明 BoxPainter
不需要重繪,為什麼會一直繪製, CupertinoActivityIndicator
太垃圾了。
這也算不上什麼異常,本質就是 RepaintBoundary
機制,通過 debugDumpRenderTree()
方法檢視渲染樹,可以看出:這兩者在同一渲染區域內,如下它們都在 up7
。在同一片渲染區域內的一個節點重繪,會連帶這片區域的所有渲染節點重繪。像 Wrap
、Column
、Row
、SingleChildScrollView
、Stack
這樣可以有多個子元件,對應的渲染物件會在同一層。
我們可以通過 RepaintBoundary
,將 CupertinoActivityIndicator
對應的渲染物件隔開,這樣就不會影響其他節點。注意,這並不是 CupertinoActivityIndicator
自身的問題,是 RepaintBoundary
機制使然。
Wrap(
spacing: 20,
children: [
RepaintBoundary( //<----
child: CupertinoActivityIndicator(
animating: false,
radius: 15,
),
),
CustomPaint(
size: Size(50, 50),
painter: BoxPainter(),
),
],
),
複製程式碼
CupertinoActivityIndicator 元件
的使用方式到這裡就介紹完畢,雖然是個簡單的小元件,但麻雀雖小五臟俱全。是很值得去研究和學習的。那本文到這裡就結束了,謝謝觀看,明天見~