2024-03-16 14:11:43 +08:00

608 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { PureComponent } from 'react';
import { Animated, Image, StyleSheet, PanResponder, View, Text, Platform } from 'react-native';
import variables from '../../common/styles/variables';
import sliderStyles from './styles';
import Coord from './Coord';
const thumbImage = require('./images/rectangle.png');
const otherThumbImage = require('./images/rectangle.png');
const styles = StyleSheet.create(sliderStyles);
export default class Slider extends PureComponent {
constructor(props) {
super(props);
/**
* 通过props获取滑块对应的value值
*/
this.getValueByProps = (isOther) => {
const { value, range } = this.props;
if (range && value instanceof Array) {
if (isOther) {
return value[1];
}
return value[0];
}
return value;
};
/**
* 判断用户触控的区域是否在滑块上
*/
this.thumbTouchCheck = (e) => {
const nativeEvent = e.nativeEvent;
const { range } = this.props;
if (range) {
const otherThumbCoord = this.getThumbCoord(range);
const otherCheckResult = otherThumbCoord.contain(nativeEvent.locationX, nativeEvent.locationY);
if (otherCheckResult) {
this.isOther = true;
return otherCheckResult;
}
}
const ThumbCoord = this.getThumbCoord();
const checkResult = ThumbCoord.contain(nativeEvent.locationX, nativeEvent.locationY);
if (checkResult) {
this.isOther = false;
return checkResult;
}
return false;
};
this.getThumbCoord = (isOther) => {
const { thumbSize, otherThumbSize, containerSize } = this.state;
let currThumb = thumbSize;
if (isOther) {
currThumb = otherThumbSize;
}
const { vertical } = this.props;
let x = null;
let y = null;
if (vertical) {
x = (containerSize.width - currThumb.width) / 2;
y = this.getThumbLeft(this.getCurrentValue(isOther));
}
else {
x = this.getThumbLeft(this.getCurrentValue(isOther));
y = (containerSize.height - currThumb.height) / 2;
}
return new Coord(x, y, currThumb.width, currThumb.height);
};
/**
* 滚动状态响应
*/
this.scroll = (gestureState) => {
if (this.props.disabled) {
return;
}
if (this.isOther) {
const isOtherValue = this.getValue(gestureState, this.isOther);
this.setCurrentValue(isOtherValue, this.isOther);
}
else {
const value = this.getValue(gestureState);
this.setCurrentValue(value);
}
};
this.touchStart = (e) => {
this.oldValue = this.state.value.__getValue();
this.oldOtherValue = this.state.otherValue.__getValue();
return this.thumbTouchCheck(e);
};
this.pressStart = () => {
if (this.isOther) {
this.otherPreviousLeft = this.getThumbLeft(this.getCurrentValue(this.isOther));
}
else {
this.previousLeft = this.getThumbLeft(this.getCurrentValue());
}
};
this.lastMove = (_, gestureState) => {
this.scroll(gestureState);
};
this.touchEnd = (_, gestureState) => {
this.scroll(gestureState);
if (this.oldValue !== this.getCurrentValue()) {
this.triggerEvent('onChange');
}
if (this.props.range && this.oldOtherValue !== this.getCurrentValue(true)) {
this.triggerEvent('onChange');
}
};
this.measureContainer = (x) => {
this.handleMeasure('containerSize', x);
};
this.measureTrack = (x) => {
this.handleMeasure('trackSize', x);
};
this.measureThumb = (x) => {
this.handleMeasure('thumbSize', x);
};
this.measureOtherThumb = (x) => {
this.handleMeasure('otherThumbSize', x);
};
this.handleMeasure = (name, x) => {
const { width, height } = x.nativeEvent.layout;
const size = { width, height };
const currentSize = this.state[name];
if (currentSize &&
width === currentSize.width &&
height === currentSize.height) {
return;
}
const newState = {
[name]: size
};
// 双滑块
this.setState(newState);
};
/**
* 获取可滑动长度
*/
this.getScrollLength = () => {
const { vertical } = this.props;
const { trackSize } = this.state;
if (vertical) {
return trackSize.height;
}
else {
return trackSize.width;
}
};
/**
* 获取滑块的坐标的宽度
* 如果是横向slider则取width,纵向取height
*/
this.getThumbOffset = (isOther) => {
const { vertical } = this.props;
const { thumbSize, otherThumbSize } = this.state;
if (vertical && isOther) {
return otherThumbSize.height;
}
else if (!vertical && isOther) {
return otherThumbSize.width;
}
else if (vertical && !isOther) {
return thumbSize.height;
}
else if (!vertical && !isOther) {
return thumbSize.height;
}
};
/**
* 获取当前value值所占的百分比
*/
this.getRatio = (value) => {
const { min, max } = this.props;
if (max === min) {
return 0;
}
return (value - min) / (max - min);
};
/**
* 滑块在滑动轴上的偏移量
* value => x
*/
this.getThumbLeft = (value) => {
const { vertical } = this.props;
let ratio = this.getRatio(value);
if (vertical) {
ratio = 1 - ratio;
}
const scrollLength = this.getScrollLength();
return (ratio * scrollLength);
};
/**
* 互斥prop
* 刻度属性只有正在非纵向轴、非双滑块下才生效
*/
this.showStep = () => {
const { vertical, step, range } = this.props;
if (!range && !vertical && step) {
return true;
}
return false;
};
/**
* 获取滑动位置所对应的value值和getThumbLeft方法对应
* x => value
*/
this.getValue = (gestureState, isOther) => {
let previousLeft = this.previousLeft;
if (isOther) {
previousLeft = this.otherPreviousLeft;
}
const scrollLength = this.getScrollLength();
const { step, min, max, vertical } = this.props;
let thumbLeft = null;
if (vertical) {
thumbLeft = previousLeft + gestureState.dy;
}
else {
thumbLeft = previousLeft + gestureState.dx;
}
let ratio = thumbLeft / scrollLength;
if (vertical) {
ratio = 1 - ratio;
}
if (this.showStep()) {
return Math.max(min, Math.min(max, min + Math.round(ratio * (max - min) / step) * step));
}
return Math.max(min, Math.min(max, ratio * (max - min) + min));
};
/**
* 获取滑块的value值
*/
this.getCurrentValue = (isOther) => {
let value = this.state.value;
if (isOther) {
value = this.state.otherValue;
}
return value.__getValue();
};
this.setCurrentValue = (value, isOther) => {
if (isOther) {
this.setState({
otherTip: `${Math.round(value)}`
});
this.state.otherValue.setValue(value);
}
else {
this.setState({
tip: `${Math.round(value)}`
});
this.state.value.setValue(value);
}
};
this.triggerEvent = (event) => {
if (this.props[event]) {
let args = [Math.round(this.getCurrentValue())];
const { range } = this.props;
if (range) {
if (this.compareValue()) {
args.unshift(Math.round(this.getCurrentValue(range)));
}
else {
args.push(Math.round(this.getCurrentValue(range)));
}
this.props[event](args);
}
else {
this.props[event](args[0]);
}
}
};
/**
* 默认滑块的的滑块图片渲染
*/
this.renderThumbImage = (isOther) => {
if (!isOther && !thumbImage)
return;
if (isOther && !otherThumbImage)
return;
const { renderThumb } = this.props;
const { thumbSize } = this.state;
if (typeof renderThumb === 'function') {
return renderThumb(isOther);
}
return React.createElement(Image, { style: [{width:thumbSize.width-20,height:thumbSize.height-50}, {translateX:thumbSize.width/2 - 10,position:"absolute",bottom:0, borderRadius: this.getThumbOffset() / 2 }], source: isOther ? otherThumbImage : thumbImage });
};
/**
* 刻度模块的渲染
*/
this.renderMarks = () => {
const { step, marks, min, max, thumbSize } = this.props;
if (!this.showStep() || !marks) {
return null;
}
const maxStep = Math.ceil(Math.abs((max - min) / step)) + 1;
let currStep = 0;
const markViewArr = [];
while (maxStep > currStep) {
if (React.isValidElement(marks[currStep])) {
markViewArr.push(marks[currStep]);
}
else {
markViewArr.push(React.createElement(View, { key: currStep, style: { width: thumbSize, alignItems: 'center' } },
React.createElement(Text, { style: styles.markItemText }, marks[currStep]),
React.createElement(View, { style: styles.markItemLine })));
}
currStep += 1;
}
return (React.createElement(View, { style: styles.markContainer }, markViewArr));
};
/**
* 渲染滑块的toopTip提示
*/
this.renderThumbToolTip = (isOther) => {
const { showTip,thumbSize, renderTip } = this.props;
if (!showTip) {
return;
}
const { tip, otherTip } = this.state;
return (React.createElement(View, { style: [
styles.tip,
{ bottom:20 + (thumbSize - 30) / 2}
] },
React.createElement(View, { key: 1, style: styles.tipContent }, renderTip ? renderTip(isOther ? otherTip : tip)
:
React.createElement(Text, { style: styles.tipText }, isOther ? otherTip : tip)),
React.createElement(View, { key: 2, style: styles.tipIcon })));
};
/**
* 滑动的起始和结束x值
*/
this.getScrollRange = () => {
const scrollLength = this.getScrollLength();
return [0, scrollLength];
};
/**
* 滑块渲染
*/
this.renderThumb = (isOther) => {
const { vertical, range } = this.props;
if (isOther && !range)
return;
const { value, otherValue, thumbSize, otherThumbSize } = this.state;
let currValue = value;
let currThumb = thumbSize;
let measureFn = this.measureThumb;
if (isOther) {
currValue = otherValue;
currThumb = otherThumbSize;
measureFn = this.measureOtherThumb;
}
const thumbLeft = this.getThumbLeft(currValue.__getValue());
let thumbStyle = {
transform: [{ translateX: thumbLeft+7.5 + (currThumb.width - 20) / 2 }, { translateY: -20 + (currThumb.width -50) / 2 }],
alignItems: 'center',
};
if (vertical) {
thumbStyle.transform = [{ translateX: 0 }, { translateY: thumbLeft }];
}
if (this.showAndroidTip) {
return (React.createElement(Animated.View, { onLayout: measureFn, renderToHardwareTextureAndroid: true, style: [
styles.thumb,
thumbStyle,
] },
this.renderThumbToolTip(isOther),
React.createElement(View, { style: [
currThumb,
{ }
] }, this.renderThumbImage(isOther))));
}
return (React.createElement(Animated.View, { onLayout: measureFn, renderToHardwareTextureAndroid: true, style: [
styles.thumb,
currThumb,
thumbStyle,
] },
this.renderThumbToolTip(isOther),
this.renderThumbImage(isOther)));
};
/**
* 两个滑块值比较滑块A的值是否大于B
*/
this.compareValue = () => {
const { range } = this.props;
return range && this.getCurrentValue() >= this.getCurrentValue(true);
};
/**
* 滑轨色值计算
* 双滑块模式下,需要根据两个滑块的值大小结果互换色值
* 垂直滑块模式下,因为滑块的渲染是从顶部计算的,所以滑块需要使用反向色值来实现从底部滑动的效果
* 假设滑块A,B
*/
this.getTrackColor = (isOther) => {
const { minTrackColor, midTrackColor, maxTrackColor, range, vertical } = this.props;
let activeColor = '';
// 双滑块B
if (isOther) {
if (this.compareValue()) {
if (vertical) {
// 纵向双滑块A>=B B => midTrackColor
activeColor = midTrackColor;
}
else {
// 横向双滑块A>=B B => minTrackColor
activeColor = minTrackColor;
}
}
else {
if (vertical) {
// 纵向双滑块A<B B => trackColor
activeColor = maxTrackColor;
}
else {
// 横向双滑块A<B B => midTrackColor
activeColor = midTrackColor;
}
}
// 双滑块A
}
else if (range) {
if (this.compareValue()) {
if (vertical) {
// 纵向双滑块A<B A => trackColor
activeColor = maxTrackColor;
}
else {
// 横向双滑块A<B A => midTrackColor
activeColor = midTrackColor;
}
}
else {
if (vertical) {
// 纵向双滑块A>=B A => midTrackColor
activeColor = midTrackColor;
}
else {
// 横向单滑块A>=B A => minTrackColor
activeColor = minTrackColor;
}
}
// 单滑块
}
else {
if (vertical) {
// 纵向单滑块 A => trackColor
activeColor = maxTrackColor;
}
else {
// 横向单滑块 A => minTrackColor
activeColor = minTrackColor;
}
}
return [activeColor];
};
/**
* 默认滑块划过的滑轨
*/
this.renderMinimumTrack = (isOther) => {
const { disabled, range, vertical, trackWeight } = this.props;
if (isOther && !range)
return;
const { value, otherValue } = this.state;
let currValue = value;
let minimumTrackColor = null;
let currKey = 'minTrack';
if (isOther) {
currValue = otherValue;
currKey = 'otherMinTrack';
}
const minimumTrackWidth = this.getThumbLeft(currValue.__getValue());
// 滑轨颜色值设定
const trackColors = this.getTrackColor(isOther);
minimumTrackColor = trackColors[0];
const minimumTrackStyle = {
position: 'absolute',
backgroundColor: minimumTrackColor
};
let trackStyle = null;
if (vertical) {
minimumTrackStyle.height = minimumTrackWidth;
minimumTrackStyle.width = this.props.trackWeight;
trackStyle = { marginVertical: this.getThumbOffset(isOther) / 2 };
}
else {
minimumTrackStyle.height = this.props.trackWeight;
minimumTrackStyle.width = minimumTrackWidth;
trackStyle = { marginHorizontal: this.getThumbOffset(isOther) / 2 };
}
return (React.createElement(Animated.View, { key: currKey, renderToHardwareTextureAndroid: true, style: [{ borderRadius: trackWeight / 2 }, minimumTrackStyle, trackStyle] }));
};
this.getTrackStyle = () => {
const { range, vertical, maxTrackColor, minTrackColor, trackWeight, thumbSize } = this.props;
const trackStyle = { backgroundColor: maxTrackColor };
let marginArr = ['marginLeft', 'marginRight', 'marginHorizontal', 'height'];
const rest = thumbSize - trackWeight;
const spacing = rest > 0 ? Math.ceil(rest / 2) : 0;
if (vertical) {
marginArr = ['marginTop', 'marginBottom', 'marginVertical', 'width'];
trackStyle.flex = 1;
trackStyle.alignItems = 'flex-start';
trackStyle.backgroundColor = minTrackColor;
trackStyle.marginHorizontal = spacing;
}
else {
trackStyle.marginVertical = spacing;
}
// 样式处理
if (range) {
trackStyle[marginArr[0]] = this.getThumbOffset() / 2;
trackStyle[marginArr[1]] = this.getThumbOffset(true) / 2;
}
else {
trackStyle[marginArr[2]] = this.getThumbOffset() / 2;
}
trackStyle[marginArr[3]] = this.props.trackWeight;
return trackStyle;
};
this.renderTracks = () => {
const { vertical, trackWeight } = this.props;
const trackStyle = this.getTrackStyle();
// 如果value > oldValue则代表两个滑块滑动位置互换则更新渲染层级
let tracks = [
React.createElement(View, { style: [{ borderRadius: trackWeight / 2 }, trackStyle], onLayout: this.measureTrack })
];
// vertical的值和this.compareValue()值相同时,次滑块轴在底层,反之主滑块轴在底层
if (vertical === this.compareValue()) {
tracks.push(this.renderMinimumTrack(true));
tracks.push(this.renderMinimumTrack());
}
else {
tracks.push(this.renderMinimumTrack());
tracks.push(this.renderMinimumTrack(true));
}
return tracks;
};
this.state = {
containerSize: { width: 0, height: 0 },
trackSize: { width: 0, height: 0 },
thumbSize: { width: props.thumbSize, height: props.thumbSize },
otherThumbSize: { width: props.thumbSize, height: props.thumbSize },
value: new Animated.Value(this.getValueByProps()),
otherValue: new Animated.Value(this.getValueByProps(true)),
tip: `${this.getValueByProps()}`,
otherTip: `${this.getValueByProps(true)}`
};
this.isOther = false;
this.showAndroidTip = this.props.showTip && Platform.OS === 'android';
}
componentWillMount() {
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: this.touchStart,
onMoveShouldSetPanResponder: _ => false,
onPanResponderGrant: this.pressStart,
onPanResponderMove: this.lastMove,
onPanResponderRelease: this.touchEnd,
onPanResponderTerminationRequest: _ => false,
onPanResponderTerminate: this.touchEnd
});
}
componentWillReceiveProps(nextProps) {
let newValue = 0;
let newOtherValue = 0;
const { range } = this.props;
if (range && nextProps.value instanceof Array) {
newValue = nextProps.value[0];
newOtherValue = nextProps.value[1];
}
else {
newValue = nextProps.value;
}
if (this.getValueByProps() !== newValue) {
this.setCurrentValue(newValue);
}
if (range && this.getValueByProps(true) !== newOtherValue) {
this.setCurrentValue(newOtherValue, range);
}
}
render() {
const { style, vertical } = this.props;
return (React.createElement(View, { style: [
{ flexDirection: vertical ? 'row' : 'column' },
style
] },
this.renderMarks(),
React.createElement(View, { style: {
alignItems: vertical ? 'center' : undefined,
justifyContent: vertical ? undefined : 'center'
}, onLayout: this.measureContainer },
this.renderTracks(),
this.renderThumb(),
this.renderThumb(true),
React.createElement(View, Object.assign({ renderToHardwareTextureAndroid: true, style: styles.touchArea }, this.panResponder.panHandlers)))));
}
}
Slider.defaultProps = {
value: 0,
min: 0,
max: 1,
step: 0,
maxTrackColor: variables.mtdFillGray,
minTrackColor: variables.mtdBrandPrimary,
midTrackColor: variables.mtdBrandDanger,
range: false,
vertical: false,
showTip: false,
trackWeight: 5,
thumbSize: 30
};
//# sourceMappingURL=Slider.js.map