I have a custom arc slider that works fine on the initial render. However, after initially moving it, it will not move again eg it only works once. The panresponder still detects gestures and through some debugging, i see that the position i release the handle is not the starting position for subsequent touches. I think my calculations are wrong, but cant see an issue
const ArcSlider = (props) => {
const [value, setValue] = useState(initialValue);
const moveStartValue = useRef(value);
const moveStartRadian = useRef(getRadianByValue(value));
const startCartesian = useRef(polarToCartesian(moveStartRadian.current));
const containerRef = useRef(null);
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => false,
onPanResponderGrant: handlePanResponderGrant,
onPanResponderMove: handlePanResponderMove,
onPanResponderRelease: () => console.log('PanResponder released'),
onPanResponderTerminationRequest: () => false,
onPanResponderTerminate: () => console.log('PanResponder terminated'),
})
).current;
function handlePanResponderGrant() {
moveStartValue.current = value;
moveStartRadian.current = getRadianByValue(value);
startCartesian.current = polarToCartesian(moveStartRadian.current);
}
function handlePanResponderMove(e, gestureState) {
let { x, y } = startCartesian.current;
x += gestureState.dx;
y += gestureState.dy;
const radian = cartesianToPolar(x, y);
const ratio = (moveStartRadian.current - radian) / ((Math.PI - openingRadian) * 2);
const diff = max - min;
let newValue;
if (step) {
newValue = moveStartValue.current + Math.round((ratio * diff) / step) * step;
} else {
newValue = moveStartValue.current + ratio * diff;
}
newValue = Math.max(min, Math.min(max, newValue));
setValue((curValue) => {
newValue = Math.abs(newValue - curValue) > diff / 4 ? curValue : newValue;
return Math.round(newValue);
});
if (onChange) {
onChange(newValue);
}
}
function polarToCartesian(radian) {
const distance = radius + getExtraSize() / 2;
const x = distance + radius * Math.sin(radian);
const y = distance + radius * Math.cos(radian);
return { x, y };
}
function cartesianToPolar(x, y) {
const distance = radius + getExtraSize() / 2;
if (x === distance) {
return y > distance ? 0 : Math.PI / 2;
}
const a = Math.atan((y - distance) / (x - distance));
return (x < distance ? (Math.PI * 3) / 2 : Math.PI / 2) - a;
}
function getRadianByValue(value) {
return (Math.PI - openingRadian) * 2 * (max - value) / (max - min) + openingRadian;
}
function getExtraSize() {
return Math.max(strokeWidth, (buttonRadius + buttonStrokeWidth) * 2);
}
const svgSize = radius * 2 + getExtraSize();
const startRadian = 2 * Math.PI - openingRadian;
const startPoint = polarToCartesian(startRadian);
const endPoint = polarToCartesian(openingRadian);
const currentRadian = getRadianByValue(value);
const curPoint = polarToCartesian(currentRadian);
return (
<View
{...panResponder.panHandlers}
ref={containerRef}
style={[styles.container, { width: svgSize - strokeWidth * 2, height: svgSize - strokeWidth * 2 }]}
>
<Svg width={svgSize} height={svgSize}>
<Defs>
<LinearGradient x1="0%" y1="100%" x2="100%" y2="0%" id="gradient">
{linearGradient.map((item, index) => (
<Stop key={index} offset={item.stop} stopColor={item.color} />
))}
</LinearGradient>
</Defs>
<Path
strokeWidth={strokeWidth}
stroke={backgroundTrackColor}
fill="none"
strokeLinecap="round"
d={`M${startPoint.x},${startPoint.y} A ${radius},${radius},0,1,1,${endPoint.x},${endPoint.y}`}
/>
<Path
strokeWidth={strokeWidth}
stroke="url(#gradient)"
fill="none"
strokeLinecap="round"
d={`M${startPoint.x},${startPoint.y} A ${radius},${radius},0,${startRadian - currentRadian >= Math.PI ? '1' : '0'
},1,${curPoint.x},${curPoint.y}`}
/>
<Circle
cx={curPoint.x}
cy={curPoint.y}
r={buttonRadius}
fill={buttonBorderColor}
stroke={buttonBorderColor}
strokeWidth={buttonStrokeWidth}
/>
</Svg>
</View>
);
};
2