赞
踩
我们直接看效果
先看下面的使用代码
- <CircularSlider5
- step={2}
- min={0}
- max={100}
- radius={100}
- value={30}
- onComplete={(changeValue: number) => this.handleEmailSbp(changeValue)}
- onChange={(changeValue: number) => this.handleEmailDpd(changeValue)}
- contentContainerStyle={styles.contentContainerStyle}
- strokeWidth={10}
- buttonBorderColor="#3FE3EB"
- buttonFillColor="#fff"
- buttonStrokeWidth={10}
- openingRadian={Math.PI / 4}
- buttonRadius={10}
- triangleLinerGradient={[
- {stop: '0%', color: '#FF7B4C'},
- {stop: '50%', color: '#FFFFFF'},
- {stop: '100%', color: '#317AF7'},
- ]}
- linearGradient={[
- {stop: '0%', color: '#3FE3EB'},
- {stop: '100%', color: '#7E84ED'},
- ]}></CircularSlider5>

{
radius: 100, // 半径
strokeWidth: 20, // 线宽
openingRadian: Math.PI / 4, // 开口弧度,为了便于计算值为实际开口弧度的一半
backgroundTrackColor: '#e8e8e8', // 底部轨道颜色
linearGradient: [
{stop: '0%', color: '#1890ff'},
{stop: '100%', color: '#f5222d'},
], // 渐变色
min: 0, // 最小值
max: 100, // 最大值
buttonRadius: 12, // 按钮半径
buttonBorderColor: '#fff', // 按钮边框颜色
buttonStrokeWidth: 1, // 按钮线宽
};
本组件使用到了
1.react-native-svg
2.PanResponder
具体代码如下
- import React, {PureComponent} from 'react';
- import Svg, {
- Path,
- G,
- Defs,
- LinearGradient,
- Stop,
- Circle,
- } from 'react-native-svg';
- import {StyleSheet, View, PanResponder} from 'react-native';
-
- export default class CircularSlider extends PureComponent {
- static defaultProps = {
- radius: 100, // 半径
- strokeWidth: 20, // 线宽
- openingRadian: Math.PI / 4, // 开口弧度,为了便于计算值为实际开口弧度的一半
- backgroundTrackColor: '#e8e8e8', // 底部轨道颜色
- linearGradient: [
- {stop: '0%', color: '#1890ff'},
- {stop: '100%', color: '#f5222d'},
- ], // 渐变色
- min: 0, // 最小值
- max: 100, // 最大值
- buttonRadius: 12, // 按钮半径
- buttonBorderColor: '#fff', // 按钮边框颜色
- buttonStrokeWidth: 1, // 按钮线宽
- };
-
- constructor(props) {
- super(props);
- this._panResponder = PanResponder.create({
- onStartShouldSetPanResponder: () => true,
- onMoveShouldSetPanResponder: () => false,
- onPanResponderGrant: this._handlePanResponderGrant,
- onPanResponderMove: this._handlePanResponderMove,
- onPanResponderRelease: this._handlePanResponderEnd,
- onPanResponderTerminationRequest: () => false,
- onPanResponderTerminate: this._handlePanResponderEnd,
- });
-
- this.state = {
- value: props.value || props.min,
- };
-
- this._containerRef = React.createRef();
- }
-
- _handlePanResponderGrant = () => {
- /*
- * 记录开始滑动开始时的滑块值、弧度和坐标,用户后续值的计算
- */
- const {value} = this.state;
- this._moveStartValue = value;
- // 获取开始移动的弧度
- this._moveStartRadian = this.getRadianByValue(value);
- // 根据弧度获取开始的极坐标
- this._startCartesian = this.polarToCartesian(this._moveStartRadian);
- // console.log(`开始滑动弧度${this._startCartesian}`);
- // console.log(`开始滑动${this._startCartesian.x}:${this._startCartesian.y}`);
- };
-
- _handlePanResponderMove = (e, gestureState) => {
- const {min, max, step, openingRadian} = this.props;
- let {x, y} = this._startCartesian;
- x += gestureState.dx;
- y += gestureState.dy;
- // console.log(`滑动过程中${x}:${y}`);
- const radian = this.cartesianToPolar(x, y); // 当前弧度
- console.log(`滑动过程中的弧度${radian}`);
- const ratio =
- (this._moveStartRadian - radian) / ((Math.PI - openingRadian) * 2); // 弧度变化所占比例
- const diff = max - min; // 最大值和最小值的差
-
- let value;
- if (step) {
- value = this._moveStartValue + Math.round((ratio * diff) / step) * step;
- } else {
- value = this._moveStartValue + ratio * diff;
- }
- // 处理极值
- value = Math.max(min, Math.min(max, value));
- this.setState({
- value,
- });
- // this.setState(({value: curValue}) => {
- // value = Math.abs(value - curValue) > diff / 4 ? curValue : value; // 避免直接从最小值变为最大值
- // return {value: Math.round(value)};
- // });
- this._fireChangeEvent('onChange');
- };
-
- _handlePanResponderEnd = (e, gestureState) => {
- if (this.props.disabled) {
- return;
- }
-
- this._fireChangeEvent('onComplete');
- };
-
- _fireChangeEvent = event => {
- if (this.props[event]) {
- this.props[event](this.state.value);
- }
- };
-
- /**
- * 极坐标转笛卡尔坐标
- * @param {number} radian - 弧度表示的极角
- */
- polarToCartesian(radian) {
- const {radius} = this.props;
- const distance = radius + this._getExtraSize() / 2; // 圆心距离坐标轴的距离
- const x = distance + radius * Math.sin(radian);
- const y = distance + radius * Math.cos(radian);
- return {x, y};
- }
-
- /**
- * 笛卡尔坐标转极坐标
- * @param {*} x
- * @param {*} y
- */
- cartesianToPolar(x, y) {
- const {radius} = this.props;
- const distance = radius + this._getExtraSize() / 2; // 圆心距离坐标轴的距离
- if (x === distance) {
- return y > distance ? 0 : Math.PI / 2;
- }
- const a = Math.atan((y - distance) / (x - distance)); // 计算点与圆心连线和 x 轴的夹角
- return (x < distance ? (Math.PI * 3) / 2 : Math.PI / 2) - a;
- }
-
- /**
- * 获取当前弧度
- */
- getCurrentRadian() {
- return this.getRadianByValue(this.state.value);
- }
-
- /**
- * 根据滑块的值获取弧度
- * @param {*} value
- */
- getRadianByValue(value) {
- const {openingRadian, min, max} = this.props;
- return (
- ((Math.PI - openingRadian) * 2 * (max - value)) / (max - min) +
- openingRadian
- );
- }
-
- /**
- * 获取除半径外额外的大小,返回线宽和按钮直径中较大的
- */
- _getExtraSize() {
- const {strokeWidth, buttonRadius, buttonStrokeWidth} = this.props;
- return Math.max(strokeWidth, (buttonRadius + buttonStrokeWidth) * 2);
- }
-
- _onLayout = () => {
- const ref = this._containerRef.current;
- if (ref) {
- ref.measure((x, y, width, height, pageX, pageY) => {
- this.vertexX = pageX;
- this.vertexY = pageY;
- });
- }
- };
-
- render() {
- const {
- radius,
- strokeWidth,
- backgroundTrackColor,
- openingRadian,
- linearGradient,
- buttonRadius,
- buttonBorderColor,
- buttonFillColor,
- buttonStrokeWidth,
- style,
- contentContainerStyle,
- children,
- } = this.props;
- const svgSize = radius * 2 + this._getExtraSize();
- const startRadian = 2 * Math.PI - openingRadian; // 起点弧度
- const startPoint = this.polarToCartesian(startRadian);
- const endPoint = this.polarToCartesian(openingRadian);
- const currentRadian = this.getCurrentRadian(); // 当前弧度
- const curPoint = this.polarToCartesian(currentRadian);
-
- const contentStyle = [styles.content, contentContainerStyle];
-
- return (
- <View
- onLayout={this._onLayout}
- ref={this._containerRef}
- style={[styles.container, style]}>
- <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>
- <G rotation={0} origin={`${svgSize / 2}, ${svgSize / 2}`}>
- <Path
- strokeWidth={strokeWidth}
- stroke={backgroundTrackColor}
- fill="none"
- strokeLinecap="round"
- d={`M${startPoint.x},${startPoint.y} A ${radius},${radius},0,${
- startRadian - openingRadian >= Math.PI ? '1' : '0'
- },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={buttonFillColor || buttonBorderColor}
- stroke={buttonBorderColor}
- strokeWidth={buttonStrokeWidth}
- {...this._panResponder.panHandlers}
- />
- </G>
- </Svg>
- <View style={contentStyle} pointerEvents="box-none">
- {children}
- </View>
- </View>
- );
- }
- }
-
- const styles = StyleSheet.create({
- container: {
- justifyContent: 'center',
- alignItems: 'center',
- },
- content: {
- position: 'absolute',
- left: 0,
- top: 0,
- bottom: 0,
- right: 0,
- },
- });

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。