import * as React from 'react';
import {
Dimensions,
PanResponder,
PanResponderGestureState,
Text,
View,
ViewStyle,
} from 'react-native';
import { slider } from '../../_styles/themes/default.components';
import { scaleFont } from '../../_styles/themes/responsive';
const screenWidth = Dimensions.get('window').width;
export interface IMDSliderProps {
styles?: IMDSliderStyle;
width?: number; // 组件宽度
circleSize?: number; // 滑块大小
bothway?: boolean; // 是否启用双向滑动
range?: number; // 范围,从0开始
startValue?: number; // 起始值
endValue?: number; // 结束值
min?: number; // 可拖动的最小值
max?: number; // 可拖动的最大值
step?: number; // 步长
format?: string; // 格式化
formatColor?: string; // 格式化颜色
disabled?: boolean; // 是否可滑动
onChange: (startValue: number, endAmount: number) => void; // 回调
}
interface IMDSliderState {
range: number;
startValue: number;
endValue: number;
start: number;
end: number;
resetFocus: boolean;
}
export interface IMDSliderStyle {
container?: ViewStyle;
hanlder?: ViewStyle;
hanlderDisabled?: ViewStyle;
circle?: ViewStyle;
}
export const MDSelectorStyles: IMDSliderStyle = {
container: {
height: slider.height,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: slider.bg,
},
hanlder: {
backgroundColor: slider.bgHandler,
height: slider.progressBarHeight,
},
hanlderDisabled: {
backgroundColor: slider.bgHandlerDisabled,
height: slider.progressBarHeight,
},
circle: {
position: 'absolute',
borderColor: slider.circleBorderColor,
borderWidth: scaleFont(1),
backgroundColor: slider.bg,
},
};
export default class MDSlider extends React.Component<
IMDSliderProps,
IMDSliderState
> {
public static defaultProps: IMDSliderProps = {
styles: MDSelectorStyles,
circleSize: 22,
width: screenWidth - 40,
bothway: false,
range: 100,
startValue: 0,
endValue: 50,
min: 0,
step: 1,
disabled: false,
format: '',
formatColor: slider.formatColor,
onChange () {},
};
constructor (props: IMDSliderProps) {
super(props);
const { width, circleSize, range, startValue, endValue } = this.props;
const scale = (width! - circleSize!) / range!;
const start = startValue === 0 ? 0 : scale * startValue!;
const end = scale * endValue!;
this.state = {
range: range!,
startValue: startValue!,
endValue: endValue!,
start,
end,
resetFocus: false,
};
}
private startPanResponder = PanResponder.create({});
private endPanResponder = PanResponder.create({});
public componentWillReceiveProps (nextProps: any) {
const { range, startValue, endValue } = this.props;
if (
range !== nextProps.range ||
startValue !== nextProps.startValue ||
endValue !== nextProps.endValue
) {
const scale = (nextProps.width - nextProps.circleSize) / nextProps.range;
const start =
nextProps.startValue === 0 ? 0 : scale * nextProps.startValue;
const end = scale * nextProps.endValue;
this.setState({
range: nextProps.range,
startValue: nextProps.startValue,
endValue: nextProps.endValue,
start,
end,
});
}
}
public componentWillMount () {
const { disabled } = this.props;
this.startPanResponder = this.initStartPanResponder(disabled!);
this.endPanResponder = this.initEndPanResponder(disabled!);
}
public render () {
const { start, end, startValue, endValue, resetFocus } = this.state;
const { styles, width, circleSize, disabled, bothway, format } = this.props;
const startTip = this.renderFormat(
false,
bothway!,
startValue,
start,
format!
);
const startCircle = this.renderCircle(
true,
bothway!,
circleSize!,
start,
end
);
const endTip = this.renderFormat(true, bothway!, endValue, end, format!);
const endCircle = this.renderCircle(false, false, circleSize!, start, end);
const containerBar = this.renderContainerBar(width!, disabled!, start, end);
return (
{startTip}
{endTip}
{containerBar}
{resetFocus ? startCircle : endCircle}
{resetFocus ? endCircle : startCircle}
);
}
private initStartPanResponder (disabled: boolean) {
return PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderTerminate: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
this.forceUpdate();
},
onPanResponderMove: (evt, gestureState) => {
if (disabled) {
return;
}
const { offset, amount } = this.countStepInfo(gestureState, true);
this.setState({
start: offset,
startValue: amount,
});
},
onPanResponderRelease: (evt, gestureState) => {
if (disabled) {
return;
}
const { offset, amount } = this.countStepInfo(gestureState, true);
this.setState(
{
start: offset,
startValue: amount,
},
() => {
this.props.onChange(this.state.startValue, this.state.endValue);
}
);
},
});
}
private initEndPanResponder (disabled: boolean) {
return PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderTerminationRequest: (evt, gestureState) => true,
onPanResponderTerminate: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
this.forceUpdate();
},
onPanResponderMove: (evt, gestureState) => {
if (disabled) {
return;
}
const { offset, amount } = this.countStepInfo(gestureState, false);
this.setState({
end: offset,
endValue: amount,
});
},
onPanResponderRelease: (evt, gestureState) => {
if (disabled) {
return;
}
const { offset, amount } = this.countStepInfo(gestureState, false);
if (this.state.endValue === 0 || this.state.endValue === this.props.min!) {
this.setState({resetFocus: true});
} else {
this.setState({resetFocus: false});
}
this.setState(
{
end: offset,
endValue: amount,
},
() => {
this.props.onChange(this.state.startValue, this.state.endValue);
}
);
},
});
}
private renderFormat (
isEnd: boolean,
bothway: boolean,
amount: number,
offset: number,
format: string
) {
const formatView = (
{format + amount}
);
return isEnd ? formatView : bothway ? formatView : null;
}
private renderCircle (
isStart: boolean,
bothway: boolean,
circleSize: number,
start: number,
end: number
) {
if (isStart && !bothway) {
return null;
}
const styles = this.props.styles || {};
const handler = isStart
? this.startPanResponder.panHandlers
: this.endPanResponder.panHandlers;
return (
);
}
private renderContainerBar (
width: number,
disabled: boolean,
start: number,
end: number
) {
const styles = this.props.styles || {};
return (
);
}
private countStepInfo (
gestureState: PanResponderGestureState,
isStart: boolean
) {
const { width, circleSize, range, step, min } = this.props;
const max = this.props.max || this.props.range!;
const { start, end } = this.state;
const scale = (width! - circleSize!) / range!;
const realStep = step! * scale;
let offset = gestureState.moveX;
let amount = 0;
if (isStart) {
offset = offset >= end ? end : gestureState.moveX - 2 * circleSize!;
offset = parseFloat('' + offset / realStep) * realStep;
amount = Math.ceil(start / scale);
if (amount > max!) {
amount = max!;
offset = Math.ceil(max! * scale);
}
if (offset <= circleSize!) {
offset = 0;
amount = 0;
}
if (amount < min!) {
amount = min!;
offset = Math.ceil(min! * scale);
}
} else {
offset = offset <= start ? start : gestureState.moveX - 2 * circleSize!;
offset = parseFloat('' + offset / realStep) * realStep;
amount = Math.ceil(offset / scale);
if (amount < min!) {
amount = min!;
offset = Math.ceil(min! * scale);
}
if (amount > max!) {
amount = max!;
offset = Math.ceil(max! * scale);
}
if (offset >= width! - circleSize!) {
offset = width! - circleSize!;
amount = range!;
}
}
return {
offset,
amount,
};
}
}