Slider 是用户调整数值的直观组件,音量、亮度、价格筛选、字体大小……几乎所有需要连续或离散数值的场景都会用到它。ArkTS 里是 Slider,Flutter 里也是 Slider,名字一样,但用法和细节有不少差异。
Flutter 的 Slider 分两种:单值 Slider 和范围值 RangeSlider。前者控制一个数值,后者控制一个区间(最小值 + 最大值)。这篇把两种都拆开讲,覆盖基础用法、离散档位、自定义颜色、联动控制、RGB 混色器、自定义主题、双向数据同步等场景。
先记住 Slider 的取值逻辑:value 是当前值,min/max 是取值区间(默认 0.0/1.0),divisions 决定是否离散,label 是拖拽时浮出的提示。onChanged 回调里面更新状态,onChangeStart 和 onChangeEnd 在开始拖拽和结束拖拽时触发。
最简单的连续滑块,范围 0.0~1.0:
double _brightness = 0.7;
Slider(
value: _brightness,
onChanged: (v) => setState(() => _brightness = v),
)
配合图标做视觉提示会更直观。比如亮度调节,左边放低亮度图标,右边放高亮度图标:
Row(
children: [
Icon(Icons.brightness_low),
Expanded(
child: Slider(value: _brightness, onChanged: (v) => setState(() => _brightness = v)),
),
Icon(Icons.brightness_high),
],
)
Expanded 包住 Slider 让它撑满剩余空间,不设固定宽度。
显示当前值。label 参数在拖拽时浮出提示文字,配合 SliderTheme 的 showValueIndicator: ShowValueIndicator.always 可以一直显示:
SliderTheme(
data: SliderTheme.of(context).copyWith(
showValueIndicator: ShowValueIndicator.always,
),
child: Slider(
value: _brightness,
onChanged: (v) => setState(() => _brightness = v),
label: '${(_brightness * 100).toInt()}%',
),
)
showValueIndicator 有三个可选值:always(始终显示)、onlyForDiscrete(仅离散时显示)、never(从不显示)。
如果希望值只能落在几个固定的档位上,设 divisions 参数。比如字体大小从 0.8 倍到 1.4 倍,分 6 档:
Slider(
value: _fontSizeVal,
min: 0.8,
max: 1.4,
divisions: 6, // 6 等分,共 7 个档位
label: '${_fontSizeVal.toStringAsFixed(1)}x',
onChanged: (v) => setState(() => _fontSizeVal = v),
)
divisions 表示分割的段数,可选的档位数量是 divisions + 1。比如 0.8 到 1.4,divisions: 6,档位就是 0.8/0.9/1.0/1.1/1.2/1.3/1.4。
拖拽时滑块会吸附到最近的档位,label 显示当前档位的值。离散滑块的 label 默认在拖拽结束时浮出,showValueIndicator: ShowValueIndicator.onlyForDiscrete。
Slider 的主色、轨道色、滑块颜色都可以改:
Slider(
value: _volume,
onChanged: (v) => setState(() => _volume = v),
activeColor: Colors.green,
inactiveColor: Colors.green.shade100,
thumbColor: Colors.green.shade700,
overlayColor: WidgetStateProperty.all(Colors.green.withValues(alpha: 0.12)),
)
activeColor 已选中部分(左半段)的颜色,inactiveColor 未选中部分(右半段)的颜色,thumbColor 滑块的圆形颜色。overlayColor 是拖拽时的波纹效果颜色,用的是 WidgetStateProperty,可以针对不同状态设定不同的颜色。
RangeSlider 控制两个值:RangeValues(start, end),适合做价格筛选、距离范围等:
RangeValues _priceRange = RangeValues(100, 800);
RangeSlider(
values: _priceRange,
min: 0,
max: 1000,
divisions: 20,
labels: RangeLabels(
'\u00a5${_priceRange.start.toInt()}',
'\u00a5${_priceRange.end.toInt()}',
),
onChanged: (v) => setState(() => _priceRange = v),
)
values 用 RangeValues 类型,labels 用 RangeLabels,分别传两个 label。拖拽时浮出上界和下界的文字。
RangeSlider 的 activeColor 作用在两个滑块之间的区域(选中区间),跟单值 Slider 的 activeColor 含义一致。
divisions 同样适用于 RangeSlider,做了分档后用户选值更精准。比如 0-1000 元分 20 档,每档 50 元。
另一种使用场景是选择范围并在上方实时显示数值:
Column(
children: [
Row(
children: [
Text('${_distance.start.toInt()}km', style: ...),
Spacer(),
Text('${_distance.end.toInt()}km', style: ...),
],
),
RangeSlider(
values: _distance,
min: 1,
max: 50,
divisions: 49,
labels: RangeLabels('${_distance.start.toInt()}km', '${_distance.end.toInt()}km'),
onChanged: (v) => setState(() => _distance = v),
),
],
)
上方两个 Text 实时显示当前范围,下方 RangeSlider 控制。这种布局在商品筛选页面很常见。
多个 Slider 互相配合创造出一个复杂的结果——比如用三个 Slider 分别控制红/绿/蓝三个通道,实时合成颜色:
double _red = 0.4, _green = 0.6, _blue = 0.8;
Color get _mixColor => Color.from(alpha: 1, red: _red, green: _green, blue: _blue);
Container(
height: 60,
decoration: BoxDecoration(
color: _mixColor,
borderRadius: BorderRadius.circular(8),
),
)
每个 Slider 控制一个颜色通道,_mixColor 是三个值实时合成的 Color。三个 Slider 互不依赖,但输出会聚合成一个结果。用三个 setState 回调各自更新。
配合上方预览色块和右侧数值显示,交互感很强。这个场景在 ArkTS 里也可以用三个 Slider 组件实现,思路完全一样。
音量控制常见的设计:Switch 控制 Slider 是否可用,关掉 Switch 时音量归零且 Slider 灰色不可拖拽:
bool _sliderEnabled = true;
double _linkedValue = 50;
Row(
children: [
Icon(Icons.volume_up),
Text('媒体音量'),
Spacer(),
Switch(
value: _sliderEnabled,
onChanged: (v) {
setState(() {
_sliderEnabled = v;
if (!v) _linkedValue = 0;
});
},
),
],
)
AbsorbPointer(
absorbing: !_sliderEnabled,
child: Opacity(
opacity: _sliderEnabled ? 1.0 : 0.4,
child: Slider(
value: _linkedValue,
max: 100,
onChanged: (v) => setState(() => _linkedValue = v),
),
),
)
AbsorbPointer 包住 Slider,absorbing 为 true 时拦截所有触摸事件。Opacity 改变透明度,视觉上提示禁用状态。这叫"组合禁用",比设 onChanged: null 更灵活——onChanged: null 也会自动变灰,但不能包裹额外逻辑。
Switch 关闭时把 _linkedValue 设为 0,做到"关即静音"。
Slider 和文本框相互同步:拖 Slider 文本框数字跟着变,改文本框 Slider 跟着动:
final _numberCtrl = TextEditingController(text: '50');
TextField(
controller: _numberCtrl,
keyboardType: TextInputType.number,
onChanged: (v) {
final n = double.tryParse(v);
if (n != null && n >= 0 && n <= 100) {
setState(() => _linkedValue = n);
}
},
)
Slider(
value: _linkedValue,
max: 100,
onChanged: (v) {
setState(() {
_linkedValue = v;
_numberCtrl.text = v.toInt().toString();
});
},
)
双向同步的关键是:Slider 回调里更新 controller 的 text,TextField 回调里更新 Slider 的 value。注意防止无限循环(TextField 同步到 Slider,Slider 同步到 TextField,TextField 又触发 onChanged……),但 Flutter 的 setState 重新 build 时不会触发 TextField 的 onChanged,所以不会循环。
全局自定义 Slider 的样式通过 SliderTheme 组件覆盖:
SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 6,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 10),
overlayShape: RoundSliderOverlayShape(overlayRadius: 20),
activeTrackColor: Colors.deepPurple,
inactiveTrackColor: Colors.deepPurple.shade100,
thumbColor: Colors.deepPurple,
overlayColor: Colors.deepPurple.withValues(alpha: 0.12),
valueIndicatorColor: Colors.deepPurple,
valueIndicatorTextStyle: TextStyle(color: Colors.white),
),
child: Slider(...),
)
SliderTheme 可以包的参数非常多:trackHeight 轨道高度、thumbShape 滑块形状和大小、overlayShape 波纹形状、valueIndicatorColor 数值提示气泡颜色等。如果在 ThemeData 层面统一盖,用 Theme.of(context).copyWith(sliderTheme: ...) 全局生效。
| 组件 | 参数 | 用途 |
|---|---|---|
Slider | value | 当前值 |
min / max | 范围(默认 0.0/1.0) | |
divisions | 分档数(null 连续) | |
label | 拖拽时浮出文字 | |
activeColor | 已选中轨道颜色 | |
inactiveColor | 未选中轨道颜色 | |
thumbColor | 滑块颜色 | |
overlayColor | 波纹颜色(WidgetStateProperty) | |
onChanged | 值变化回调 | |
onChangeStart / onChangeEnd | 开始/结束拖拽 | |
RangeSlider | values | RangeValues 类型 |
labels | RangeLabels 类型 | |
divisions | 分档 | |
activeColor / inactiveColor | 同 Slider |
| 特性 | Flutter | ArkTS |
|---|---|---|
| 单值 | Slider | Slider |
| 范围 | RangeSlider | RangeSlider |
| 离散 | divisions 参数 | step 参数 |
| 取值区间 | min/max | min/max |
| 自定义颜色 | activeColor / inactiveColor | trackColor / selectedColor |
| 自定义形状 | SliderTheme + thumbShape | trackThickness / blockColor |
| 值提示 | label + showValueIndicator | showTips + tipValue |
| 禁用 | onChanged: null | disabled: true |
| 范围 labels | RangeLabels | 无原生,手动拼 |
核心差异在自定义程度上。Flutter 的 Slider 通过 SliderTheme 可以改几乎所有视觉元素,包括轨道粗细、滑块形状、气泡样式。ArkTS 的参数相对少一些,但常用场景够用了。
divisions 容易算错档位。divisions: 6 不是 6 个档位,而是 6 段,档位是 divisions + 1 = 7 个。我一开始以为 divisions 就是档位数,结果值比预期少了一档。
RangeSlider 的 labels 必须用 RangeLabels。RangeSlider 的 labels 类型是 RangeLabels,不是 String。如果写 labels: '${_price.start}' 会编译报错。
Slider 的 overlayColor 要 WidgetStateProperty。overlayColor 接受 WidgetStateProperty<Color?>,不是直接传 Color。用 WidgetStateProperty.all(Colors.blue.withAlpha(30)) 处理。
SliderTheme 影响子树内所有 Slider。SliderTheme 包住的是它的子树,不只是包住的那个 Slider。如果只想改一个 Slider 的样式,用 SliderTheme 精准包裹,或者每条都单独 copyWith。
Slider 在 Expanded 外不撑满。Slider 默认宽度是 double.infinity,但如果父级没有约束宽度(比如直接放 Column 里不设 crossAxisAlignment),Slider 会很窄。通常在 Row 里包 Expanded 或在 Column 里不限制宽度。
Slider 和分数步进不精确。divisions: 100 加上浮点数运算,有时候 onChanged 拿到的值会有 0.0000001 这种尾差。解决办法是显示时做四舍五入,或者改用整数 slider(min/max/value 都是 int,Slider 会自动处理)。
Slider 看起来简单,但写起来发现它的玩法不少。连续/离散、单值/范围、颜色/联动/混色器/双向同步,每个场景都有不同的参数组合。最让我意外的是 RGB 混色器——三个普通 Slider 组合起来就能做出现实的交互界面,这在 ArkTS 里也是同样思路,但 Flutter 的颜色合成一句 Color.from() 就够了。
SliderTheme 的可定制程度确实高,轨道粗细、滑块形状、气泡样式都能改。不过大部分场景调调 activeColor 就够了,不需要动 SliderTheme。
对了,这篇的 demo 在主页点"Slider 滑块"就能看 10 种效果。下篇来写 DropdownButton,把下拉选择的各种样式说清楚。
原文作者:;如有侵权,请联系管理员删除。