Я успешно собрал компонент кругового слайдера из различных частично документированных фрагментов кода (в основном здесь ).Я немного прибрался, и теперь у меня есть кое-что, что я могу использовать в качестве скользящего диска управления, в котором я могу установить произвольный максимальный угол для скользящей области:
Однако я хочу больше гибкости - я хочу иметь возможность выбрать произвольный начальный угол.Это позволит мне создавать циферблаты, например, элементы управления приборной панели автомобиля.Ниже показана моя попытка заставить это работать.Как вы можете видеть, это портит обработку касаний:
Ясно, что мне нужно компенсировать вращение в обработке касаний.
Я достигаю всего этого путем инкапсуляции SVG-компонента (содержащего всю графику и текст, показанный здесь) в виде.Для примера, с которым у меня возникли проблемы, я поворачиваю вид контейнера (стараясь передать это значение поворота и тексту, чтобы повернуть этот текст обратно в вертикальное положение).
Компонент машинописного текста:
import React, { Component } from 'react';
import ReactNative, { PanResponder, View, Dimensions } from 'react-native';
import Svg, {
Path,
Circle,
G,
Text,
Defs,
LinearGradient,
Stop,
Linecap
} from 'react-native-svg';
interface Props {
value: number;
dialRadius: number;
btnRadius: number;
startCoord: number;
startGradient: string;
endGradient: string;
dialWidth: number;
cap: Linecap;
dialBgWidth: number;
backgroundColor: string;
textSize: number;
textColor: string;
showValue: boolean;
btnFill: string;
maxAngle: number;
rotationOffset: number;
onValueChange (angle: number): void;
}
interface State {
angle: number;
xCenter: number;
yCenter: number;
}
export default class CircularSlider extends Component<Props, State> {
static defaultProps = {
btnRadius: 10,
dialRadius: 80,
dialWidth: 20,
textColor: 'white',
textSize: 30,
value: 0,
showValue: true,
startGradient: '#12D8FA',
endGradient: '#12D8FA',
backgroundColor: 'white',
startCoord: 0,
cap: 'butt',
btnFill: 'transparent',
dialBgWidth: 20,
maxAngle: 360,
onValueChange: null,
rotationOffset: 0
};
_panResponder: any;
circleSlider: any;
container: any;
constructor (props: any) {
super(props);
this.state = {
angle: this.props.value,
xCenter: 0,
yCenter: 0
};
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (e, gs) => true,
onStartShouldSetPanResponderCapture: (e, gs) => true,
onMoveShouldSetPanResponder: (e, gs) => true,
onMoveShouldSetPanResponderCapture: (e, gs) => true,
onPanResponderMove: (e, gs) => {
let xOrigin =
this.state.xCenter - (this.props.dialRadius + this.props.btnRadius);
let yOrigin =
this.state.yCenter - (this.props.dialRadius + this.props.btnRadius);
let a = this.cartesianToPolar(gs.moveX - xOrigin, gs.moveY - yOrigin);
this.setState({ angle: Math.min(a, this.props.maxAngle) });
}
});
}
polarToCartesian (angle: number) {
let r = this.props.dialRadius;
let hC = this.props.dialRadius + this.props.btnRadius;
let a = ((angle - (90)) * Math.PI) / (180.0) ;
let x = hC + r * Math.cos(a);
let y = hC + r * Math.sin(a);
return { x, y };
}
cartesianToPolar (x: number, y: number) {
let hC = this.props.dialRadius + this.props.btnRadius;
if (x === 0) {
return y > hC ? 0 : 180;
} else if (y === 0) {
return x > hC ? 90 : 270;
} else {
return (
Math.round((Math.atan((y - hC) / (x - hC)) * 180) / Math.PI) +
(x >= hC ? 90 : 270)
);
}
}
handleMeasure = (ox: number, oy: number, width: number, height: number, px: number, py: number) => {
this.setState({
xCenter: px + (this.props.dialRadius + this.props.btnRadius),
yCenter: py + (this.props.dialRadius + this.props.btnRadius)
});
}
handleOnLayout = () => {
this.circleSlider.measure(this.handleMeasure);
}
render () {
let {
btnRadius,
dialRadius,
rotationOffset,
textSize
} = this.props;
let width = (dialRadius + btnRadius) * 2;
let startCoord = this.polarToCartesian(this.props.startCoord);
let endCoord = this.polarToCartesian(this.state.angle);
let maxAngle = this.polarToCartesian(this.props.maxAngle);
return (
<View
ref={r => this.container = r }
style={{ flex: 1, margin: 50, transform: [{ rotate: `${rotationOffset}deg` }] }}
>
<Svg
onLayout={this.handleOnLayout}
ref={r => this.circleSlider = r}
width={width}
height={width}
>
<Defs>
<LinearGradient id='gradient1' x1='0%' y1='0%' x2='100%' y2='0%'>
<Stop offset='0%' stopColor={this.props.startGradient} />
<Stop offset='100%' stopColor={this.props.endGradient} />
</LinearGradient>
</Defs>
<Text
transform={`rotate(${-rotationOffset}, 90, 90)`} // compensate for rotated container
x={width / 2}
y={width / 2 + textSize / 4}
fontSize={textSize}
fill={this.props.textColor}
textAnchor='middle'
>
{this.props.showValue &&
this.props.onValueChange(this.state.angle) + ''}
</Text>
<G>
<Path
stroke={this.props.backgroundColor}
strokeWidth={this.props.dialWidth}
fill='none'
strokeLinecap={this.props.cap}
strokeLinejoin='round'
d={`M${startCoord.x} ${startCoord.y} A ${dialRadius} ${dialRadius} 0 ${
(this.props.startCoord + 180) % 360 > (this.props.maxAngle) ? 0 : 1
} 1 ${maxAngle.x} ${maxAngle.y}`}
/>
<Path
stroke={'url(#gradient1)'}
strokeWidth={this.props.dialWidth}
fill='none'
strokeLinecap={this.props.cap}
strokeLinejoin='round'
d={`M${startCoord.x} ${startCoord.y} A ${dialRadius} ${dialRadius} 0 ${
(this.props.startCoord + 180) % 360 > this.state.angle ? 0 : 1
} 1 ${endCoord.x} ${endCoord.y}`}
/>
<G x={endCoord.x - btnRadius} y={endCoord.y - btnRadius}>
<Circle
r={btnRadius}
cx={btnRadius}
cy={btnRadius}
fill={this.props.btnFill}
{...this._panResponder.panHandlers}
/>
</G>
</G>
</Svg>
</View>
);
}
}
И компонент используется таким образом, на данный момент: <CircularSlider rotationOffset={-135} maxAngle={270} onValueChange={(angle) => angle } />
(или без смещения для первого примера GIF).
У меня естьпробовал:
- Регулировка текущего угла перед вставкой в функцию PolarToCartesian (), но это стало очень грязным и не имело смысла, по крайней мере, так, как я это сделал.
- Экспериментируйте с
this.container.measure(this.handleMeasure)
вместо this.circleSlider.measure(this.handleMeasure)
и this.circleSlider.measureLayout(ReactNative.findNodeHandle(this.container), this.handleMeasure)
и удаляя дополнительные параметры px: number, py: number
из handleMeasure и считывая смещение (ox и oy), но это не дает правильного вида движения вообще...
Так как мне компенсировать поворот вида контейнера при обработке касаний?ИЛИ Есть ли совершенно другой подход к этому, который дает мне нужное мне поведение?
(кстати, используя React Native 0.57.0 и response-native-svg ^ 7.0.2)