模板版本:v0.4.0

@ptomasroos/react-native-multi-slider

本项目基于 @ptomasroos/react-native-multi-slider 开发。

该第三方库的仓库已迁移至 Gitcode,且支持直接从 npm 下载,新的包名为:@react-native-ohos/react-native-multi-slider 版本所属关系如下:

三方库名称 三方库版本 发布信息 支持RN版本 Autolink 编译API版本 社区基线版本 npm地址
@react-native-ohos/react-native-multi-slider ~2.3.0 Gitcode Releases 0.82/0.77 否 API12+ v2.2.2 Npm Address
@react-native-ohos/react-native-multi-slider ~2.2.3 Gitcode Releases 0.72 否 API12+ v2.2.2 Npm Address
@react-native-oh-tpl/react-native-multi-slider <= 2.2.2-0.0.2@deprecated Github Releases(deprecated) 0.72 否 API12+ v2.2.2 Npm Address

1. 安装与使用

进入到工程目录并输入以下命令:

npm

npm install @react-native-ohos/react-native-multi-slider

yarn

yarn add @react-native-ohos/react-native-multi-slider

下面的代码展示了这个库的基本使用场景:

使用时 import 的库名不变。

import React from 'react';

import { StyleSheet, View, Text, ScrollView, Alert, Animated, Image } from 'react-native';
import MultiSlider from '@ptomasroos/react-native-multi-slider';

const AnimatedView = Animated.createAnimatedComponent(View);

CustomLabel.defaultProps = {
  leftDiff: 0,
};

const width = 50;
const pointerWidth = width * 0.47;

function LabelBase(props:any) {
  const { position, value, leftDiff, pressed } = props;
  const scaleValue = React.useRef(new Animated.Value(0.1)); // Behaves oddly if set to 0
  const cachedPressed = React.useRef(pressed);

  React.useEffect(() => {
    Animated.timing(scaleValue.current, {
      toValue: pressed ? 1 : 0.1,
      duration: 200,
      delay: pressed ? 0 : 2000,
      useNativeDriver: false,
    }).start();
    cachedPressed.current = pressed;
  }, [pressed]);

  return (
    Number.isFinite(position) &&
    Number.isFinite(value) && (
      <AnimatedView
        style={[
          labelStyles.sliderLabel,
          {
            left: position - width / 2,
            transform: [
              { translateY: width },
              { scale: scaleValue.current },
              { translateY: -width },
            ],
          },
        ]}
      >
        <View style={labelStyles.pointer} />
        <Text style={labelStyles.sliderLabelText}>{value}</Text>
      </AnimatedView>
    )
  );
}

function CustomLabel(props:any) {
  const {
    leftDiff,
    oneMarkerValue,
    twoMarkerValue,
    oneMarkerLeftPosition,
    twoMarkerLeftPosition,
    oneMarkerPressed,
    twoMarkerPressed,
  } = props;

  return (
    <View style={labelStyles.parentView}>
      <LabelBase
        position={oneMarkerLeftPosition}
        value={oneMarkerValue}
        leftDiff={leftDiff}
        pressed={oneMarkerPressed}
      />
      <LabelBase
        position={twoMarkerLeftPosition}
        value={twoMarkerValue}
        leftDiff={leftDiff}
        pressed={twoMarkerPressed}
      />
    </View>
  );
}

class CustomMarker extends React.Component<any> {
  render() {
    return (
      <View>
        <Text style={markerStyles.text}>{this.props.valuePrefix}{this.props.currentValue}</Text>
        <Image
          style={markerStyles.image}
          source={
            this.props.pressed ? require('./ruby.png') : require('./diamond.png')
          }
          resizeMode="contain"
        />
        <Text style={markerStyles.text}>{this.props.valuePrefix}{this.props.currentValue}{this.props.valueSuffix}</Text>
      </View>
    );
  }
}

export default function MultiSliderDemo() {
  let testRef = React.useRef<View>(null);
  let [currentTestId, setCurrentTestId] = React.useState('');

  // 滑动指标配置
  const touchDimensionsConfig = {
    height: 60,
    width: 60,
    borderRadius: 10,
    slipDisplacement: 200,
  };

  // 当值开始变化时回调
  const onValuesChangeStart = () => Alert.alert('Success', '值开始变化时回调');

  // 值改变时回调
  const onValuesChange = () => Alert.alert('Success', '值改变时回调');

  // 当值停止变化时回调
  const onValuesChangeFinish = () => Alert.alert('Success', '值停止变化时回调');

  // 第一个光标切换时回调
  const onToggleOne = () => Alert.alert('Success', '第一个光标切换回调');

  // 第二个光标切换时回调
  const onToggleTwo = () => Alert.alert('Success', '第二个光标切换回调');

  // 获取绑定的testID
  const getCurrentTestId = () => {
    const testId = testRef.current?.props.testID || '';
    setCurrentTestId(testId);
  };

  return (
    <ScrollView>
      <View style={styles.container}>
        <Text style={styles.title}>MultiSlider功能验证</Text>
        <View style={styles.sliders}>
          <Text style={styles.text}>values(设置values,1和6)</Text>
          <MultiSlider values={[1, 6]} />

          <Text style={styles.text}>
            onValuesChangeStart(值开始变化触发回调)
          </Text>
          <MultiSlider onValuesChangeStart={onValuesChangeStart} />

          <Text style={styles.text}>
            onValuesChange(值改变时回调触发回调)
          </Text>
          <MultiSlider onValuesChange={onValuesChange} />

          <Text style={styles.text}>
            onValuesChangeFinish(当值停止变化时回调)
          </Text>
          <MultiSlider onValuesChangeFinish={onValuesChangeFinish} />

          <Text style={styles.text}>sliderLength(滑块长度{100})</Text>
          <MultiSlider sliderLength={100} />

          <Text style={styles.text}>
            touchDimensions(滑动指标borderRadius 50)
          </Text>
          <MultiSlider touchDimensions={touchDimensionsConfig} />

          <Text style={styles.text}>enableLabel(开启自定义标签)</Text>
          <MultiSlider enableLabel customLabel={() => <CustomLabel />} />

          <Text style={styles.text}>customMarker(自定义光标)</Text>
          <MultiSlider customMarker={CustomMarker} />

          <Text style={styles.text}>customMarkerLeft/Right</Text>
          <Text style={styles.text}>自定义左右光标:默认未启用</Text>
          <MultiSlider
            values={[0, 10]}
            customMarkerLeft={() => <CustomMarker />}
            customMarkerRight={() => <CustomMarker pressed />}
          />

          <Text style={styles.text}>isMarkersSeparated</Text>
          <Text style={styles.text}>自定义左右光标:启用</Text>
          <MultiSlider
            values={[0, 10]}
            isMarkersSeparated={true}
            customMarkerLeft={() => <CustomMarker />}
            customMarkerRight={() => <CustomMarker pressed />}
          />

          <Text style={styles.text}>min(滑块可用最小值3)</Text>
          <MultiSlider enableLabel min={3} />

          <Text style={styles.text}>max(滑块可用最大值8)</Text>
          <MultiSlider enableLabel max={8} />

          <Text style={styles.text}>step(滑块步长5)</Text>
          <MultiSlider enableLabel step={5} />

          <Text style={styles.text}>optionsArray(节点标记)</Text>
          <MultiSlider enableLabel optionsArray={[2, 6, 9]} />

          <Text style={styles.text}>containerStyle(滑块容器样式)</Text>
          <MultiSlider
            containerStyle={{backgroundColor: 'lightblue', padding: 10}}
          />

          <Text style={styles.text}>trackStyle(轨道样式:默认)</Text>
          <MultiSlider trackStyle={{backgroundColor: 'red', height: 5}} />

          <Text style={styles.text}>selectedStyle(轨道样式:滑过后)</Text>
          <MultiSlider selectedStyle={{backgroundColor: 'green', height: 5}} />

          <Text style={styles.text}>unselectedStyle(轨道样式:未滑过)</Text>
          <MultiSlider unselectedStyle={{backgroundColor: 'red', height: 5}} />

          <Text style={styles.text}>markerContainerStyle(光标容器样式)</Text>
          <MultiSlider
            markerContainerStyle={{
              backgroundColor: 'transparent',
              borderColor: 'blue',
              borderWidth: 2,
            }}
          />

          <Text style={styles.text}>markerStyle(光标样式)</Text>
          <MultiSlider
            markerStyle={{
              backgroundColor: 'red',
              width: 20,
              height: 20,
              borderRadius: 10,
            }}
          />

          <Text style={styles.text}>
            pressedMarkerStyle(接触光标后的样式)
          </Text>
          <MultiSlider pressedMarkerStyle={{backgroundColor: 'darkblue'}} />

          <Text style={styles.text}>valuePrefix(值的前缀 b)</Text>
          <MultiSlider
            values={[0, 10]}
            customMarker={CustomMarker}
            valuePrefix="b"
          />

          <Text style={{...styles.text, marginTop: 20}}>
            valueSuffix(值的后缀 a)
          </Text>
          <MultiSlider
            values={[0, 10]}
            customMarker={CustomMarker}
            valueSuffix="a"
          />

          <Text style={styles.text}>enabledOne(禁用第一个光标)</Text>
          <MultiSlider values={[0, 10]} enabledOne={false} />

          <Text style={styles.text}>enabledTwo(禁用第二个光标)</Text>
          <MultiSlider values={[0, 10]} enabledTwo={false} />

          <Text style={styles.text}>stepsAs</Text>
          <Text style={styles.text}>自定义步骤标签,默认展示</Text>
          <MultiSlider
            values={[0, 10]}
            step={2}
            showSteps
            stepsAs={[
              {index: 1, stepLabel: 't1', prefix: 'a1', suffix: 'b1'},
              {index: 2, stepLabel: 't2', prefix: 'a2', suffix: 'b2'},
            ]}
          />

          <Text style={styles.text}>showStepLabels</Text>
          <Text style={styles.text}>隐藏/显示自定义步骤标签,这里隐藏</Text>
          <MultiSlider
            values={[0, 10]}
            step={2}
            showSteps
            showStepLabels={false}
            stepsAs={[
              {index: 1, stepLabel: 't', prefix: 'a', suffix: 'b'},
              {index: 2, stepLabel: 't', prefix: 'a', suffix: 'b'},
            ]}
          />

          <Text style={styles.text}>
            showStepMarkers(显示步骤对应的刻度点)
          </Text>
          <MultiSlider values={[0, 10]} step={2} showSteps showStepMarkers />

          <Text style={styles.text}>onToggleOne</Text>
          <Text style={styles.text}>点击第一个光标回调</Text>
          <MultiSlider values={[0, 10]} onToggleOne={onToggleOne} />

          <Text style={styles.text}>onToggleTwo</Text>
          <Text style={styles.text}>点击第二个光标回调</Text>
          <MultiSlider values={[0, 10]} onToggleTwo={onToggleTwo} />

          <Text style={styles.text}>allowOverlap</Text>
          <Text style={styles.text}>允许/禁止光标重叠,默认禁止,这里允许</Text>
          <MultiSlider values={[0, 10]} enableLabel allowOverlap={true} />

          <Text style={styles.text}>snapped(步进式移动光标)</Text>
          <MultiSlider sliderLength={300} step={3} snapped />

          <Text style={styles.text}>smoothSnapped(跳转最近节点)</Text>
          <MultiSlider smoothSnapped sliderLength={300} step={3} />

          <View style={{marginTop: 60, marginBottom: 150}}>
            <Text style={styles.text}>vertical(垂直方向)</Text>
            <MultiSlider vertical />
          </View>

          <Text style={styles.text}>markerOffsetX(横向偏移20)</Text>
          <MultiSlider markerOffsetX={20} />

          <Text style={styles.text}>markerOffsetY(纵向偏移20)</Text>
          <MultiSlider markerOffsetY={20} />

          <Text style={styles.text}>markerSize(光标与边缘的距离)</Text>
          <MultiSlider markerSize={50} />

          <Text style={styles.text}>minMarkerOverlapDistance</Text>
          <Text style={styles.text}>避免大光标重叠,间隔100</Text>
          <MultiSlider values={[0, 10]} minMarkerOverlapDistance={100} />

          <Text style={styles.text}>minMarkerOverlapStepDistance</Text>
          <Text style={styles.text}>避免大光标重叠,间隔2步</Text>
          <MultiSlider
            values={[0, 300]}
            step={3}
            allowOverlap={false}
            smoothSnapped
            minMarkerOverlapStepDistance={2}
          />

          <Text style={styles.text}>imageBackgroundSource(背景图片)</Text>
          <MultiSlider imageBackgroundSource={require('./logo-og.png')} />

          <Text style={styles.text}>testID(设置testID)</Text>
          <MultiSlider values={[0, 6]} testID={'666'} ref={testRef} />
          <Text onPress={getCurrentTestId}>
            {currentTestId || '点击获取testID'}
          </Text>
        </View>
      </View>
    </ScrollView>
  );
}


const labelStyles = StyleSheet.create({
  parentView: {
    position: 'relative',
  },
  sliderLabel: {
    position: 'absolute',
    justifyContent: 'center',
    bottom: '100%',
    width: width,
    height: width,
  },
  sliderLabelText: {
    textAlign: 'center',
    lineHeight: width,
    borderRadius: width / 2,
    borderWidth: 2,
    borderColor: '#999',
    backgroundColor: '#fff',
    flex: 1,
    fontSize: 18,
    color: '#aaa',
  },
  pointer: {
    position: 'absolute',
    bottom: -pointerWidth / 4,
    left: (width - pointerWidth) / 2,
    transform: [{ rotate: '45deg' }],
    width: pointerWidth,
    height: pointerWidth,
    backgroundColor: '#999',
  },
});

const markerStyles = StyleSheet.create({
  image: {
    height: 40,
    width: 40,
  },
  text: {
    alignSelf: 'center',
    paddingVertical: 20,
  },
  title: {
    fontSize: 30,
  },  
});

const styles = StyleSheet.create({
    container: {
      flex: 1,
      flexDirection: 'column',
      justifyContent: 'center',
      alignItems: 'center',
    },
    sliders: {
      margin: 20,
      // width: 280,
    },
    text: {
      alignSelf: 'center',
      paddingVertical: 20,
    },
    title: {
      fontSize: 30,
    },
    sliderOne: {
      flexDirection: 'row',
      justifyContent: 'space-around',
    },
  });

2. 约束与限制

2.1 兼容性

本文档内容基于以下版本验证通过:

  1. RNOH: 0.72.20; SDK: HarmonyOS NEXT Developer Beta1; IDE: DevEco Studio 5.0.3.200; ROM: 3.0.0.18;
  2. RNOH: 0.77.18; SDK: HarmonyOS 6.0.0 Release SDK; IDE: DevEco Studio 6.0.0.868; ROM: 6.0.0.112;
  3. RNOH: 0.82.1; SDK: HarmonyOS 6.0.0 Release SDK; IDE: DevEco Studio 6.0.0.858; ROM: 6.0.0.112;

3. 属性

"Platform"列表示该属性在原三方库上支持的平台。

"HarmonyOS Support"列为 yes 表示 HarmonyOS 平台支持该属性;no 则表示不支持;partially 表示部分支持。使用方法跨平台一致,效果对标 iOS 或 Android 的效果。

Name Description Type Required Platform HarmonyOS Support
values 滑块的预设值列表 array of numbers NO All YES
onValuesChangeStart 数值开始变化时的回调函数 function NO All YES
onValuesChange 数值变化时的回调函数 function NO All YES
onValuesChangeFinish 数值停止变化时的回调函数 function NO All YES
sliderLength 滑块长度 number NO All YES
touchDimensions 定义滑块触摸区域的尺寸 object NO All YES
enableLabel 是否启用标签渲染 function NO All YES
customLabel 用于在滑块游标上方渲染标签的组件 function NO All YES
customMarker 滑块游标使用的组件 function NO All YES
customMarkerLeft 左侧滑块游标使用的组件 function NO All YES
customMarkerRight 右侧滑块游标使用的组件 function NO All YES
isMarkersSeparated 是否启用分开的左右自定义游标 boolean NO All YES
min 滑块可设置的最小值 number NO All YES
max 滑块可设置的最大值 number NO All YES
step 滑块步长 number NO All YES
optionsArray 滑块可选值列表,设置后会忽略 min、max array of numbers NO All YES
containerStyle 滑块容器样式 style object NO All YES
trackStyle 滑块轨道默认样式 style object NO All YES
selectedStyle 轨道滑动选中区域的样式 style object NO All YES
unselectedStyle 轨道未选中区域的样式 style object NO All YES
markerContainerStyle 游标容器样式 style object NO All YES
markerStyle 游标样式 style object NO All YES
pressedMarkerStyle 游标被触摸时的样式 style object NO All YES
stepStyle 步长样式 style object NO - YES
stepLabelStyle 步长标签样式 style object NO - YES
stepMarkerStyle 步长标记点样式 style object NO - YES
valuePrefix 数值前缀 string NO All YES
valueSuffix 数值后缀 string NO All YES
enabledOne 是否启用第一个游标 boolean NO All YES
enabledTwo 是否启用第二个游标 boolean NO All YES
stepsAs 用于自定义步长标签,需传入数组:[{index, stepLabel, prefix, suffix}],需同时开启 showSteps 和 showStepLabels array of objects NO All YES
showSteps 是否显示步长 boolean NO All YES
showStepMarkers 是否在轨道上显示步长标记,需同时开启 showSteps boolean NO All YES
showStepLabels 是否在轨道下方显示步长标签,需同时开启 showSteps boolean NO All YES
onToggleOne 第一个游标状态切换监听回调 function callback NO All YES
onToggleTwo 第二个游标状态切换监听回调 function callback NO All YES
allowOverlap 是否允许游标之间重叠 boolean NO All YES
snapped 是否将游标固定在分段位置,滑块会被分为 N 个固定点位 boolean NO All YES
smoothSnapped 与 snapped 类似,滑动时可自由拖动,松开后吸附到最近点位 boolean NO All YES
vertical 是否使用垂直方向(默认水平) boolean NO All YES
markerOffsetX 第一个游标 X 偏移量 number NO All YES
markerOffsetY 第二个游标 Y 偏移量 number NO All YES
markerSize 游标与轨道边缘的间距,防止游标超出轨道 number NO All YES
minMarkerOverlapDistance 当值>0且allowOverlap=false时,设置两游标最小像素间距,防止大游标重叠 number NO All YES
minMarkerOverlapStepDistance 以步长为单位设置两游标最小间距,不可与 minMarkerOverlapDistance 同时使用 number NO All YES
imageBackgroundSource 背景图片资源路径 string NO All YES
testID 用于端到端测试定位当前视图 string NO All YES

4. 遗留问题

5. 其他

6. 开源协议

本项目基于The MIT License (MIT),请自由地享受和参与开源。