Skyroc Native UI

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 在拖拽过程中实时触发。

SliderBasic.tsx
Loading…
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 };

何时使用

  • 连续量的粗略调节:音量、亮度、透明度、价格区间。
  • 需要精确输入数值时用 StepperInput,滑块不适合要求精度的场景。
  • 分档很少(3 ~ 5 档)时考虑 Radio,比让用户对准刻度更省事。

步长与范围

min / max 限定范围,step 决定取值粒度:所有取值都会对齐到 min + n × step,再夹回边界。

SliderStep.tsx
Loading…
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]
区间初值首尾倒置后一个值夹到不小于前一个
SliderBoundary.tsx
Loading…
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 会替你把值形状对上。两端互为边界,不允许穿越:允许穿越会让松手后「手上这个滑块」变成另一个,比夹住更难用。

SliderRange.tsx
Loading…
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 只在值稳定后触发一次 —— 松手、点击轨道、辅助技术步进各一次。需要发请求或落库时用后者。

SliderChangeAfterDrag.tsx
Loading…
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)。

SliderColor.tsx
Loading…
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
SliderSize.tsx
Loading…
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。

SliderVertical.tsx
Loading…
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 也随之失效。

SliderCustomThumb.tsx
Loading…
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 只阻止手势,视觉保持正常,适合纯展示的进度条。两者都会摘掉滑块的无障碍调节能力。

SliderDisabled.tsx
Loading…
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缺省圆钮本体,传了自定义滑块时不渲染
SliderStyles.tsx
Loading…
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 后到,回写的是同一个数,两边不会打架。

SliderControlled.tsx
Loading…
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 逐档调节(区间模式下同样受两端不穿越的约束),每次调节都会触发一次 onChangeAfterDragdisabled / readonlyaccessible 关闭,滑块不再被辅助技术聚焦。

API

Slider

SliderProps 是由 range 区分的判别联合:range 缺省或为 false 时值是 number,为 true 时值是 [number, number]。下表是两种形态共用的属性。

属性说明类型默认值
min最小值number0
max最大值number100
step步长,取值会对齐到 min + n × step;非正数按 1 处理number1
color激活段填充色与默认圆钮描边色'primary' | 'destructive' | 'success' | 'warning' | 'info' | 'accent' | 'carbon' | 'secondary''primary'
barSize轨道粗细(px),水平模式是高、垂直模式是宽number2
thumbSize滑块直径(px),同时决定轨道两端的内缩量number24
vertical是否垂直方向,值从下往上增大;父级必须有确定高度booleanfalse
disabled禁用,不响应手势并整体置灰booleanfalse
readonly只读,不响应手势但不置灰booleanfalse
className根容器类名,合并在 classNames.root 之后string-
classNames各 slot 的类名覆盖,见「样式覆盖」一节SlotClassNames<SliderSlots>-
testID测试标识,挂在根节点上string-
ref根容器 View 的 ref,用于 measure / 滚动定位Ref<View>-

单值模式(range 缺省或 false

属性说明类型默认值
value当前值(受控)number-
defaultValue非受控初始值,缺省为 minnumber-
onChange值变化回调,拖拽过程中实时触发(value: number) => void-
onChangeAfterDrag值稳定后触发:松手、点击轨道、无障碍步进各一次(value: number) => void-
thumb自定义滑块内容,缺省渲染主题色描边的圆钮ReactNode-
range单值模式标记falsefalse

区间模式(rangetrue

属性说明类型默认值
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';

SliderPropsSingleSliderProps | RangeSliderProps 的判别联合,判别键为 range

SliderSlots

可通过 classNames 覆盖的 slot 名称。

'activeBar' | 'hitArea' | 'root' | 'thumb' | 'thumbInner' | 'track'

SlotClassNames

classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 SliderSlots。

Partial<Record<Slots, string>>

包内还导出了 sliderVariants 与三个常量:DEFAULT_SLIDER_BAR_SIZE(2)、DEFAULT_SLIDER_THUMB_SIZE(24)、SLIDER_MIN_HIT_SIZE(44)。