Slider
单值 / 区间滑块,支持垂直方向与自定义滑块
滑块(Slider)在连续或分档的取值范围内选择一个值或一段区间。手势基于 react-native-gesture-handler,位移与激活段由 react-native-reanimated 的 shared value 在 UI 线程驱动 —— 拖拽期间的权威值全在 UI 线程上,React 状态只是它的渲染镜像,重渲染跟不跟得上都不影响跟手性。
import { Slider } from '@skyroc/native-ui';组件内部用到了 GestureDetector,请确保 App 根节点已经包了 GestureHandlerRootView。
基础用法
默认取值范围 0 ~ 100、步长 1。传 value + onChange 即为受控,onChange 在拖拽过程中实时触发。
import { Slider, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const SliderBasic = () => {
const [basic, setBasic] = useState(30);
return (
<View className="gap-2 bg-background p-4">
<Slider
value={basic}
onChange={setBasic}
/>
<Text color="muted">当前值:{basic}</Text>
</View>
);
};
export { SliderBasic };何时使用
- 连续量的粗略调节:音量、亮度、透明度、价格区间。
- 需要精确输入数值时用
Stepper或Input,滑块不适合要求精度的场景。 - 分档很少(3 ~ 5 档)时考虑
Radio,比让用户对准刻度更省事。
步长与范围
min / max 限定范围,step 决定取值粒度:所有取值都会对齐到 min + n × step,再夹回边界。
import { Slider, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const SliderStep = () => {
const [stepped, setStepped] = useState(60);
return (
<View className="gap-2 bg-background p-4">
<Slider
max={200}
min={20}
step={20}
value={stepped}
onChange={setStepped}
/>
<Text color="muted">当前值:{stepped}(20 ~ 200,步长 20)</Text>
</View>
);
};
export { SliderStep };边界值
组件对异常入参做了兜底,不会把 NaN 或倒置的区间传进布局:
| 情况 | 行为 |
|---|---|
| 值越界 | 夹回 min / max |
step ≤ 0 | 按 1 处理 |
min === max | 比例退化为 0,滑块停在起点 |
| 区间未传初值 | 两端都停在 min,而不是 [min, 0] |
| 区间初值首尾倒置 | 后一个值夹到不小于前一个 |
import { Slider, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
const SliderBoundary = () => {
return (
<View className="gap-4 bg-background p-4">
<View className="gap-2">
<Text className="text-sm text-muted-foreground">默认值 120 会夹到 max=80</Text>
<Slider
defaultValue={120}
max={80}
min={20}
/>
</View>
<View className="gap-2">
<Text className="text-sm text-muted-foreground">step=0 按 1 处理</Text>
<Slider
defaultValue={35}
step={0}
/>
</View>
<View className="gap-2">
<Text className="text-sm text-muted-foreground">区间未传初值时两端都停在 min=20</Text>
<Slider
range
min={20}
/>
</View>
</View>
);
};
export { SliderBoundary };区间选择
range 设为 true 后渲染两个滑块,值类型随之变成 [number, number] —— 这是一个判别联合,TypeScript 会替你把值形状对上。两端互为边界,不允许穿越:允许穿越会让松手后「手上这个滑块」变成另一个,比夹住更难用。
import { Slider, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const SliderRange = () => {
const [rangeValue, setRangeValue] = useState<[number, number]>([20, 70]);
return (
<View className="gap-2 bg-background p-4">
<Slider
range
value={rangeValue}
onChange={setRangeValue}
/>
<Text color="muted">
当前区间:{rangeValue[0]} ~ {rangeValue[1]}
</Text>
</View>
);
};
export { SliderRange };拖动结束事件
onChange 在拖拽过程中每帧触发,onChangeAfterDrag 只在值稳定后触发一次 —— 松手、点击轨道、辅助技术步进各一次。需要发请求或落库时用后者。
import { Slider, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const SliderChangeAfterDrag = () => {
const [settled, setSettled] = useState(30);
return (
<View className="gap-2 bg-background p-4">
<Slider
defaultValue={30}
onChangeAfterDrag={setSettled}
/>
<Text color="muted">松手后的值:{settled}</Text>
</View>
);
};
export { SliderChangeAfterDrag };语义颜色
color 同时决定激活段的填充色与默认圆钮的描边色(圆钮内部始终是 bg-background)。
import { Slider, Text } from '@skyroc/native-ui';
import type { ThemeColor } from '@skyroc/native-ui';
import { View } from 'react-native';
const COLORS: ThemeColor[] = ['primary', 'success', 'warning', 'destructive', 'info', 'accent', 'carbon', 'secondary'];
const SliderColor = () => {
return (
<View className="gap-4 bg-background p-4">
{COLORS.map(color => (
<View
key={color}
className="flex-row items-center gap-4"
>
<View className="flex-1">
<Slider
color={color}
defaultValue={60}
/>
</View>
<Text
className="w-20"
color="muted"
>
{color}
</Text>
</View>
))}
</View>
);
};
export { SliderColor };未激活的底轨与 color 无关,统一是 bg-muted-foreground/20。
尺寸
尺寸不做成枚举档位,直接给两个像素值:
| 属性 | 含义 | 默认值 |
|---|---|---|
barSize | 轨道粗细,水平模式是高、垂直模式是宽 | 2 |
thumbSize | 滑块直径,同时决定轨道两端各自内缩的量(半径) | 24 |
import { Slider, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
const SliderSize = () => {
return (
<View className="gap-4 bg-background p-4">
<View className="gap-2">
<Text className="text-sm text-muted-foreground">barSize=2 / thumbSize=16</Text>
<Slider
barSize={2}
defaultValue={40}
thumbSize={16}
/>
</View>
<View className="gap-2">
<Text className="text-sm text-muted-foreground">barSize=6 / thumbSize=24</Text>
<Slider
barSize={6}
defaultValue={40}
thumbSize={24}
/>
</View>
<View className="gap-2">
<Text className="text-sm text-muted-foreground">barSize=12 / thumbSize=32</Text>
<Slider
barSize={12}
defaultValue={40}
thumbSize={32}
/>
</View>
</View>
);
};
export { SliderSize };轨道只有 2px,直接挂手势等于没有命中区,所以外面套了一层透明命中层,交叉轴跨度取 max(thumbSize, 44) —— 视觉上仍然只看得到那条细轨,可点面积却不低于系统建议的 44pt。轨道两端各内缩半个滑块,圆钮因此永远落在命中层内,不会溢出到相邻内容上。
垂直方向
vertical 把主轴换成纵向,值从下往上增大。垂直模式下根容器是 h-full,父级必须有确定高度,否则轨道长度为 0。
import { Slider } from '@skyroc/native-ui';
import { View } from 'react-native';
const SliderVertical = () => {
return (
<View className="bg-background p-4">
<View className="h-56 flex-row gap-8">
<Slider
vertical
defaultValue={40}
/>
<Slider
range
vertical
color="success"
defaultValue={[20, 80]}
/>
<Slider
vertical
barSize={8}
color="warning"
defaultValue={65}
thumbSize={28}
/>
</View>
</View>
);
};
export { SliderVertical };自定义滑块
单值模式用 thumb,区间模式用 startThumb / endThumb(垂直模式下 start 在下、end 在上)。传入后不再渲染默认圆钮,classNames.thumbInner 也随之失效。
import Ionicons from '@expo/vector-icons/Ionicons';
import { Slider } from '@skyroc/native-ui';
import { View } from 'react-native';
import { withUniwind } from 'uniwind';
const ThumbIcon = withUniwind(Ionicons);
const SliderCustomThumb = () => {
return (
<View className="gap-6 bg-background p-4">
<Slider
defaultValue={50}
thumbSize={28}
thumb={
<View className="size-7 items-center justify-center rounded-full bg-primary shadow-sm">
<ThumbIcon
colorClassName="accent-primary-foreground"
name="reorder-two"
size={16}
/>
</View>
}
/>
<Slider
range
color="destructive"
defaultValue={[30, 70]}
thumbSize={20}
endThumb={<View className="size-5 rounded-sm bg-destructive shadow-sm" />}
startThumb={<View className="size-5 rounded-sm bg-destructive shadow-sm" />}
/>
</View>
);
};
export { SliderCustomThumb };自定义内容不会自动缩放:定位框的边长仍是 thumbSize,请把 thumbSize 一并调成你的内容尺寸,否则命中区与视觉会对不齐。
禁用与只读
disabled 阻止手势并把整体降到 50% 不透明度;readonly 只阻止手势,视觉保持正常,适合纯展示的进度条。两者都会摘掉滑块的无障碍调节能力。
import { Slider, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
const SliderDisabled = () => {
return (
<View className="gap-4 bg-background p-4">
<View className="gap-2">
<Text className="text-sm font-medium text-foreground">disabled</Text>
<Slider
disabled
defaultValue={40}
/>
</View>
<View className="gap-2">
<Text className="text-sm font-medium text-foreground">readonly</Text>
<Slider
readonly
defaultValue={40}
/>
</View>
</View>
);
};
export { SliderDisabled };样式覆盖
className 追加到根容器上,classNames 按 slot 细粒度覆盖:
| slot | 作用位置 |
|---|---|
root | 最外层 View |
hitArea | 包住轨道的透明命中层(交叉轴跨度 ≥ 44) |
track | 底轨 |
activeBar | 激活段(从起点或区间左端到当前值) |
thumb | 滑块定位框,位移由动画驱动 |
thumbInner | 缺省圆钮本体,传了自定义滑块时不渲染 |
import { Slider } from '@skyroc/native-ui';
import { View } from 'react-native';
const SliderStyles = () => {
return (
<View className="gap-4 bg-background p-4">
<Slider
className="rounded-xl bg-secondary px-4"
defaultValue={45}
/>
<Slider
classNames={{
activeBar: 'bg-info',
thumbInner: 'border-info bg-info/10',
track: 'bg-info/20'
}}
defaultValue={60}
/>
</View>
);
};
export { SliderStyles };轨道与滑块的尺寸、定位走的是内联 style,类名改不动,需要调整时请用 barSize / thumbSize。
外部控制
受控 value 可以从组件外任意更新,组件会把新值推回 UI 线程;拖拽期间 UI 线程先写、React 后到,回写的是同一个数,两边不会打架。
import { Button, Slider, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const SliderControlled = () => {
const [controlled, setControlled] = useState(50);
return (
<View className="gap-3 bg-background p-4">
<Slider
step={5}
value={controlled}
onChange={setControlled}
/>
<Text color="muted">当前值:{controlled}</Text>
<View className="flex-row gap-2">
<Button
color="primary"
variant="outline"
onPress={() => setControlled(Math.max(0, controlled - 5))}
>
-5
</Button>
<Button
color="primary"
variant="outline"
onPress={() => setControlled(Math.min(100, controlled + 5))}
>
+5
</Button>
<Button
color="primary"
variant="ghost"
onPress={() => setControlled(50)}
>
重置
</Button>
</View>
</View>
);
};
export { SliderControlled };无障碍
每个滑块都带 accessibilityRole="adjustable"、accessibilityValue={{ min, max, now }} 以及 increment / decrement 两个无障碍动作,辅助技术可以按 step 逐档调节(区间模式下同样受两端不穿越的约束),每次调节都会触发一次 onChangeAfterDrag。disabled / readonly 时 accessible 关闭,滑块不再被辅助技术聚焦。
API
Slider
SliderProps 是由 range 区分的判别联合:range 缺省或为 false 时值是 number,为 true 时值是 [number, number]。下表是两种形态共用的属性。
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| min | 最小值 | number | 0 |
| max | 最大值 | number | 100 |
| step | 步长,取值会对齐到 min + n × step;非正数按 1 处理 | number | 1 |
| color | 激活段填充色与默认圆钮描边色 | 'primary' | 'destructive' | 'success' | 'warning' | 'info' | 'accent' | 'carbon' | 'secondary' | 'primary' |
| barSize | 轨道粗细(px),水平模式是高、垂直模式是宽 | number | 2 |
| thumbSize | 滑块直径(px),同时决定轨道两端的内缩量 | number | 24 |
| vertical | 是否垂直方向,值从下往上增大;父级必须有确定高度 | boolean | false |
| disabled | 禁用,不响应手势并整体置灰 | boolean | false |
| readonly | 只读,不响应手势但不置灰 | boolean | false |
| className | 根容器类名,合并在 classNames.root 之后 | string | - |
| classNames | 各 slot 的类名覆盖,见「样式覆盖」一节 | SlotClassNames<SliderSlots> | - |
| testID | 测试标识,挂在根节点上 | string | - |
| ref | 根容器 View 的 ref,用于 measure / 滚动定位 | Ref<View> | - |
单值模式(range 缺省或 false)
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| value | 当前值(受控) | number | - |
| defaultValue | 非受控初始值,缺省为 min | number | - |
| onChange | 值变化回调,拖拽过程中实时触发 | (value: number) => void | - |
| onChangeAfterDrag | 值稳定后触发:松手、点击轨道、无障碍步进各一次 | (value: number) => void | - |
| thumb | 自定义滑块内容,缺省渲染主题色描边的圆钮 | ReactNode | - |
| range | 单值模式标记 | false | false |
区间模式(range 为 true)
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| range* | 开启区间模式,两端互为边界、不允许穿越 | true | - |
| value | 当前区间(受控) | [number, number] | - |
| defaultValue | 非受控初始区间,缺省为 [min, min] | [number, number] | - |
| onChange | 区间变化回调,拖拽过程中实时触发 | (value: [number, number]) => void | - |
| onChangeAfterDrag | 区间稳定后触发:松手、点击轨道、无障碍步进各一次 | (value: [number, number]) => void | - |
| startThumb | 自定义左侧(垂直模式为下侧)滑块内容 | ReactNode | - |
| endThumb | 自定义右侧(垂直模式为上侧)滑块内容 | ReactNode | - |
类型
import type { RangeSliderProps, SingleSliderProps, SliderProps, SliderSlots } from '@skyroc/native-ui';SliderProps 是 SingleSliderProps | RangeSliderProps 的判别联合,判别键为 range。
SliderSlots
可通过 classNames 覆盖的 slot 名称。
SlotClassNames
classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 SliderSlots。
包内还导出了 sliderVariants 与三个常量:DEFAULT_SLIDER_BAR_SIZE(2)、DEFAULT_SLIDER_THUMB_SIZE(24)、SLIDER_MIN_HIT_SIZE(44)。