1. 前言
实现这个功能主要会使用RN原生API里的PanResponder。通过使用PanResponder,我们才能实现对整个拖动过程中的:拖动开始、拖动过程中、拖动结束,这3个关键点的重写。
2. 效果展示
3. 使用
import ThreeSlider from './threeSlider';
/**
* 开始移动时
*/
onGrant() {
this.setState({ isShowSliderText: true });
}
/**
* 拖动结束的回调
* @param {*} ishow
*/
onRelease() {
//隐藏
this.setState({ isShowSliderText: false });
}
/**
* A 移动时的回调
*/
onStartMove(start) {
console.log(start);
this.setState({ start, sliderText: start });
}
/**
* A点正在移动时Slider不在区域内
*/
onStartSliderMove(start, slideValue) {
this.setState({ start, slideValue, sliderText: start });
this.player.seek(slideValue);
}
/**
* B 移动时的回调
*/
onEndMove(end) {
this.setState({ end, sliderText: end });
}
/**
* B点正在移动时Slider不在区域内
*/
onEndSliderMove(end, slideValue) {
this.setState({ end, slideValue, sliderText: end });
this.player.seek(slideValue);
}
/**
* 播放节点 移动时的回调
*/
onSlideMove(slideValue) {
this.setState({ slideValue, sliderText: slideValue });
this.player.seek(slideValue);
}
/**
* threeSlider View
* @returns
*/
threeSliderView = () => {
return (
<View style={{ flex: 1, height: 70 }}>
<ThreeSlider
range={this.state.duration}
startA={this.state.start}
endB={this.state.end}
slideValue={this.state.slideValue}
onGrant={this.onGrant.bind(this)} //开始移动的回调
onRelease={this.onRelease.bind(this)} // 移动结束时的回调
onStartMove={this.onStartMove.bind(this)} //A点正在移动时的回调
onStartSliderMove={this.onStartSliderMove.bind(this)} //A点正在移动时Slider不在区域内
onEndMove={this.onEndMove.bind(this)} //B点正在移动时的回调
onEndSliderMove={this.onEndSliderMove.bind(this)} //B点正在移动时Slider不在区域内的回调
onSlideMove={this.onSlideMove.bind(this)} //播放正在移动时的回调
/>
</View>
);
};
3. 完整代码
//threeSlider.js
/**
* 三个点的滑动进度条
*/
// Match参考 https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil
import React, { Component } from 'react';
import { StyleSheet, View, PanResponder, Text, Dimensions } from 'react-native';
const roundSize = 30; // 圆的大小
const width = Dimensions.get('window').width - roundSize * 2; // 设备宽度
export default class Index extends Component {
constructor(props) {
super(props);
let scale = width / this.props.range;
let { range, startA, endB, slideValue } = this.props;
let start = Math.round(
startA === 0 ? roundSize / 2 : startA === range ? width - roundSize : scale * startA
);
let end = Math.round(endB === 0 ? width : scale * endB);
let slide = Math.round(
slideValue === 0 ? roundSize / 2 : slideValue === range ? width - roundSize : scale * slideValue
);
this.state = {
range,
startA,
endB,
slideValue,
start, // 起始坐标
end, // 结束坐标
slide
};
}
//父元素对组件的props或state进行了修改
UNSAFE_componentWillReceiveProps(nextProps) {
let scale = width / this.props.range;
let { range, startA, endB, slideValue } = nextProps;
let start = Math.round(
startA === 0 ? roundSize / 2 : startA === range ? width - roundSize : scale * startA
);
let slide = Math.round(
slideValue === 0
? roundSize / 2
: slideValue === range
? width - roundSize
: scale * slideValue
);
let end = Math.round(endB === 0 ? width : scale * endB);
this.setState({
range,
startA,
endB,
slideValue,
start, // 起始坐标
end, // 结束坐标
slide
});
}
//组件将要被加载到视图之前调用
UNSAFE_componentWillMount() {
let scale = width / this.props.range;
this.panResponderStart = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderTerminationRequest: (evt, gestureState) => true,
// 开始
onPanResponderGrant: (evt, gestureState) => {
this.forceUpdate();
//显示
this.props.onGrant();
},
onPanResponderMove: (evt, gestureState) => {
// 开始的拖动事件
let start = gestureState.moveX; // 当前拖动所在的坐标
let threshold = this.state.end - roundSize; // 阀值
if (start >= threshold) {
// 保证开始价格不会超过结束价格
start = threshold;
}
let startA = Math.round(start / scale); // 计算 实际的值
// 保证开始价格不会小于最小值
if (start <= roundSize) {
start = roundSize / 2;
startA = 0;
}
//修正播放的进度条
if (start > this.state.slide) {
//A点正在移动时Slider不在区域内
let slideValue = Math.ceil(start / scale);
this.setState({ slide: start, slideValue, start, startA }, () => {
this.props.onStartSliderMove(this.state.startA, slideValue);
});
} else {
this.setState(
{
start,
startA
},
() => {
this.props.onStartMove(this.state.startA);
}
);
}
},
onPanResponderRelease: (evt, gestureState) => {
// 隐藏
this.props.onRelease();
return true;
},
onPanResponderTerminate: (evt, gestureState) => true
});
this.panResponderEnd = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
this.forceUpdate();
//显示
this.props.onGrant();
},
onPanResponderMove: (evt, gestureState) => {
// 结束的拖动事件
let end = gestureState.moveX;
let threshold = this.state.start + roundSize; // 阀值
if (end <= threshold) {
// 保证开始价格不会超过结束价格
end = threshold;
}
// end = parseInt(end / step) * step;
let endB = (end / scale).toFixed(2);
if (end >= width) {
// 保证结束价格不会超过最大值
end = width;
endB = this.state.range;
}
//修正播放的进度条
if (end < this.state.slide) {
let slideValue = Math.floor(end / scale);
this.setState({ slide: end, slideValue });
}
if (end < this.state.slide) {
//B点正在移动时Slider不在区域内
let slideValue = Math.ceil(end / scale);
this.setState({ slide: end, slideValue, end, endB }, () => {
this.props.onEndSliderMove(this.state.endB, slideValue);
});
} else {
this.setState(
{
end,
endB
},
() => {
this.props.onEndMove(this.state.endB);
}
);
}
},
onPanResponderRelease: (evt, gestureState) => {
// 隐藏
this.props.onRelease();
return true;
},
onPanResponderTerminate: (evt, gestureState) => true
});
this.panResponderPlay = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
this.forceUpdate();
//显示
this.props.onGrant();
},
onPanResponderMove: (evt, gestureState) => {
// 结束的拖动事件
let slide = gestureState.moveX;
let slideValue = Math.round(slide / scale);
//最小值
if (slide <= this.state.start) {
// 保证开始价格不会小于最小值
slide = this.state.start;
slideValue= this.state.startA
}
//最大值
if (slide >= this.state.end) {
// 保证开始价格不会大于最大值
slide = this.state.end;
slideValue = this.state.range
}
this.setState(
{
slide,
slideValue
},
() => {
this.props.onSlideMove(this.state.slideValue);
}
);
},
onPanResponderRelease: (evt, gestureState) => {
// 隐藏
this.props.onRelease();
return true;
},
onPanResponderTerminate: (evt, gestureState) => true
});
}
render() {
let { start, end, slide } = this.state;
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<View
style={[
styles.progressContainer,
{ backgroundColor: '#D6D7E6' },
{ width: start == roundSize / 2 ? 0 : start }
]}
/>
<View style={[styles.progressContainer, { width: width - start - (width - end) }]} />
<View
style={[
styles.progressContainer,
{ backgroundColor: '#D6D7E6' },
{ width: width - end }
]}
/>
</View>
<View style={[styles.startA, { left: start }]} {...this.panResponderStart.panHandlers}>
<Text>A</Text>
</View>
<View style={[styles.endB, { left: end }]} {...this.panResponderEnd.panHandlers}>
<Text>B</Text>
</View>
<View style={[styles.slide, { left: slide }]} {...this.panResponderPlay.panHandlers}></View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
height: 70,
justifyContent: 'center',
alignItems: 'center'
},
progressContainer: {
backgroundColor: '#ffa710',
height: 4
},
startA: {
position: 'absolute',
width: roundSize,
height: roundSize,
borderRadius: roundSize / 2,
borderColor: '#D6D7E6',
borderWidth: 1,
shadowColor: 'rgba(0,0,0,0.6)',
shadowRadius: 5,
shadowOpacity: 0.9,
backgroundColor: 'green',
top: 1,
justifyContent: 'center',
alignItems: 'center'
},
endB: {
position: 'absolute',
width: roundSize,
height: roundSize,
borderRadius: roundSize / 2,
borderColor: '#D6D7E6',
borderWidth: 1,
shadowColor: 'rgba(0,0,0,0.6)',
shadowRadius: 5,
shadowOpacity: 0.9,
backgroundColor: 'yellow',
top: 1,
justifyContent: 'center',
alignItems: 'center'
},
slide: {
position: 'absolute',
width: roundSize,
height: roundSize,
borderRadius: roundSize / 2,
borderColor: '#D6D7E6',
borderWidth: 1,
shadowColor: 'rgba(0,0,0,0.6)',
shadowRadius: 5,
shadowOpacity: 0.9,
backgroundColor: '#094D45',
bottom: 1
}
});
//默认props
Index.defaultProps = {
range: 1000, // 默认
startA: 0, // 起始
endB: width
};