前言:
為應掘金的八月更文挑戰
,我準備在本月挑選 31
個以前沒有介紹過的元件,進行全面分析和屬性介紹。這些文章將來會作為 Flutter 元件集錄
的重要素材。希望可以堅持下去,你的支援將是我最大的動力~
- 1.【Flutter 元件集錄】NotificationListener| 8月更文挑戰
- 2.【Flutter 元件集錄】Dismissible| 8月更文挑戰
- 3.【Flutter 元件集錄】Switch 是怎樣煉成的| 8月更文挑戰
[本文]
一、 Switch 元件使用詳解
可能有人會覺得 Switch
元件非常簡單,有什麼好說的呢?其實 Switch
元件原始碼洋洋灑灑 近千行
,其中關於主題處理
、平臺適配
、事件處理
、動畫處理
、繪製處理
都有值得我們學習的地方。那麼廢話不多說,來一起看看 Switch
是怎麼煉成的吧。
1. Switch 最簡使用:value
與 onChanged
Switch
元件的使用中注意:該元件是 StatelessWidget
,表示本身並不維護 開關狀態
。這也就意味著,我把只能通過 重新構建 Switch元件
來切換 開關狀態
。在構建 Switch
時必須傳入 value
和 onChanged
兩個引數,其中 value
表示 Switch
開關的狀態,onChanged
是狀態變化回撥函式。
如下,在 _SwitchDemoState
中定義狀態 _value
用於表示 Switch
開關的狀態,在 _onChanged
回撥中改變狀態值,並 重新構建 Switch
元件,這樣就能達到點選進行開關
的效果。
class SwitchDemo extends StatefulWidget {
const SwitchDemo({Key? key}) : super(key: key);
@override
_SwitchDemoState createState() => _SwitchDemoState();
}
class _SwitchDemoState extends State<SwitchDemo> {
bool _value = false;
@override
Widget build(BuildContext context) {
return Switch(
value: _value,
onChanged: _onChanged,
);
}
void _onChanged(bool value) {
setState(() {
_value = value;
});
}
}
複製程式碼
其實這裡可能很讓人疑惑 Switch
為什麼不自己維護 開關狀態
,要將改狀態交由外界指定
呢?既然 Switch
是 StatelessWidget
,為什麼可以執行滑動的動畫?還有 onChanged
方法又是何時觸發的?帶著這些問題我們來逐漸去認識這個屬性而陌生的 Switch
元件。
2. Switch 的四個主要顏色
從 Switch
的構造方法中可以看出,其中定義了非常多的顏色相關屬性。
先看前四個顏色屬性:
inactiveThumbColor
代表關閉時圓圈
的顏色。inactiveTrackColor
代表關閉時滑槽
的顏色。
activeColor
代表開啟時圓圈
的顏色。inactiveTrackColor
代表開啟時滑槽
的顏色。
Switch(
activeColor: Colors.blue,
activeTrackColor: Colors.green,
inactiveThumbColor: Colors.orange,
inactiveTrackColor: Colors.pinkAccent,
value: _value,
onChanged: _onChanged,
);
複製程式碼
3. hoverColor 、 mouseCursor 和 splashRadius
前兩個屬性一般只能在桌面或web 端起作用,hoverColor
顧名思義是滑鼠懸浮時,外層的大圈顏色,splashRadius
表示大圈的半徑,如果不想要外圈的懸浮效果,可以將半徑設為 0 。另外, mouseCursor
代表滑鼠的樣式,比如下面的小拳頭是 SystemMouseCursors.grabbing
。
Switch(
activeColor: Colors.blue,
activeTrackColor: Colors.green,
inactiveThumbColor: Colors.orange,
inactiveTrackColor: Colors.pinkAccent,
hoverColor: Colors.blue.withOpacity(0.2),
mouseCursor: SystemMouseCursors.grabbing,
value: _value,
onChanged: _onChanged,
);
複製程式碼
mouseCursor
屬性的型別為 MouseCursor
,其中 SystemMouseCursors
中定義了非常多的滑鼠指標型別以供使用。下面給出幾個效果:
contextMenu | copy | forbidden | text |
---|---|---|---|
5. 指定圖片
通過 activeThumbImage
和 inactiveThumbImage
可以指定小圓中開啟/關閉
時的圖片。另外 onActiveThumbImageError
和 onInactiveThumbImageError
兩個回撥用於圖片載入錯誤的監聽。
當小圓同時指定 圖片
和 顏色
屬性時,會顯示 圖片
。
Switch(
activeColor: Colors.blue,
activeThumbImage: AssetImage('assets/images/icon_head.png'),
inactiveThumbImage: AssetImage('assets/images/icon_8.jpg'),
activeTrackColor: Colors.green,
inactiveThumbColor: Colors.orange,
inactiveTrackColor: Colors.pinkAccent,
hoverColor: Colors.blue.withOpacity(0.2),
mouseCursor: SystemMouseCursors.move,
splashRadius: 15,
value: _value,
onChanged: _onChanged,
);
複製程式碼
6.主題相關屬性: thumbColor 和 trackColor
一些具有互動性的 Material
元件會通過有 MaterialState
列舉定義互動行為,有如下 7
個元素。
enum MaterialState {
hovered,
focused,
pressed,
dragged,
selected,
disabled,
error,
}
複製程式碼
可以看出這兩個成員都是 MaterialStateProperty
型別,那這種型別的物件如何建立,又有什麼特點呢?
---->[Switch 成員宣告]----
final MaterialStateProperty<Color?>? thumbColor;
final MaterialStateProperty<Color?>? trackColor;
複製程式碼
簡單來說通過 MaterialStateProperty.resolveWith
方法,傳入一個函式返回對應泛型資料。如下回撥函式為 getThumbColor
,回撥引數為 Set<MaterialState>
。也僅僅說,會根據 MaterialState
集合,來返回泛型資料。從 thumbColor
屬性原始碼註釋中可以看出,Switch
有如下四種 MaterialState
。
在 getThumbColor
中根據 states
的情況,分別對幾種狀態返回不同顏色,這樣 Switch
在不同的狀態下,就會自動使用對應顏色。比如下面的 onChanged: null
代表 Switch
不可用,在 getThumbColor
中當為 disabled
,會返回紅色。
thumbColor
代表小圓顏色,trackColor
代表滑槽
顏色,使用方式是一樣的。這裡可能有人會問:有三個屬性可以設定小圓,那它們同時存在,優先順序怎麼樣?結果測試發現,inactiveThumbImage
會優先顯示,優先順序如下:
inactiveThumbImage > thumbColor > inactiveThumbColor > 預設 Switch 主題
複製程式碼
上面提到了 預設 Switch 主題
,這裡就來說一下 SwitchTheme
,它是一個 InheritedWidget
,維護 SwitchThemeData
型別資料,具體內容如下:
我們可以通過在上層巢狀 SwitchTheme
來為子樹中的 Switch
指定預設樣式,由於 MaterialApp
內部繼承了 SwitchTheme
元件,我們可以在 theme
中指定 Switch
的主題樣式。這樣在指定 Switch
的相關顏色屬性,就會使用預設的主題樣式:
7. Switch 的焦點: focusColor 與 autofocus
Switch
元件是擁有焦點的,焦點相關的處理被封裝在元件內部。focusColor
表示聚焦時的顏色,可被聚焦的元件有個特點:在桌面或 web 平臺中可以通過 Tab
鍵,切換焦點。如下是六個 Switch
通過 Tab
鍵切換焦點的效果:
@override
Widget build(BuildContext context) {
return
Wrap(
children: List.generate(6, (index) => Switch(
value: _value,
focusColor: Colors.blue.withOpacity(0.1),
onChanged: _onChanged,
))
);
}
複製程式碼
8. Switch 的尺寸相關: materialTapTargetSize
MaterialTapTargetSize
是一個列舉型別,有兩個元素。該屬性可以影響 Switch
的大小,如下分佈是 padded
和 shrinkWrap
的效果。通過除錯可知,預設是 padded
。下面在原始碼分析中會詳細介紹該屬性的作用。
enum MaterialTapTargetSize {
padded,
shrinkWrap,
}
複製程式碼
二、 挖掘 Switch 原始碼中的一些細節
1. 型別 _SwitchType
Switch
類中有一個 _SwitchType
型別成員,該成員完全被封裝在 Switch
內部,我們是無法直接操作的。 _SwitchType
是隻有兩個元素
的列舉類。
enum _SwitchType { material, adaptive }
---->[Switch 成員宣告]----
final _SwitchType _switchType;
複製程式碼
既然是成員變數,必然會在類內部被初始化,一般來說對 成員變數
初始化的地方在 構造方法
中。如下, Switch 的普通構造
中,會將 _switchType
設為 _SwitchType.material
。
一般來說,列舉物件就是為了分類處理,在 Switch#build
方法中,會根據 _switchType
的值進行不同的構建邏輯
,如果是 material
,則所有的平臺都使用Material
風格的 Switch
。 如果是 adaptive
會根據平臺的不同,使用不同的風格的 Switch
。在 android
、 fuchsia
、 linux
、 windows
中會使用 Material
風格;在 iOS
和 macOS
中會使用 Cupertino
風格。
到這裡,可能有人會問, _SwitchType
成員完全被封裝在 Switch
內部,那如何設定 adaptive
型別呢?仔細檢視原始碼可以看出 Switch
還有一個 adaptive
構造,此處會將 _switchType
設為 _SwitchType.adaptive
。
2. 兩種風格的 Switch 構建
_buildCupertinoSwitch
是當模式為 adaptive
時,用於構建 iOS
和 macOS
平臺 Switch
元件構建,可以看出其內部是通過 CupertinoSwitch
進行構建,效果如下:
_buildMaterialSwitch
用於構建 Material
風格的 Switch
元件構建,可見其內部通過 _MaterialSwitch
元件進行構建。到這裡我們就可以回答:既然 Switch
是 StatelessWidget
,為什麼可以執行滑動的動畫?因為 _MaterialSwitch
元件是 StatefulWidget
,它可以在內部改變元件狀態。
3.Switch 尺寸的確定
從上面可以看出,兩種風格的 Switch
都是通過 _getSwitchSize
獲取 Size
尺寸的。如下程式碼中,可以看出,尺寸是通過 MaterialTapTargetSize
物件控制的。如果未指定 materialTapTargetSize
則會通過主題獲取,除錯可以看出,主題中 materialTapTargetSize
預設是 padded
。
Size _getSwitchSize(ThemeData theme) {
final MaterialTapTargetSize effectiveMaterialTapTargetSize = materialTapTargetSize
?? theme.switchTheme.materialTapTargetSize
?? theme.materialTapTargetSize;
switch (effectiveMaterialTapTargetSize) {
case MaterialTapTargetSize.padded:
return const Size(_kSwitchWidth, _kSwitchHeight);
case MaterialTapTargetSize.shrinkWrap:
return const Size(_kSwitchWidth, _kSwitchHeightCollapsed);
}
}
複製程式碼
下面分別是 padded
和 shrinkWrap
的除錯資訊,可以很清楚地看出尺寸情況。
到這裡 Switch
元件的原始碼就已經面面俱到了,我們可以發現,它作為一個 StatelessWidget
並不能做太多的事,只是定義了很多屬性,並通過別的元件進行構建。也就是說,它本身起到平臺差異的統籌、封裝的作用,目的就是方便使用者使用。
4. onChanged 方法觸發的時機
通過除錯可以發現,onChanged 方法
的觸發是 ToggleableStateMixin#_handleTap
中觸發的。如下是 buildToggleable
的原始碼,可以看出其中通過 GestureDetector
監聽點選事件。
在 _MaterialSwitchState.build
方法中,可以看到其中通過 GestureDetector
監聽了水平拖拽事件,這也是為什麼 Switch
可以支援拖動的原因,同時 child
屬性是 buildToggleable
,也就是上面的元件,支援點選事件。這是一個很好的多事件監聽的案例。
5.動畫的建立與觸發
仔細看一下滑動的過程,可以看出其中有 位移動畫
和 透明度漸變動畫
。 首先來說一下動畫的來源:
這些動畫器都定義在 ToggleableStateMixin
中。而 _MaterialSwitchState
混入了 ToggleableStateMixin
。
和隱式動畫一樣, _MaterialSwitchState
中的動畫觸發也是通過重構元件,執行 didUpdateWidget
。如果你瞭解隱式動畫,就不難理解 Switch
的動畫觸發機制。
最後,繪製是通過 _SwitchPainter
畫出來的,這個畫板是比較複雜的,這裡就不展開了,有興趣的可以自己研究一下。
Switch 元件
的使用方式到這裡就完全介紹完畢,那本文到這裡就結束了,謝謝觀看,明天見~