多色色轮跟随边缘问题

问题描述

我正在尝试实现一个多色色轮,它允许用户拖动多个选择器来更改颜色。

这里的问题是,当用户开始拖动其中一个选择器并一直拖动到轮子的边缘时,一旦选择器碰到边缘,拖动就会被取消。

所需的实现是在轮子外保持拖动,但让选择器跟随轮子的边缘,直到用户抬起拇指。

我已经实现了 outBounds 方法来检测手势是否在方向盘外,但是我所做的每一次尝试,尝试使用 Math.cos 和 Math.sin 设置选择器跟随边缘都失败了。

任何帮助将不胜感激。 谢谢。

代码:

import React,{ Component } from 'react';
import { Animated,Image,Dimensions,PanResponder,StyleSheet,View,Text } from 'react-native';
import colorsys from 'colorsys';
import wheelPng from './color_wheel.png';
import pickerPng from './picker.png';
import colors from '../../../common/colors';
import { isSmallerDevice } from '../../../helpers/layoutFunctions';

class ColorWheel extends Component {

    static defaultProps = {
        thumbSize: 40,initialColor: '#ffffff',onColorChange: () => { },}

    constructor(props) {
        super(props)
        this.state = {
            offset: { x: 0,y: 0 },currentColor: props.initialColor,colors: props.colors,pans: props.colors.map(color => new Animated.ValueXY()),activeIndex: null,radius: 0,renew: false,spring: new Animated.Value(1)
        }
    }

    static getDerivedStateFromProps(nextProps,prevState) {
        let update = { ...prevState };
        if (nextProps.colors && nextProps.colors.length && nextProps.colors !== prevState.colors) {
            if (nextProps.colors.length > prevState.colors.length) {
                update.colors = nextProps.colors;
                update.pans = [...prevState.pans,new Animated.ValueXY()];
                update.renew = true;
            }
        }
        return update;
    }

    componentDidUpdate(prevProps,prevState) {
        if (this.state.renew) {
            this.renewResponders();
            this.props.colors.forEach((col,index) => {
                this.forceUpdate(col);
            });
        }
    }

    componentDidMount = () => {
        this.renewResponders();
    }

    renewResponders = () => {
        const { colors } = this.props;
        this._panResponders = colors.map((color,index) => this.createResponder(color,index));
        this.setState({ renew: false });
    }

    createResponder = (color,index) => {
        const responder = PanResponder.create({
            onPanResponderTerminationRequest: () => false,onStartShouldSetPanResponderCapture: ({ nativeEvent }) => {
                this.state.spring.setValue(1.3);
                const { onSwiperDisabled } = this.props;
                onSwiperDisabled && onSwiperDisabled();

                if (this.outBounds(nativeEvent)) return
                this.updateColor({ index,nativeEvent })
                this.setState({ panHandlerReady: true })

                this.state.pans[index].setValue({
                    x: -this.state.left + nativeEvent.pageX - this.props.thumbSize / 2,y: -this.state.top + nativeEvent.pageY - this.props.thumbSize / 2 - 40,})
                return true
            },onStartShouldSetPanResponder: (e,gestureState) => true,onMoveShouldSetPanResponderCapture: () => true,onMoveShouldSetPanResponder: () => true,onPanResponderGrant: () => true,onPanResponderMove: (event,gestureState) => {
                this.setState({ activeIndex: index });
                if (this.outBounds(gestureState)) return

                this.resetPanHandler(index)
                return Animated.event(
                    [
                        null,{
                            dx: this.state.pans[index].x,dy: this.state.pans[index].y,},],{ listener: (ev) => this.updateColor({ nativeEvent: ev.nativeEvent,index }),useNativeDriver: false },)(event,gestureState)
            },onPanResponderRelease: ({ nativeEvent }) => {
                const { onSwiperEnabled } = this.props;
                onSwiperEnabled && onSwiperEnabled();


                this.state.pans[index].flattenOffset()
                const { radius } = this.calcPolar(nativeEvent)
                if (radius < 0.1) {
                    this.forceUpdate('#ffffff',index)
                }

                Animated.spring(this.state.spring,{
                    toValue: 1,stiffness: 400,damping: 10,useNativeDriver: false,}).start(() => {
                    this.setState({ panHandlerReady: true,activeIndex: null })
                });
                if (this.props.onColorChangeComplete) {
                    this.props.onColorChangeComplete({ index,color: this.state.hsv });
                }
            },})

        return { color,responder };
    }

    onLayout() {
        setTimeout(() => {
            this.self && this.measureOffset()
        },200);
    }

    measureOffset() {
        /*
        * const {x,y,width,height} = nativeEvent.layout
        * onlayout values are different than measureInWindow
        * x and y are the distances to its previous element
        * but in measureInWindow they are relative to the window
        */
        this.self.measureInWindow((x,height) => {
            const window = Dimensions.get('window')
            const absX = x % width
            const radius = Math.min(width,height) / 2
            const offset = {
                x: absX + width / 2,y: y % window.height + height / 2,}

            this.setState({
                offset,radius,height,top: y % window.height,left: absX,});
            //
            this.forceUpdate(this.state.currentColor)
        });

    }

    calcPolar(gestureState) {
        const {
            pageX,pageY,moveX,moveY,} = gestureState
        const [x,y] = [pageX || moveX,pageY || moveY]
        const [dx,dy] = [x - this.state.offset.x,y - this.state.offset.y]
        return {
            deg: Math.atan2(dy,dx) * (-180 / Math.PI),// pitagoras r^2 = x^2 + y^2 normalized
            radius: Math.sqrt(dy * dy + dx * dx) / this.state.radius,}
    }

    outBounds(gestureState) {
        const { radius } = this.calcPolar(gestureState);
        return radius > 1
    }

    resetPanHandler(index) {
        if (!this.state.panHandlerReady) {
            return
        }

        this.setState({ panHandlerReady: false })
        this.state.pans[index].setOffset({
            x: this.state.pans[index].x._value,y: this.state.pans[index].y._value,})
        this.state.pans[index].setValue({ x: 0,y: 0 })
    }

    calcCartesian(deg,radius) {
        const r = radius * this.state.radius; // was normalized
        const rad = Math.PI * deg / 180;
        const x = r * Math.cos(rad);
        const y = r * Math.sin(rad);
        return {
            left: this.state.width / 2 + x,top: this.state.height / 2 - y,}
    }

    updateColor = ({ nativeEvent,index }) => {
        const { deg,radius } = this.calcPolar(nativeEvent);
        const hsv = { h: deg,s: 100 * radius,v: 100 };
        this.setState({ hsv });
        this.props.onColorChange({ index,color: hsv });
    }

    forceUpdate = (color,index) => {
        const { h,s,v } = colorsys.hex2Hsv(color);
        const { left,top } = this.calcCartesian(h,s / 100);
        this.props.onColorChange({ color: { h,v },index });
        if (index)
            this.state.pans[index].setValue({
                x: left - this.props.thumbSize / 2,y: top - this.props.thumbSize / 2,});
        else
            this.props.colors.forEach((col,index) => {
                this.animatedUpdate(col,index);
            });
    }

    animatedUpdate = (color,s / 100)
        // this.setState({ currentColor: color })
        // this.props.onColorChange({ h,v })
        Animated.spring(this.state.pans[index],{
            toValue: {
                x: left - this.props.thumbSize / 2,y: top - this.props.thumbSize / 2 - 40,useNativeDriver: false
        }).start()
    }

    render() {
        const { radius,activeIndex } = this.state
        const thumbStyle = [
            styles.circle,this.props.thumbStyle,{
                position: 'absolute',width: this.props.thumbSize,height: this.props.thumbSize,borderRadius: this.props.thumbSize / 2,// backgroundColor: this.state.currentColor,opacity: this.state.offset.x === 0 ? 0 : 1,flexDirection: 'row',alignItems: 'center',alignContent: 'center',justifyContent: 'center',]

        const { colors } = this.props;

        // const panHandlers = this._panResponder && this._panResponder.panHandlers || {}
        return (
            <View
                ref={node => {
                    this.self = node
                }}
                onLayout={nativeEvent => this.onLayout(nativeEvent)}
                style={[styles.coverResponder,this.props.style]}>
                {!!radius && <Image
                    style={[styles.img,{
                        height: radius * 2,width: radius * 2
                    }]}
                    source={wheelPng}
                />}
                {colors && colors.map((color,index) =>
                    <Animated.View key={index} style={[this.state.pans[index].getLayout(),thumbStyle,{ zIndex: activeIndex === index ? 9 : 3,transform: [{ scale: activeIndex === index ? this.state.spring : 1 }] }]} {...this._panResponders && this._panResponders[index] && this._panResponders[index].responder.panHandlers}>
                        <Animated.Image
                            style={[
                                {
                                    height: this.props.thumbSize * 2,width: this.props.thumbSize * 2,resizeMode: 'contain',position: 'absolute',tintColor: '#000000'
                                }]}
                            source={pickerPng}
                        />
                        <Animated.View style={[styles.circle,{
                            position: 'absolute',top: -8,left: 2,backgroundColor: color,justifyContent: 'center'
                        }]} >
                            <Text style={isSmallerDevice ? styles.smallerDeviceCountText : styles.countText}>{index + 1}</Text>
                        </Animated.View>
                    </Animated.View>
                )}
            </View>
        )
    }
}

const styles = StyleSheet.create({
    coverResponder: {
        flex: 1,justifyContent: 'center'
    },img: {
        alignSelf: 'center',circle: {
        position: 'absolute',backgroundColor: '#000000',// borderWidth: 3,// borderColor: '#EEEEEE',elevation: 3,shadowColor: 'rgb(46,48,58)',shadowOffset: { width: 0,height: 2 },shadowOpacity: 0.8,shadowRadius: 2,countText: {
        flex: 1,textAlign: 'center',fontFamily: 'Rubik-Bold',fontSize: 20,color: colors.titleMain
    },smallerDeviceCountText: {
        flex: 1,fontSize: 16,color: colors.titleMain
    }
})

export default ColorWheel;

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...