问题描述
我正在尝试实现一个多色色轮,它允许用户拖动多个选择器来更改颜色。
这里的问题是,当用户开始拖动其中一个选择器并一直拖动到轮子的边缘时,一旦选择器碰到边缘,拖动就会被取消。
所需的实现是在轮子外保持拖动,但让选择器跟随轮子的边缘,直到用户抬起拇指。
我已经实现了 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 (将#修改为@)