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 不为null,Slider 支持离散的值。
3.颜色定制Slider 的颜色可以通过 activeColor、inactiveColor、secondaryActiveColor、thumbColor 和 overlayColor 进行定制。
4.交互方式通过 allowedInteraction 属性,可以定义用户与 Slider 的交互方式。
5.焦点管理通过 autofocus 和 focusNode,Slider 可以管理焦点。如果autofocus 为 true,Slider 将在初始化时获取焦点。
6.鼠标光标通过 mouseCursor 属性,可以定义鼠标指针在悬停或进入 Slider 时的光标样式。
7.事件回调通过 onChanged、onChangeStart 和 onChangeEnd,Slider 可以在用户拖动滑块选择新值时触发事件。
8.标签显示通过 label 属性,Slider 可以在滑块处于活动状态时显示标签。
9.次要轨道值通过 secondaryTrackValue,Slider 可以显示次要轨道。

2. Slider组件

这一小节介绍 Flutter 内置的滑块 Slider 组件的用法。这个组件有两个构造函数,分别是 Slider 和 Slider.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 构造函数基本一致。我们设置了滑块的 值范围、离散划分的数量、标签 以及各种颜色。同时,我们也定义了onChanged、onChangeStart 和 onChangeEnd回调来响应用户的操作。

2.3 离散值功能:divisions属性

divisions 属性用于将 Slider 的整个值范围划分为等间隔的离散值。

例如,如果 min 为 0.0,max 为 10.0,divisions 为 5,那么Slider可以取的值就是 0.0, 2.0, 4.0, 6.0, 8.0, 10.0。如果 divisions 为 null,那么 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,它的值范围从 0 到 10,分为 5 个离散的部分。这意味着用户只能选择 0.0, 2.0, 4.0, 6.0, 8.0, 10.0 这些值,不能选择这些值之间的值。当用户拖动滑块改变值时,onChanged 回调会被调用,我们在这个回调中更新 _currentValue 的值并刷新界面。

2.4 颜色定制功能

Slider 组件提供了多个属性来定制滑块的颜色:

  • activeColor:活动部分的颜色,即滑块左侧(或右侧,取决于语言和方向)的轨道颜色。

  • inactiveColor:非活动部分的颜色,即滑块右侧(或左侧,取决于语言和方向)的轨道颜色。

  • secondaryActiveColor:滑块轨道上 thumb 和 Slider.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),
)

在这里插入图片描述

在这个例子中,并通过 activeColor、inactiveColor、thumbColor 和 overlayColor 属性来定制滑块的颜色。

当用户拖动滑块时,滑块左侧的轨道颜色为蓝色,滑块右侧的轨道颜色为灰色,滑块 thumb 的颜色为红色,滑块 thumb 被聚焦、悬停或拖动时的高亮颜色为黄色。

2.5 焦点管理:autofocus 和 focusNode 属性

2.4.1 FocusNode 对象

在 Flutter 中,焦点管理是通过 FocusNode 对象来实现的。FocusNode 对象表示用户界面中的一个可以获得键盘输入焦点的元素。

2.4.2 autofocus 和 focusNode 属性

而Slider 组件提供了 autofocus 和 focusNode 两个属性来管理焦点。

  • 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 ,它的值范围从 0 到 100,分为 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,它的值范围从 0 到 100,分为 5 个离散的部分。

我们使用 label 属性来显示当前滑块的值,这个值是 _currentValue 四舍五入后的整数。

当用户拖动滑块改变值时,onChanged 回调会被调用,我们在这个回调中更新 _currentValue 的值并刷新界面。

这样,用户在拖动滑块时,就可以看到滑块上方显示的当前值。

2.8 次要轨道值:secondaryTrackValue 属性

在 Slider 组件中,所谓 次要轨道 是指滑块轨道上的一个可选部分,它表示一个次要的值。这个次要的值由 secondaryTrackValue 属性来设置。这个功能可以用于表示一些特殊的场景,例如在一个音频播放器中,value 可以表示当前的播放位置,而 secondaryTrackValue 可以表示缓冲的位置。

secondaryTrackValue 属性的值必须在 min 和 max 之间。当设置了 secondaryTrackValue,滑块轨道上会显示两个活动部分:

  • 一个是从 min 到 value;
  • 另一个是从 value 到 secondaryTrackValue。

这两个活动部分的颜色可以通过 activeColor 和 secondaryActiveColor 来分别设置。

例如:

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),),);}
}

在这里插入图片描述

在这个例子中,我们通过 activeColor、inactiveColor、thumbColor 和 overlayColor 属性来定制滑块的颜色。当用户拖动滑块时,滑块左侧的轨道颜色为蓝色,滑块右侧的轨道颜色为灰色,滑块 thumb 的颜色为红色,滑块 thumb 被聚焦、悬停或拖动时的高亮颜色为绿色。

2.9 关于事件回调

上面小节中实际上已经用到的各个事件回调,这里简单补充说明一下。Slider 组件提供了三个事件回调:

  1. onChanged:当用户通过拖动选择新值时调用。它的参数是新选择的值;
  2. onChangeStart:当用户开始选择新值时调用。它的参数是开始选择时的值;
  3. onChangeEnd:当用户完成选择新值时调用。它的参数是完成选择后的值。

3. Slider组件源码分析

【注】:不考虑 Cupertino 风格的实现(实际上内部通过CupertinoSlider转换),完整分析一个滑块组件的实现,在Flutter源代码中也有两千多行。这部分内容涉及广泛,将在后续逐渐补充。

3.1 Slider类

很显然,一个滑块中是必然存在状态的,因此需要从 StatefulWidget 得到一个 Slider 类,用于实现相关的滑块构造函数,用作外部使用的接口,这也就是前文介绍过的两个构造函数: Slider 和 Slider.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。

销毁组件分析

接着 initState 看 dispose 也算是思维常态,毕竟创建了相关的资源就需要销毁以防止内存泄漏。对应的是有状态组件状态类的 dispose 方法。这里具体说来就是 _SliderState 的 dispose 方法。其源代码为:


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 方法在其状态类中。在 滑块 组件中,具体说来就是 _SliderState 的 build 方法。其代码如下:


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,则在 Android、Fuchsia、Linux 和 Windows 平台上构建 Material 风格的滑块,在 iOS 和 macOS 平台上构建 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 风格滑块的构建,正如前文所述,将用于 iOS 和 macOS 平台。

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 组件被用于 iOS 和 macOS 平台时,实际上内部将自动使用 CupertinoSlider 组件实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/news/210782.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…