Flutter筆記:滑塊及其實現分析1

Flutter筆記
滑塊分析1

作者李俊才 (jcLee95):https://blog.csdn.net/qq_28550263
郵箱 :291148484@163.com
本文地址:https://blog.csdn.net/qq_28550263/article/details/134900784


本文從設計角度,考慮滑塊組件的使用場景,實現一個滑塊組件應該包含的功能,介紹 Flutter 中滑塊組件的用法,并分析 Slider 的實現源碼。


1. 概述

1.1 滑塊組件的使用場景

Flutter 中,Slider 組件是一個非常靈活和強大的工具,它可以在各種不同的應用場景中幫助用戶選擇和調整值。

在實際的應用開發中,Slider 組件有著廣泛的應用場景。例如,在音頻或視頻播放的應用中,Slider 組件常常被用于調整音量或者播放進度。用戶可以通過拖動滑塊來增加或減少音量,或者跳轉到視頻的特定位置。在這種情況下,Slider 的最小值通常設置為0(表示無聲或視頻開始),最大值設置為100(表示最大音量或視頻結束)。

另外,在一些圖形設計或繪圖應用中,Slider組件可以用于選擇顏色的不同陰影。用戶可以通過拖動滑塊來選擇從純黑到純白的不同灰度。在這種情況下,Slider的最小值和最大值可以表示顏色的最深和最淺陰影。

再如,在一些日歷或時間管理應用中,Slider組件可以用于選擇時間或日期。例如,用戶可以通過拖動滑塊來選擇一天中的特定時間或一年中的特定日期。在這種情況下,Slider 的 最小值(min) 和 最大值(max) 可以表示一天的開始和結束,或一年的開始和結束。

1.2 一個滑塊組件應該具備的功能

這一小節是寫給像自己定義滑塊組件的讀者的——假設自己設計一個Slider組件,它可以具備什么樣的功能呢?歸納起來,可以考慮實現下面這些方面的功能:

序號功能描述
1.值選擇Slider 組件允許用戶通過拖動滑塊在 最小值(min)和 最大值(max)之間選擇一個值。當前選中的值由 value 屬性表示。
2.離散和連續值通過 divisions 屬性,Slider 可以支持連續和離散的值。如果divisions為null,Slider 支持連續的值;如果 divisions 不為nullSlider 支持離散的值。
3.顏色定制Slider 的顏色可以通過 activeColorinactiveColorsecondaryActiveColorthumbColoroverlayColor 進行定制。
4.交互方式通過 allowedInteraction 屬性,可以定義用戶與 Slider 的交互方式。
5.焦點管理通過 autofocusfocusNodeSlider 可以管理焦點。如果autofocustrueSlider 將在初始化時獲取焦點。
6.鼠標光標通過 mouseCursor 屬性,可以定義鼠標指針在懸停或進入 Slider 時的光標樣式。
7.事件回調通過 onChangedonChangeStartonChangeEndSlider 可以在用戶拖動滑塊選擇新值時觸發事件。
8.標簽顯示通過 label 屬性,Slider 可以在滑塊處于活動狀態時顯示標簽。
9.次要軌道值通過 secondaryTrackValueSlider 可以顯示次要軌道。

2. Slider組件

這一小節介紹 Flutter 內置的滑塊 Slider 組件的用法。這個組件有兩個構造函數,分別是 SliderSlider.adaptive,它們都用來創建滑塊,但是在行為上有一些不同:

  • Slider 構造函數創建的是一個 Material Design 滑塊,它在所有平臺上的外觀和行為都是一致的;
  • Slider.adaptive 構造函數創建的滑塊會根據當前平臺自適應其外觀和行為。例如,當運行在 iOS 平臺時,它會盡可能地模仿 iOS 風格的滑塊。這使得你的應用可以更好地融入到用戶的設備和操作系統中,提供更自然的用戶體驗。

2.1 通過Slider構造函數創建滑塊

const Slider(Key? key,required double value, // 當前選中的值double? secondaryTrackValue, // 次要軌道值required ValueChanged<double>? onChanged, // 當用戶通過拖動選擇新值時調用ValueChanged<double>? onChangeStart, // 當用戶開始選擇新值時調用ValueChanged<double>? onChangeEnd, // 當用戶完成選擇新值時調用double min = 0.0, // 用戶可以選擇的最小值double max = 1.0, // 用戶可以選擇的最大值int? divisions, // 離散劃分的數量String? label, // 當滑塊處于活動狀態并滿足SliderThemeData.showValueIndicator時,顯示在滑塊上方的標簽Color? activeColor, // 活動部分的顏色Color? inactiveColor, // 非活動部分的顏色Color? secondaryActiveColor, // 滑塊軌道上thumb和Slider.secondaryTrackValue之間部分的顏色Color? thumbColor, // thumb的顏色MaterialStateProperty<Color?>? overlayColor, // 通常用于指示滑塊thumb被聚焦、懸停或拖動的高亮顏色MouseCursor? mouseCursor, // 鼠標指針進入或懸停在小部件上時的光標SemanticFormatterCallback? semanticFormatterCallback, // 用于從滑塊值創建語義值的回調FocusNode? focusNode, // 用作此小部件的焦點節點的可選焦點節點bool autofocus = false, // 如果此小部件將被選為初始焦點,則為TrueSliderInteraction? allowedInteraction // 用戶與滑塊交互的允許方式
)

例如:

Slider(value: _currentValue,min: 0,max: 100,divisions: 5,label: '$_currentValue',onChanged: (double value) {setState(() {_currentValue = value;});},onChangeStart: (double startValue) {print('Started change at $startValue');},onChangeEnd: (double endValue) {print('Ended change at $endValue');},activeColor: Colors.blue,inactiveColor: Colors.grey,thumbColor: Colors.red,overlayColor: MaterialStateProperty.all(Colors.green),
)

在這個例子中,我們創建了一個Slider,它的值范圍從0到100,分為5個離散的部分。當用戶拖動滑塊改變值時,onChanged回調會被調用,我們在這個回調中更新_currentValue的值并刷新界面。同時,我們也定義了onChangeStart和onChangeEnd回調來在開始和結束拖動時打印消息。最后,我們通過activeColor、inactiveColor、thumbColor和overlayColor屬性來自定義滑塊的顏色。

2.2 通過Slider.adaptive構造函數創建滑塊

const Slider.adaptive(Key? key,required double value, // 當前選中的值double? secondaryTrackValue, // 次要軌道值required ValueChanged<double>? onChanged, // 當用戶通過拖動選擇新值時調用ValueChanged<double>? onChangeStart, // 當用戶開始選擇新值時調用ValueChanged<double>? onChangeEnd, // 當用戶完成選擇新值時調用double min = 0.0, // 用戶可以選擇的最小值double max = 1.0, // 用戶可以選擇的最大值int? divisions, // 離散劃分的數量String? label, // 當滑塊處于活動狀態并滿足SliderThemeData.showValueIndicator時,顯示在滑塊上方的標簽MouseCursor? mouseCursor, // 鼠標指針進入或懸停在小部件上時的光標Color? activeColor, // 活動部分的顏色Color? inactiveColor, // 非活動部分的顏色Color? secondaryActiveColor, // 滑塊軌道上thumb和Slider.secondaryTrackValue之間部分的顏色Color? thumbColor, // thumb的顏色MaterialStateProperty<Color?>? overlayColor, // 通常用于指示滑塊thumb被聚焦、懸停或拖動的高亮顏色SemanticFormatterCallback? semanticFormatterCallback, // 用于從滑塊值創建語義值的回調FocusNode? focusNode, // 用作此小部件的焦點節點的可選焦點節點bool autofocus = false, // 如果此小部件將被選為初始焦點,則為TrueSliderInteraction? allowedInteraction // 用戶與滑塊交互的允許方式
)

以下是一個使用Slider.adaptive構造函數創建滑塊的例子:

Slider.adaptive(value: _currentValue,min: 0,max: 100,divisions: 5,label: '$_currentValue',onChanged: (double value) {setState(() {_currentValue = value;});},onChangeStart: (double startValue) {print('Started change at $startValue');},onChangeEnd: (double endValue) {print('Ended change at $endValue');},activeColor: Colors.blue,inactiveColor: Colors.grey,thumbColor: Colors.red,overlayColor: MaterialStateProperty.all(Colors.green),
)

在這個例子中,我們使用了 Slider.adaptive 構造函數,它會根據當前平臺創建一個自適應的滑塊。其他的用法和 Slider 構造函數基本一致。我們設置了滑塊的 值范圍離散劃分的數量標簽 以及各種顏色。同時,我們也定義了onChangedonChangeStartonChangeEnd回調來響應用戶的操作。

2.3 離散值功能:divisions屬性

divisions 屬性用于將 Slider 的整個值范圍劃分為等間隔的離散值。

例如,如果 min0.0max10.0divisions5,那么Slider可以取的值就是 0.0, 2.0, 4.0, 6.0, 8.0, 10.0。如果 divisionsnull,那么 Slider 可以取任意值。

例如:

class _MyHomePageState extends State<MyHomePage> {double _currentValue = 0;Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('divisions屬性例子'),),body: Center(child: Slider(value: _currentValue,min: 0,max: 10,divisions: 5,onChanged: (double value) {setState(() {_currentValue = value;});},),),);}
}

在這里插入圖片描述

在這個例子中,我們創建了一個 Slider,它的值范圍從 010,分為 5 個離散的部分。這意味著用戶只能選擇 0.0, 2.0, 4.0, 6.0, 8.0, 10.0 這些值,不能選擇這些值之間的值。當用戶拖動滑塊改變值時,onChanged 回調會被調用,我們在這個回調中更新 _currentValue 的值并刷新界面。

2.4 顏色定制功能

Slider 組件提供了多個屬性來定制滑塊的顏色:

  • activeColor:活動部分的顏色,即滑塊左側(或右側,取決于語言和方向)的軌道顏色。

  • inactiveColor:非活動部分的顏色,即滑塊右側(或左側,取決于語言和方向)的軌道顏色。

  • secondaryActiveColor:滑塊軌道上 thumbSlider.secondaryTrackValue 之間部分的顏色。

  • thumbColor:滑塊 thumb 的顏色。

  • overlayColor:通常用于指示滑塊 thumb 被 聚焦、懸停 或 拖動 的高亮顏色,它是一個 MaterialStateProperty<Color?> 類型的屬性。

例如:

Slider(value: _currentValue,min: 0,max: 100,divisions: 5,label: _currentValue.round().toString(),onChanged: (double value) {setState(() {_currentValue = value;});},activeColor: Colors.blue,inactiveColor: Colors.grey,thumbColor: Colors.red,overlayColor: MaterialStateProperty.all(Colors.yellow),
)

在這里插入圖片描述

在這個例子中,并通過 activeColorinactiveColorthumbColoroverlayColor 屬性來定制滑塊的顏色。

當用戶拖動滑塊時,滑塊左側的軌道顏色為藍色,滑塊右側的軌道顏色為灰色,滑塊 thumb 的顏色為紅色,滑塊 thumb 被聚焦、懸停或拖動時的高亮顏色為黃色。

2.5 焦點管理:autofocus 和 focusNode 屬性

2.4.1 FocusNode 對象

在 Flutter 中,焦點管理是通過 FocusNode 對象來實現的。FocusNode 對象表示用戶界面中的一個可以獲得鍵盤輸入焦點的元素。

2.4.2 autofocusfocusNode 屬性

Slider 組件提供了 autofocusfocusNode 兩個屬性來管理焦點。

  • autofocus 屬性是一個布爾值,如果為 true,則 Slider 將在初始化時自動獲取焦點。

  • focusNode 屬性是一個 FocusNode 對象,你可以通過它來控制 Slider 的焦點狀態。例如,你可以調用 FocusNode 的 requestFocus 和 unfocus 方法來手動讓 Slider 獲得或失去焦點。

例如:

FocusNode _focusNode = FocusNode();Slider(value: _currentValue,min: 0,max: 100,divisions: 5,label: _currentValue.round().toString(),onChanged: (double value) {setState(() {_currentValue = value;});},autofocus: true,focusNode: _focusNode,
)

在這個例子中,我們通過 autofocus 屬性讓它在初始化時自動獲取焦點。我們還創建了一個 FocusNode 對象,并通過 focusNode 屬性將它關聯到 Slider。這樣,我們就可以通過 _focusNode 來手動控制 Slider 的焦點狀態。例如,我們可以在某個按鈕的點擊事件中調用 _focusNode.requestFocus 來讓 Slider 獲得焦點,或調用 _focusNode.unfocus 來讓 Slider 失去焦點。

2.6 交互方式:allowedInteraction 屬性

allowedInteraction屬性用于定義用戶與滑塊的交互方式。這個屬性的類型是SliderInteraction,它是一個枚舉類型,包含以下幾個值:

  • SliderInteraction.all:允許所有交互,包括 拖動點擊使用鍵盤
  • SliderInteraction.drag:只允許拖動 滑塊
  • SliderInteraction.none 允許任何交互。

例如:

Slider(value: _currentValue,min: 0,max: 100,divisions: 5,label: _currentValue.round().toString(),onChanged: (double value) {setState(() {_currentValue = value;});},allowedInteraction: SliderInteraction.drag,
)

其中,我們創建了一個 Slider ,它的值范圍從 0100,分為 5 個離散的部分。

我們使用 allowedInteraction 屬性來限制用戶只能通過拖動滑塊來改變值,不能通過點擊或使用鍵盤。當用戶拖動滑塊改變值時,onChanged 回調會被調用,我們在這個回調中更新 _currentValue 的值并刷新界面。這樣,用戶在拖動滑塊時,就可以看到滑塊上方顯示的當前值。

2.7 標簽功能:label 屬性

label 屬性用于在滑塊處于 活動狀態 并滿足SliderThemeData.showValueIndicator 時,顯示在滑塊上方的標簽。這個標簽通常用于顯示當前滑塊的值,幫助用戶更準確地選擇值。

例如:

Slider(value: _currentValue,min: 0,max: 100,divisions: 5,label: _currentValue.round().toString(),onChanged: (double value) {setState(() {_currentValue = value;});},
)

在這個例子中,我們創建了一個Slider,它的值范圍從 0100,分為 5 個離散的部分。

我們使用 label 屬性來顯示當前滑塊的值,這個值是 _currentValue 四舍五入后的整數。

當用戶拖動滑塊改變值時,onChanged 回調會被調用,我們在這個回調中更新 _currentValue 的值并刷新界面。

這樣,用戶在拖動滑塊時,就可以看到滑塊上方顯示的當前值。

2.8 次要軌道值:secondaryTrackValue 屬性

Slider 組件中,所謂 次要軌道 是指滑塊軌道上的一個可選部分,它表示一個次要的值。這個次要的值由 secondaryTrackValue 屬性來設置。這個功能可以用于表示一些特殊的場景,例如在一個音頻播放器中,value 可以表示當前的播放位置,而 secondaryTrackValue 可以表示緩沖的位置。

secondaryTrackValue 屬性的值必須在 minmax 之間。當設置了 secondaryTrackValue,滑塊軌道上會顯示兩個活動部分:

  • 一個是從 minvalue
  • 另一個是從 valuesecondaryTrackValue

這兩個活動部分的顏色可以通過 activeColorsecondaryActiveColor 來分別設置。

例如:

import 'package:flutter/material.dart';class SliderDemo extends StatefulWidget {const SliderDemo({Key? key}) : super(key: key);State<SliderDemo> createState() => _SliderDemoState();
}class _SliderDemoState extends State<SliderDemo> {// 定義一個狀態變量,表示滑塊的當前值double _currentValue = 50;Widget build(BuildContext context) {return Center(// 創建 Slider 組件child: Slider(// 設置滑塊的當前值value: _currentValue,// 設置滑塊的最小值和最大值min: 0,max: 100,// 設置滑塊的離散劃分的數量divisions: 5,// 設置滑塊的標簽,顯示當前值label: _currentValue.round().toString(),// 當用戶拖動滑塊選擇新值時,更新 _currentValue 的值并刷新界面onChanged: (double value) {setState(() {_currentValue = value;});},// 設置滑塊的顏色activeColor: Colors.blue,inactiveColor: Colors.grey,thumbColor: Colors.red,overlayColor: MaterialStateProperty.all(Colors.green),),);}
}

在這里插入圖片描述

在這個例子中,我們通過 activeColorinactiveColorthumbColoroverlayColor 屬性來定制滑塊的顏色。當用戶拖動滑塊時,滑塊左側的軌道顏色為藍色,滑塊右側的軌道顏色為灰色,滑塊 thumb 的顏色為紅色,滑塊 thumb 被聚焦、懸停或拖動時的高亮顏色為綠色。

2.9 關于事件回調

上面小節中實際上已經用到的各個事件回調,這里簡單補充說明一下。Slider 組件提供了三個事件回調:

  1. onChanged:當用戶通過拖動選擇新值時調用。它的參數是新選擇的值;
  2. onChangeStart:當用戶開始選擇新值時調用。它的參數是開始選擇時的值;
  3. onChangeEnd:當用戶完成選擇新值時調用。它的參數是完成選擇后的值。

3. Slider組件源碼分析

【注】:不考慮 Cupertino 風格的實現(實際上內部通過CupertinoSlider轉換),完整分析一個滑塊組件的實現,在Flutter源代碼中也有兩千多行。這部分內容涉及廣泛,將在后續逐漸補充。

3.1 Slider類

很顯然,一個滑塊中是必然存在狀態的,因此需要從 StatefulWidget 得到一個 Slider 類,用于實現相關的滑塊構造函數,用作外部使用的接口,這也就是前文介紹過的兩個構造函數: SliderSlider.adaptive,即:

const Slider({super.key,required this.value,this.secondaryTrackValue,required this.onChanged,this.onChangeStart,this.onChangeEnd,this.min = 0.0,this.max = 1.0,this.divisions,this.label,this.activeColor,this.inactiveColor,this.secondaryActiveColor,this.thumbColor,this.overlayColor,this.mouseCursor,this.semanticFormatterCallback,this.focusNode,this.autofocus = false,this.allowedInteraction,
}) : _sliderType = _SliderType.material,assert(min <= max),assert(value >= min && value <= max,'Value $value is not between minimum $min and maximum $max'),assert(secondaryTrackValue == null || (secondaryTrackValue >= min && secondaryTrackValue <= max),'SecondaryValue $secondaryTrackValue is not between $min and $max'),assert(divisions == null || divisions > 0);
const Slider.adaptive({super.key,required this.value,this.secondaryTrackValue,required this.onChanged,this.onChangeStart,this.onChangeEnd,this.min = 0.0,this.max = 1.0,this.divisions,this.label,this.mouseCursor,this.activeColor,this.inactiveColor,this.secondaryActiveColor,this.thumbColor,this.overlayColor,this.semanticFormatterCallback,this.focusNode,this.autofocus = false,this.allowedInteraction,
}) : _sliderType = _SliderType.adaptive,assert(min <= max),assert(value >= min && value <= max,'Value $value is not between minimum $min and maximum $max'),assert(secondaryTrackValue == null || (secondaryTrackValue >= min && secondaryTrackValue <= max),'SecondaryValue $secondaryTrackValue is not between $min and $max'),assert(divisions == null || divisions > 0);

具體的功能當然通過 createState 放在對應的 _SliderState 類中實現的:

3.1 _SliderState類

TickerProviderStateMixin

TickerProviderStateMixin 是一個 Flutter 混入(Mixin),它可以為其混入的類提供 Ticker 對象。Ticker 是一個計時器,它可以每秒觸發多次回調,用于驅動基于時間的動畫。

【注:小知識】在 Flutter 中,許多動畫相關的類,如 AnimationController,需要一個 TickerProvider 來創建自己的 Ticker
當你將 TickerProviderStateMixin 混入到一個 State 對象中,這個 State 對象就可以作為 TickerProvider,用于創建 Ticker

推薦參考我的另一篇博客《Flutter筆記:Ticker及其應用》

class _SliderState extends State<Slider> with TickerProviderStateMixin

可以看到,_SliderState 混入了 TickerProviderStateMixin,這意味著 _SliderState 可以提供 Ticker 對象。這對于 Slider 組件來說非常重要,因為 Slider 組件可能需要驅動一些基于時間的動畫,例如當用戶拖動滑塊時,滑塊的位置需要根據時間平滑地變化。

_SliderState類的靜態屬性(表)

屬性名類型描述
enableAnimationDurationDuration啟用/禁用滑塊時的動畫持續時間
valueIndicatorAnimationDurationDuration顯示/隱藏值指示器時的動畫持續時間
_traditionalNavShortcutMapMap<ShortcutActivator, Intent>傳統導航快捷鍵映射
_directionalNavShortcutMapMap<ShortcutActivator, Intent>方向導航快捷鍵映射
非靜態屬性

_SliderState類的屬性(表)

屬性名類型描述
overlayControllerAnimationController控制 overlay 顯示的動畫
valueIndicatorControllerAnimationController控制值指示器顯示的動畫
enableControllerAnimationController控制滑塊啟用/禁用的動畫
positionControllerAnimationController控制滑塊位置的動畫
interactionTimerTimer?用于延遲隱藏 overlay 和值指示器的計時器
_renderObjectKeyGlobalKey用于獲取滑塊的 RenderObject
_actionMapMap<Type, Action>動作映射
_enabledbool滑塊是否啟用
paintValueIndicatorPaintValueIndicator?用于繪制值指示器的回調
_draggingbool用戶是否正在拖動滑塊
_focusNodeFocusNode?管理滑塊焦點的 FocusNode 對象
_focusedbool滑塊是否獲得焦點
_hoveringbool鼠標指針是否在滑塊上

_SliderState類的方法(表)

方法名參數返回類型描述
initState初始化狀態,創建動畫控制器和焦點節點
dispose清理資源,取消計時器,銷毀動畫控制器和焦點節點
_handleChangeddouble value處理滑塊值改變的事件
_handleDragStartdouble value處理開始拖動滑塊的事件
_handleDragEnddouble value處理結束拖動滑塊的事件
_actionHandler_AdjustSliderIntent intent處理滑塊的動作
_handleFocusHighlightChangedbool focused處理焦點高亮改變的事件
_handleHoverChangedbool hovering處理鼠標懸停狀態改變的事件
_lerpdouble valuedouble將值從 [0, 1] 映射到 [min, max]
_discretizedouble valuedouble將連續的值離散化
_convertdouble valuedouble將值從 [min, max] 映射到 [0, 1],并可能離散化
_unlerpdouble valuedouble將值從 [min, max] 映射到 [0, 1]
buildBuildContext contextWidget構建滑塊的 widget
_buildMaterialSliderBuildContext contextWidget構建 Material 風格的滑塊
_buildCupertinoSliderBuildContext contextWidget構建 Cupertino 風格的滑塊
showValueIndicator顯示值指示器

狀態初始化分析

既然Slider是有狀態組件,有狀態組件的狀態是通過State類的 initState 實現的,因此可以分析_SliderState的initState方法。這部分源代碼如下:


void initState() {super.initState();// 創建一個控制 overlay 顯示的動畫控制器overlayController = AnimationController(duration: kRadialReactionDuration,vsync: this,);// 創建一個控制值指示器顯示的動畫控制器valueIndicatorController = AnimationController(duration: valueIndicatorAnimationDuration,vsync: this,);// 創建一個控制滑塊啟用/禁用的動畫控制器enableController = AnimationController(duration: enableAnimationDuration,vsync: this,);// 創建一個控制滑塊位置的動畫控制器positionController = AnimationController(duration: Duration.zero,vsync: this,);// 如果滑塊啟用,則將 enableController 的值設置為 1.0,否則設置為 0.0enableController.value = widget.onChanged != null ? 1.0 : 0.0;// 將滑塊的值轉換為 [0, 1] 范圍內的值,并設置給 positionControllerpositionController.value = _convert(widget.value);// 創建動作映射_actionMap = <Type, Action<Intent>>{_AdjustSliderIntent: CallbackAction<_AdjustSliderIntent>(onInvoke: _actionHandler,),};// 如果 widget 沒有提供 focusNode,則創建一個新的 focusNodeif (widget.focusNode == null) {_focusNode ??= FocusNode();}
}

在這個方法中,首先調用 super.initState() 來確保父類的初始化邏輯被執行(慣例,沒啥說的)。

  • 然后,創建四個 AnimationController 對象,它們分別用于控制 overlay 的顯示、值指示器的顯示、滑塊的啟用/禁用以及滑塊位置的動畫。每個 AnimationController 都需要一個持續時間和一個 vsync 參數,vsync 參數通常設置為 this,表示當前的 _SliderState 對象將作為 TickerProvider。

  • 接著,設置 enableController 和 positionController 的初始值。如果 widget.onChanged 不為 null,則 enableController 的值設置為 1.0,表示滑塊是啟用的;否則,設置為 0.0,表示滑塊是禁用的。positionController 的值設置為 widget.value 對應的位置。

  • 然后,初始化 _actionMap,它是一個映射,將 _AdjustSliderIntent 映射到一個 CallbackAction,這個 CallbackAction 的回調函數是 _actionHandler。

  • 最后,如果 widget.focusNode 為 null,則創建一個新的 FocusNode 對象。這個 FocusNode 對象用于管理滑塊的焦點。如果 widget.focusNode 不為 null,則使用 widget.focusNode。

銷毀組件分析

接著 initStatedispose 也算是思維常態,畢竟創建了相關的資源就需要銷毀以防止內存泄漏。對應的是有狀態組件狀態類的 dispose 方法。這里具體說來就是 _SliderStatedispose 方法。其源代碼為:


void dispose() {// 取消交互計時器interactionTimer?.cancel();// 銷毀 overlay 的動畫控制器overlayController.dispose();// 銷毀值指示器的動畫控制器valueIndicatorController.dispose();// 銷毀啟用/禁用滑塊的動畫控制器enableController.dispose();// 銷毀滑塊位置的動畫控制器positionController.dispose();// 移除 overlayEntryoverlayEntry?.remove();// 銷毀 overlayEntryoverlayEntry?.dispose();// 將 overlayEntry 設置為 nulloverlayEntry = null;// 銷毀焦點節點_focusNode?.dispose();// 調用父類的 dispose 方法super.dispose();
}

沒有什么太多可以說的,這段代碼主要完成了資源的清理工作。包括取消交互計時器,銷毀動畫控制器,移除和銷毀 overlayEntry,以及銷毀焦點節點。這些操作都是為了避免內存泄漏,當滑塊不再需要時,應該調用這個方法來釋放資源。

build 方法分析

不論是有狀態組件還是無狀態組件都是通過 build 組件實現其 UI,對于一個有狀態組件,build 方法在其狀態類中。在 滑塊 組件中,具體說來就是 _SliderStatebuild 方法。其代碼如下:


Widget build(BuildContext context) {// 確保當前的 context 中有 Material 組件assert(debugCheckHasMaterial(context));// 確保當前的 context 中有 MediaQuery 組件assert(debugCheckHasMediaQuery(context));// 根據滑塊的類型來構建不同的滑塊switch (widget._sliderType) {// 如果滑塊的類型是 Material,則構建 Material 風格的滑塊case _SliderType.material:return _buildMaterialSlider(context);// 如果滑塊的類型是 Adaptive,則根據平臺來構建不同風格的滑塊case _SliderType.adaptive: {final ThemeData theme = Theme.of(context);switch (theme.platform) {// 如果平臺是 Android、Fuchsia、Linux 或 Windows,則構建 Material 風格的滑塊case TargetPlatform.android:case TargetPlatform.fuchsia:case TargetPlatform.linux:case TargetPlatform.windows:return _buildMaterialSlider(context);// 如果平臺是 iOS 或 macOS,則構建 Cupertino 風格的滑塊case TargetPlatform.iOS:case TargetPlatform.macOS:return _buildCupertinoSlider(context);}}}
}

前面兩個 assert 基本就是寫組件的模板套路,不是我們討論的主要功能。我們的關注焦點在下面的 switch 塊中。

很清楚,可以看到:在 _SliderState 類的 build 方法中,根據滑塊的類型和當前的平臺來構建不同風格的滑塊。如果滑塊的類型是 Material,則無論在什么平臺上都構建 Material 風格的滑塊;如果滑塊的類型是 Adaptive,則在 AndroidFuchsiaLinuxWindows 平臺上構建 Material 風格的滑塊,在 iOSmacOS 平臺上構建 Cupertino 風格的滑塊。

風格構建分析

在分析 build 方法時,我們注意到依據平臺不同,分別具體交給 _buildMaterialSlider 方法 和 _buildCupertinoSlider 方法實現具體的 build。

_buildMaterialSlider方法

該方法用于,其源代碼為:

Widget _buildMaterialSlider(BuildContext context) {// 獲取當前主題final ThemeData theme = Theme.of(context);// 獲取當前滑塊主題SliderThemeData sliderTheme = SliderTheme.of(context);// 獲取默認的滑塊主題final SliderThemeData defaults = theme.useMaterial3 ? _SliderDefaultsM3(context) : _SliderDefaultsM2(context);// 如果 widget 有 active 或 inactive 顏色指定,我們盡可能地將它們插入到滑塊主題中。// 如果開發者需要更多的控制,那么他們需要使用 SliderTheme。默認的顏色來自 ThemeData.colorScheme。// 這些顏色,以及默認的形狀和文本樣式都符合 Material 指南。// 定義默認的軌道形狀、刻度標記形狀、覆蓋形狀、拇指形狀、值指示器形狀、顯示值指示器和允許的交互const SliderTrackShape defaultTrackShape = RoundedRectSliderTrackShape();const SliderTickMarkShape defaultTickMarkShape = RoundSliderTickMarkShape();const SliderComponentShape defaultOverlayShape = RoundSliderOverlayShape();const SliderComponentShape defaultThumbShape = RoundSliderThumbShape();final SliderComponentShape defaultValueIndicatorShape = defaults.valueIndicatorShape!;const ShowValueIndicator defaultShowValueIndicator = ShowValueIndicator.onlyForDiscrete;const SliderInteraction defaultAllowedInteraction = SliderInteraction.tapAndSlide;// 定義滑塊的狀態final Set<MaterialState> states = <MaterialState>{if (!_enabled) MaterialState.disabled,if (_hovering) MaterialState.hovered,if (_focused) MaterialState.focused,if (_dragging) MaterialState.dragged,};// 值指示器的顏色與拇指和活動軌道(可以由 activeColor 定義)不同,// 如果使用 RectangularSliderValueIndicatorShape。在所有其他情況下,值指示器被認為與活動顏色相同。final SliderComponentShape valueIndicatorShape = sliderTheme.valueIndicatorShape ?? defaultValueIndicatorShape;final Color valueIndicatorColor;if (valueIndicatorShape is RectangularSliderValueIndicatorShape) {valueIndicatorColor = sliderTheme.valueIndicatorColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(0.60), theme.colorScheme.surface.withOpacity(0.90));} else {valueIndicatorColor = widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary;}// 定義有效的覆蓋顏色Color? effectiveOverlayColor() {return widget.overlayColor?.resolve(states)?? widget.activeColor?.withOpacity(0.12)?? MaterialStateProperty.resolveAs<Color?>(sliderTheme.overlayColor, states)?? MaterialStateProperty.resolveAs<Color?>(defaults.overlayColor, states);}// 使用 widget 的屬性和默認值來更新滑塊主題sliderTheme = sliderTheme.copyWith(trackHeight: sliderTheme.trackHeight ?? defaults.trackHeight,activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? defaults.activeTrackColor,inactiveTrackColor: widget.inactiveColor ?? sliderTheme.inactiveTrackColor ?? defaults.inactiveTrackColor,secondaryActiveTrackColor: widget.secondaryActiveColor ?? sliderTheme.secondaryActiveTrackColor ?? defaults.secondaryActiveTrackColor,disabledActiveTrackColor: sliderTheme.disabledActiveTrackColor ?? defaults.disabledActiveTrackColor,disabledInactiveTrackColor: sliderTheme.disabledInactiveTrackColor ?? defaults.disabledInactiveTrackColor,disabledSecondaryActiveTrackColor: sliderTheme.disabledSecondaryActiveTrackColor ?? defaults.disabledSecondaryActiveTrackColor,activeTickMarkColor: widget.inactiveColor ?? sliderTheme.activeTickMarkColor ?? defaults.activeTickMarkColor,inactiveTickMarkColor: widget.activeColor ?? sliderTheme.inactiveTickMarkColor ?? defaults.inactiveTickMarkColor,disabledActiveTickMarkColor: sliderTheme.disabledActiveTickMarkColor ?? defaults.disabledActiveTickMarkColor,disabledInactiveTickMarkColor: sliderTheme.disabledInactiveTickMarkColor ?? defaults.disabledInactiveTickMarkColor,thumbColor: widget.thumbColor ?? widget.activeColor ?? sliderTheme.thumbColor ?? defaults.thumbColor,disabledThumbColor: sliderTheme.disabledThumbColor ?? defaults.disabledThumbColor,overlayColor: effectiveOverlayColor(),valueIndicatorColor: valueIndicatorColor,trackShape: sliderTheme.trackShape ?? defaultTrackShape,tickMarkShape: sliderTheme.tickMarkShape ?? defaultTickMarkShape,thumbShape: sliderTheme.thumbShape ?? defaultThumbShape,overlayShape: sliderTheme.overlayShape ?? defaultOverlayShape,valueIndicatorShape: valueIndicatorShape,showValueIndicator: sliderTheme.showValueIndicator ?? defaultShowValueIndicator,valueIndicatorTextStyle: sliderTheme.valueIndicatorTextStyle ?? defaults.valueIndicatorTextStyle,);// 解析有效的鼠標光標final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, states)?? sliderTheme.mouseCursor?.resolve(states)?? MaterialStateMouseCursor.clickable.resolve(states);// 解析有效的允許交互final SliderInteraction effectiveAllowedInteraction = widget.allowedInteraction?? sliderTheme.allowedInteraction?? defaultAllowedInteraction;// 這個大小用作值指示器的繪制的最大邊界// 必須與 range_slider.dart 中同名函數保持同步。Size screenSize() => MediaQuery.sizeOf(context);// 定義獲取輔助功能焦點的回調VoidCallback? handleDidGainAccessibilityFocus;switch (theme.platform) {case TargetPlatform.android:case TargetPlatform.fuchsia:case TargetPlatform.iOS:case TargetPlatform.linux:case TargetPlatform.macOS:break;case TargetPlatform.windows:handleDidGainAccessibilityFocus = () {// 當滑塊獲得輔助功能焦點時,自動激活滑塊。if (!focusNode.hasFocus && focusNode.canRequestFocus) {focusNode.requestFocus();}};}// 定義快捷鍵映射final Map<ShortcutActivator, Intent> shortcutMap;switch (MediaQuery.navigationModeOf(context)) {case NavigationMode.directional:shortcutMap = _directionalNavShortcutMap;case NavigationMode.traditional:shortcutMap = _traditionalNavShortcutMap;}// 獲取文本縮放因子final double textScaleFactor = theme.useMaterial3? MediaQuery.textScalerOf(context).clamp(maxScaleFactor: 1.3).textScaleFactor: MediaQuery.textScalerOf(context).textScaleFactor;// 返回語義組件,包含焦點行為檢測器和滑塊渲染對象組件return Semantics(container: true,slider: true,onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus,child: FocusableActionDetector(actions: _actionMap,shortcuts: shortcutMap,focusNode: focusNode,autofocus: widget.autofocus,enabled: _enabled,onShowFocusHighlight: _handleFocusHighlightChanged,onShowHoverHighlight: _handleHoverChanged,mouseCursor: effectiveMouseCursor,child: CompositedTransformTarget(link: _layerLink,child: _SliderRenderObjectWidget(key: _renderObjectKey,value: _convert(widget.value),secondaryTrackValue: (widget.secondaryTrackValue != null) ? _convert(widget.secondaryTrackValue!) : null,divisions: widget.divisions,label: widget.label,sliderTheme: sliderTheme,textScaleFactor: textScaleFactor,screenSize: screenSize(),onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,onChangeStart: _handleDragStart,onChangeEnd: _handleDragEnd,state: this,semanticFormatterCallback: widget.semanticFormatterCallback,hasFocus: _focused,hovering: _hovering,allowedInteraction: effectiveAllowedInteraction,),),),);
}

可以看到 _buildMaterialSlider 方法中首先獲取了當前的主題和滑塊主題。

  • 然后根據 widget 的屬性和默認值來更新滑塊主題。

  • 然后,它解析了有效的鼠標光標和允許的交互。

  • 最后,它返回了一個包含焦點行為檢測器和滑塊渲染對象組件的語義組件。

這個組件包含了滑塊的所有交互邏輯和渲染邏輯,包括焦點處理、鼠標懸停處理、滑塊值改變的處理等。

_buildCupertinoSlider方法

_buildCupertinoSlider 方法中,主要完成了 Cupertino 風格滑塊的構建,正如前文所述,將用于 iOSmacOS 平臺。

Widget _buildCupertinoSlider(BuildContext context) {// 滑塊的渲染框有固定的高度,但會占用可用的寬度。// 以這種方式包裝 [CupertinoSlider] 將有助于保持相同的大小。return SizedBox(width: double.infinity,child: CupertinoSlider(// 設置滑塊的值value: widget.value,// 設置滑塊值改變時的回調onChanged: widget.onChanged,// 設置開始拖動滑塊時的回調onChangeStart: widget.onChangeStart,// 設置結束拖動滑塊時的回調onChangeEnd: widget.onChangeEnd,// 設置滑塊的最小值min: widget.min,// 設置滑塊的最大值max: widget.max,// 設置滑塊的分段數divisions: widget.divisions,// 設置滑塊的活動顏色activeColor: widget.activeColor,// 設置滑塊的拇指顏色,如果沒有指定,則使用白色thumbColor: widget.thumbColor ?? CupertinoColors.white,),);
}

首先,它創建了一個 SizedBox,并設置其寬度為 double.infinity,這樣可以使滑塊占用可用的全部寬度。

然后,它在 SizedBox 中創建了一個 CupertinoSlider 組件,并設置了滑塊的各種屬性,包括值、最小值、最大值、分段數、活動顏色和拇指顏色等。

可見,Slider 組件被用于 iOSmacOS 平臺時,實際上內部將自動使用 CupertinoSlider 組件實現。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/210782.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/210782.shtml
英文地址,請注明出處:http://en.pswp.cn/news/210782.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SQL命令---刪除字段

介紹 使用sql語句刪除表字段。 命令 alter table 表名 drop 字段名;例子 刪除a表中的name字段。 alter table a drop name;下面是執行刪除后的表結構&#xff1a;

微服務實戰系列之通信

前言 掰個指頭數一數&#xff0c;博主的“微服務實戰系列”從無到有&#xff0c;從零走到了十五。如果比作時鐘&#xff0c;剛好走過了一刻度。 當初為什么要做這個系列&#xff0c;博主想了又想&#xff0c;私以為作為當下軟件領域的幾個“hot spot”之一&#xff0c;又乘著…

探秘機器學習核心邏輯:梯度下降的迭代過程 (圖文詳解)

一 需求解函數 f() 和 g()函數分別為求y值和求導數的函數。 目的&#xff1a;求該函數的最小值&#xff1a; 代碼&#xff1a; import numpy as np import matplotlib.pyplot as plt f lambda x : (x - 3.5) ** 2 - 4.5 * x 10 g lambda x : 2 * (x - 3.5) - 4.5x np.l…

架構LAMP

目錄 1.什么是LAMP 2.LAMP組成及作用 3.搭建Apache httpd服務 4.編譯安裝mysqld 服務 5.編譯安裝PHP 解析環境 6.安裝論壇 1.什么是LAMP LAMP架構是目前成熟的企業網站應用模式之一&#xff0c;指的是協同工作的一整套系統和相關軟件&#xff0c;能夠提供動態Web站點服務…

MATLAB算法實戰應用案例精講-【人工智能】漫談自動駕駛

目錄 常用數據集 一、自動駕駛領域數據集 1. KITTI數據集 2.CityScapes數據集 3.BDD100K數據集

go與ioc

在Go開發服務端程序時&#xff0c;使用IoC&#xff08;Inversion of Control&#xff09;機制并不像在Java等語言中那樣普遍。Go語言的設計哲學傾向于簡潔和直接&#xff0c;更注重代碼的可讀性和可維護性。 在Go中&#xff0c;通常會使用依賴注入&#xff08;Dependency Inje…

【Python】視頻剪輯小程序

近期遇到一些錄制的視頻需要剪輯。 手機上剪輯操作很耗時&#xff0c;有幾個G的視頻&#xff0c;花了一天的空余時間去剪輯。電腦上也有格式工廠&#xff0c;有很方便。 可是學了Pthon&#xff0c;又無意中了解到了moviepy這個庫&#xff0c;于是自己寫了個簡單的視頻剪輯程序。…

Windows安裝kafka

壓縮包下載地址&#xff1a;https://www.apache.org/dyn/closer.cgi?path/kafka/3.6.1/kafka_2.13-3.6.1.tgz 啟動kafka步驟 zookeeper-server-start.bat rem 閉命令提示符窗口的命令回顯&#xff0c;這樣在運行腳本時不會顯示腳本的具體命令內容 echo offrem 命令行啟動未…

Proteus仿真--8×8LED點陣屏仿電梯數字滾動顯示

本文介紹基于88LED點陣屏仿電梯數字滾動顯示設計&#xff08;完整仿真源文件及代碼見文末鏈接&#xff09; 仿真圖如下 其中K1-K5的5個按鍵分別代表不同樓層&#xff0c;摁下按鍵后在8X8LED上便會顯示到對應樓層的跳變信息&#xff0c;模擬電梯的運作 仿真運行視頻 Proteus仿…

nodejs多線程,fork和Worker

一、前言 javascript是單線程執行的&#xff0c;如果想要多線程執行&#xff0c;那么相當于再運行一個node,其實不該理解成多線程&#xff0c;更像是多進程。 二、Worker(‘worker_threads’模塊) worker有點類似exec&#xff0c;直接再cmd執行node命令&#xff0c;不同的是兩…

《安富萊嵌入式周報》第328期:自主微型機器人,火星探測器發射前失誤故障分析,微軟推出12周24期免費AI課程,炫酷3D LED點陣設計,MDK5.39發布

周報匯總地址&#xff1a;嵌入式周報 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬漢嵌入式論壇 - Powered by Discuz! 更新一期視頻教程&#xff1a; 【實戰技能】 單步運行源碼分析&#xff0c;一期視頻整明白FreeRTOS內核源碼框架和運行…

【STM32】TIM定時器基本定時功能

第一部分&#xff1a;定時器基本定時的功能&#xff1b; 第二部分&#xff1a;定時器的輸出比較功能&#xff1b; 第三部分&#xff1a;定時器輸入捕獲的功能&#xff1b; 第四部分&#xff1a;定時器的編碼接口。 1 TIM簡介 TIM&#xff08;Timer&#xff09;定時器&#…

在OpenCV基于深度學習的超分辨率模型實踐

1. 引言 OpenCV是一個開源的計算機視覺庫&#xff0c;擁有大量優秀的算法。基于最新的合并&#xff0c;OpenCV包含一個易于使用的接口&#xff0c;主要用于實現基于深度學習方法的超分辨率&#xff08;SR&#xff09;。該接口包含預先訓練的模型&#xff0c;這些模型可以非常容…

redis中使用事務保護數據完整性

事務是指一個執行過程&#xff0c;要么全部執行成功&#xff0c;要么失敗什么都不改變。不會存在一部分成功一部分失敗的情況&#xff0c;也就是事務的ACID四大特性&#xff08;原子性、一致性、隔離性、持久性&#xff09;。但是redis中的事務并不是嚴格意義上的事務&#xff…

使用flutter_native_splash替換啟動圖片,iOS端替換不成功

使用flutter_native_splash替換啟動圖片&#xff0c;iOS端替換不成功 1、刪除App重啟手機&#xff1b;2、重新創建一個新的LaunchScreen.storyboard&#xff0c;比如命名為NewLaunchScreen.storyboard&#xff0c;在General里面設置Launch Screen File為這個新的NewLaunchScree…

藍橋杯 day01 奇怪的數列

題目描述 奇怪的數列 從 X 星截獲一份電碼&#xff0c;是一些數字&#xff0c;如下&#xff1a; 13 1113 3113 132113 1113122113 ?? YY 博士經徹夜研究&#xff0c;發現了規律&#xff1a; 第一行的數字隨便是什么&#xff0c;以后每一行都是對上一行"讀出來…

智能優化算法應用:基于蝗蟲算法3D無線傳感器網絡(WSN)覆蓋優化 - 附代碼

智能優化算法應用&#xff1a;基于蝗蟲算法3D無線傳感器網絡(WSN)覆蓋優化 - 附代碼 文章目錄 智能優化算法應用&#xff1a;基于蝗蟲算法3D無線傳感器網絡(WSN)覆蓋優化 - 附代碼1.無線傳感網絡節點模型2.覆蓋數學模型及分析3.蝗蟲算法4.實驗參數設定5.算法結果6.參考文獻7.MA…

【數據挖掘】國科大蘇桂平老師數據庫新技術課程作業 —— 第二次作業

1 設 F { A B → C , B → D , C D → E , C E → G H , G → A } F\{AB\rightarrow C,B\rightarrow D, CD\rightarrow E, CE\rightarrow GH, G\rightarrow A \} F{AB→C,B→D,CD→E,CE→GH,G→A}&#xff0c;用推理的方法證明 F ∣ A B → G F\;|AB\rightarrow G F∣AB→…

持續集成交付CICD:使用Maven命令上傳Nexus制品

目錄 一、實驗 1.使用Maven命令上傳Nexus制品&#xff08;第一種方式&#xff09; 2.使用Maven命令上傳Nexus制品&#xff08;第二種方式&#xff09; 一、實驗 1.使用Maven命令上傳Nexus制品&#xff08;第一種方式&#xff09; &#xff08;1&#xff09;指定一個 hoste…

說說React jsx轉換成真實DOM的過程?

在React中&#xff0c;JSX&#xff08;JavaScript XML&#xff09;是一種語法糖&#xff0c;用于描述用戶界面的結構和組件關系。當你編寫React組件并包含JS JSX解析&#xff1a;React中的JSX代碼首先會被解析成JavaScript對象。這個過程通常是通過Babel等工具進行的&#xff0…