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 trackColor activeColor = maxTrackColor; } else { // 横向双滑块A midTrackColor activeColor = midTrackColor; } } // 双滑块A } else if (range) { if (this.compareValue()) { if (vertical) { // 纵向双滑块A trackColor activeColor = maxTrackColor; } else { // 横向双滑块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