React Native 双滑块滑动条

import React, { Component } from 'react';
import {
    View,
    PanResponder,
    StyleSheet,
    Text,
    Dimensions,
    Animated,
    Easing
} from 'react-native';

/* 数据相关: 
 * 
 */
// const labels = ['0', '100', '200', '300', '400', '600', '1000', '不限'];
const labels = ['0', '200', '400', '600', '1000', '不限'];

/* 尺寸转换方法
 * _px: 以750位基准的转屏幕
 * _pos: 以屏幕为基准的转750
 */
const dw = Dimensions.get('window').width;
const ratioDeps750 = dw / 750;
const _px = function getRpx(value) {
    return Math.floor(value * ratioDeps750);
}
const _pos = function getPos(value) {
    return Math.floor(value / ratioDeps750)
}

/* 尺寸相关 
 * excludeWidth: 距离屏幕左右边界的距离,不触发点击滑动等事件的区域
 * unitWidth: 每一个单位的总宽度,有效区域等分后的宽度
 * labelWidth: 每一个单位的有效的点击宽度
 * sliderWidth: 滑块的宽度
 * sliderInitialLeft: 第一个滑块的left
 */
const excludeWidth = 75;
const unitWidth = (750 - excludeWidth * 2) / labels.length; //100
const labelWidth = unitWidth - 20; // 两个标签核心之间间隙 40
const sliderWidth = 60;
const halfUnitWidth = unitWidth / 2; // slider:halfUnitWidth - sliderWidth / 2; line:halfUnitWidth


const duration = 300;
const easing = Easing.linear;

export default class App extends Component {
    constructor(props) {
        super(props)
        let leftBoundaryForLeft = 0;
        let rightBoundaryForLeft = 0;
        let validPosForLeft = 0;
        let leftBoundaryForRight = 0;
        let rightBoundaryForRight = 0;
        let validPosForRight = 0;
        this.state = {
            leftIndex: 0,
            rightIndex: 0,
            leftPos: new Animated.Value(0),
            rightPos: new Animated.Value(0)
        }

        this._leftSliderPanResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => true,
            onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
            onPanResponderTerminationRequest: (evt, gestureState) => true,
            onShouldBlockNativeResponder: (evt, gestureState) => true,
            onPanResponderGrant: (evt, gestureState) => {
                // 左边界
                leftBoundaryForLeft = excludeWidth + halfUnitWidth;
                // 右边界
                rightBoundaryForLeft = this.state.rightIndex * unitWidth + excludeWidth + halfUnitWidth;
                validPosForLeft = _pos(gestureState.x0)
            },
            onPanResponderMove: (evt, gestureState) => {
                //手势中心点
                let centerX = _pos(gestureState.moveX)
                if (centerX >= leftBoundaryForLeft && centerX <= rightBoundaryForLeft - unitWidth) {
                    this.state.leftPos.setValue(centerX - excludeWidth);
                    validPosForLeft = centerX;
                    let posIndex = Math.floor((validPosForLeft - excludeWidth) / unitWidth);
                    this.setState({
                        leftIndex: posIndex
                    })
                }
            },
            onPanResponderRelease: (evt, gestureState) => {
                let posIndex = Math.floor((validPosForLeft - excludeWidth) / unitWidth);
                this.setPos({ leftIndex: posIndex })
            },
            onPanResponderTerminate: (evt, gestureState) => {

            },

        });
        this._rightSliderPanResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => true,
            onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
            onPanResponderTerminationRequest: (evt, gestureState) => true,
            onShouldBlockNativeResponder: (evt, gestureState) => true,
            onPanResponderGrant: (evt, gestureState) => {
                // 左边界
                leftBoundaryForRight = this.state.leftIndex * unitWidth + excludeWidth + halfUnitWidth;
                // 右边界
                rightBoundaryForRight = 750 - excludeWidth - halfUnitWidth;
                validPosForRight = _pos(gestureState.x0)
            },
            onPanResponderMove: (evt, gestureState) => {
                //手势中心点
                let centerX = _pos(gestureState.moveX)
                if (centerX >= leftBoundaryForRight + unitWidth && centerX <= rightBoundaryForRight) {
                    this.state.rightPos.setValue(centerX - excludeWidth);
                    validPosForRight = centerX;
                    let posIndex = Math.floor((validPosForRight - excludeWidth) / unitWidth);
                    this.setState({
                        rightIndex: posIndex
                    })
                }
            },
            onPanResponderRelease: (evt, gestureState) => {
                let posIndex = Math.floor((validPosForRight - excludeWidth) / unitWidth);
                this.setPos({ rightIndex: posIndex })
            },
            onPanResponderTerminate: (evt, gestureState) => {

            },
        });
        this._viewPanResponder = PanResponder.create({
            onStartShouldSetPanResponder: (evt, gestureState) => true,
            onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
            onMoveShouldSetPanResponder: (evt, gestureState) => true,
            onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
            onPanResponderTerminationRequest: (evt, gestureState) => true,
            onShouldBlockNativeResponder: (evt, gestureState) => true,
            onPanResponderGrant: (evt, gestureState) => {

            },
            onPanResponderMove: (evt, gestureState) => {

            },
            onPanResponderRelease: (evt, gestureState) => {
                if (gestureState.moveX) {
                    return;
                }
                let x = _pos(gestureState.x0);
                if ((x - excludeWidth) % unitWidth < (unitWidth - labelWidth) / 2 || (x - excludeWidth) % unitWidth > unitWidth - (unitWidth - labelWidth) / 2) {
                    console.log('无效点')
                    return;
                }
                let posIndex = Math.floor((x - excludeWidth) / unitWidth);

                let { leftIndex, rightIndex } = this.state;
                let leftPos = leftIndex * unitWidth + halfUnitWidth;
                let rightPos = rightIndex * unitWidth + halfUnitWidth;

                // 距离左边的点更近
                if (Math.abs(x - excludeWidth - leftPos) <= Math.abs(x - excludeWidth - rightPos)) {
                    this.setPos({ leftIndex: posIndex === rightIndex ? posIndex - 1 : posIndex })
                } else {
                    this.setPos({ rightIndex: posIndex === leftIndex ? posIndex + 1 : posIndex })
                }
            },
            onPanResponderTerminate: (evt, gestureState) => {

            },

        });
    }

    render() {
        let { leftPos, rightPos, leftIndex, rightIndex } = this.state;
        return (
            <View style={styles.container}>
                <View style={styles.content}>
                    <View {...this._viewPanResponder.panHandlers} style={styles.pan} >
                        <View style={styles.labels}>
                            {labels.map((item, index) => {
                                return <Text key={index} style={{ ...StyleSheet.flatten(styles.label), color: index < leftIndex || index > rightIndex ? '#898989' : '#3F70C1' }}>{item}</Text>
                            })}
                        </View>
                        <View style={styles.lines}>
                            <View style={styles.line} />
                            <Animated.View style={{
                                ...StyleSheet.flatten(styles.hl_line),
                                left: leftPos.interpolate({
                                    inputRange: [halfUnitWidth, labels.length * unitWidth - halfUnitWidth],
                                    outputRange: [_px(halfUnitWidth), _px(labels.length * unitWidth - halfUnitWidth)]//
                                }),
                                right: rightPos.interpolate({
                                    inputRange: [halfUnitWidth, labels.length * unitWidth - halfUnitWidth],
                                    outputRange: [_px(labels.length * unitWidth - halfUnitWidth), _px(halfUnitWidth)]//
                                })
                            }} />
                        </View>
                    </View>
                    <Animated.Image {...this._leftSliderPanResponder.panHandlers} source={images.slider} style={{
                        ...StyleSheet.flatten(styles.slider),
                        left: leftPos.interpolate({
                            inputRange: [halfUnitWidth, labels.length * unitWidth - halfUnitWidth],
                            outputRange: [_px(halfUnitWidth), _px(labels.length * unitWidth - halfUnitWidth)]//
                        }),
                    }} />
                    <Animated.Image {...this._rightSliderPanResponder.panHandlers} source={images.slider} style={{
                        ...StyleSheet.flatten(styles.slider),
                        left: rightPos.interpolate({
                            inputRange: [halfUnitWidth, labels.length * unitWidth - halfUnitWidth],
                            outputRange: [_px(halfUnitWidth), _px(labels.length * unitWidth - halfUnitWidth)]//
                        }),
                    }} />
                </View>
                <Text>{leftIndex}</Text>
                <Text>{rightIndex}</Text>
            </View>
        );
    }
    componentDidMount() {
        this.setPos({ leftIndex: 0, rightIndex: labels.length - 1 })
    }
    setPos({ leftIndex, rightIndex }) {
        if (leftIndex >= 0) {
            Animated.parallel([
                Animated.timing(this.state.leftPos, {
                    toValue: leftIndex * unitWidth + halfUnitWidth,
                    duration,
                    easing,
                })
            ]).start(() => {
                this.setState({
                    leftIndex: leftIndex
                })
            })
        }
        if (rightIndex >= 0) {
            Animated.parallel([
                Animated.timing(this.state.rightPos, {
                    toValue: rightIndex * unitWidth + halfUnitWidth,
                    duration,
                    easing,
                }),
            ]).start(() => {
                this.setState({
                    rightIndex: rightIndex
                })
            })
        }
    }
}

const styles = StyleSheet.create({
    container: {
        height: _px(180),
        backgroundColor: '#fff'
    },
    content: {
        backgroundColor: '#fff',
        height: _px(180),
        position: 'relative',
        marginHorizontal: _px(excludeWidth),
        marginVertical: _px(30),
        height: _px(120),
    },
    pan: {
        backgroundColor: '#fff'
    },
    labels: {
        height: _px(60),
        flexDirection: 'row',
        justifyContent: 'space-around',
        alignItems: 'center'
    },
    label: {
        width: _px(labelWidth),
        textAlign: 'center',
    },
    lines: {
        height: _px(60),
        justifyContent: 'center',
    },
    line: {
        position: 'absolute',
        backgroundColor: '#ddd',
        height: _px(4),
        left: _px(halfUnitWidth),
        right: _px(halfUnitWidth)
    },
    hl_line: {
        position: 'absolute',
        backgroundColor: '#3F70C1',
        height: _px(4),
    },
    slider: {
        width: _px(sliderWidth),
        height: _px(sliderWidth),
        resizeMode: 'contain',
        position: 'absolute',
        top: _px(90),
        transform: [{
            translateX: -_px(sliderWidth / 2),
        }, {
            translateY: -_px(sliderWidth / 2),
        }]
    }
});
const images = {
    slider: require('./img/slider.png')
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容