当前位置:   article > 正文

ReactNative实现弧形拖动条

ReactNative实现弧形拖动条

我们直接看效果

先看下面的使用代码

  1. <CircularSlider5
  2. step={2}
  3. min={0}
  4. max={100}
  5. radius={100}
  6. value={30}
  7. onComplete={(changeValue: number) => this.handleEmailSbp(changeValue)}
  8. onChange={(changeValue: number) => this.handleEmailDpd(changeValue)}
  9. contentContainerStyle={styles.contentContainerStyle}
  10. strokeWidth={10}
  11. buttonBorderColor="#3FE3EB"
  12. buttonFillColor="#fff"
  13. buttonStrokeWidth={10}
  14. openingRadian={Math.PI / 4}
  15. buttonRadius={10}
  16. triangleLinerGradient={[
  17. {stop: '0%', color: '#FF7B4C'},
  18. {stop: '50%', color: '#FFFFFF'},
  19. {stop: '100%', color: '#317AF7'},
  20. ]}
  21. linearGradient={[
  22. {stop: '0%', color: '#3FE3EB'},
  23. {stop: '100%', color: '#7E84ED'},
  24. ]}></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

具体代码如下

  1. import React, {PureComponent} from 'react';
  2. import Svg, {
  3. Path,
  4. G,
  5. Defs,
  6. LinearGradient,
  7. Stop,
  8. Circle,
  9. } from 'react-native-svg';
  10. import {StyleSheet, View, PanResponder} from 'react-native';
  11. export default class CircularSlider extends PureComponent {
  12. static defaultProps = {
  13. radius: 100, // 半径
  14. strokeWidth: 20, // 线宽
  15. openingRadian: Math.PI / 4, // 开口弧度,为了便于计算值为实际开口弧度的一半
  16. backgroundTrackColor: '#e8e8e8', // 底部轨道颜色
  17. linearGradient: [
  18. {stop: '0%', color: '#1890ff'},
  19. {stop: '100%', color: '#f5222d'},
  20. ], // 渐变色
  21. min: 0, // 最小值
  22. max: 100, // 最大值
  23. buttonRadius: 12, // 按钮半径
  24. buttonBorderColor: '#fff', // 按钮边框颜色
  25. buttonStrokeWidth: 1, // 按钮线宽
  26. };
  27. constructor(props) {
  28. super(props);
  29. this._panResponder = PanResponder.create({
  30. onStartShouldSetPanResponder: () => true,
  31. onMoveShouldSetPanResponder: () => false,
  32. onPanResponderGrant: this._handlePanResponderGrant,
  33. onPanResponderMove: this._handlePanResponderMove,
  34. onPanResponderRelease: this._handlePanResponderEnd,
  35. onPanResponderTerminationRequest: () => false,
  36. onPanResponderTerminate: this._handlePanResponderEnd,
  37. });
  38. this.state = {
  39. value: props.value || props.min,
  40. };
  41. this._containerRef = React.createRef();
  42. }
  43. _handlePanResponderGrant = () => {
  44. /*
  45. * 记录开始滑动开始时的滑块值、弧度和坐标,用户后续值的计算
  46. */
  47. const {value} = this.state;
  48. this._moveStartValue = value;
  49. // 获取开始移动的弧度
  50. this._moveStartRadian = this.getRadianByValue(value);
  51. // 根据弧度获取开始的极坐标
  52. this._startCartesian = this.polarToCartesian(this._moveStartRadian);
  53. // console.log(`开始滑动弧度${this._startCartesian}`);
  54. // console.log(`开始滑动${this._startCartesian.x}:${this._startCartesian.y}`);
  55. };
  56. _handlePanResponderMove = (e, gestureState) => {
  57. const {min, max, step, openingRadian} = this.props;
  58. let {x, y} = this._startCartesian;
  59. x += gestureState.dx;
  60. y += gestureState.dy;
  61. // console.log(`滑动过程中${x}:${y}`);
  62. const radian = this.cartesianToPolar(x, y); // 当前弧度
  63. console.log(`滑动过程中的弧度${radian}`);
  64. const ratio =
  65. (this._moveStartRadian - radian) / ((Math.PI - openingRadian) * 2); // 弧度变化所占比例
  66. const diff = max - min; // 最大值和最小值的差
  67. let value;
  68. if (step) {
  69. value = this._moveStartValue + Math.round((ratio * diff) / step) * step;
  70. } else {
  71. value = this._moveStartValue + ratio * diff;
  72. }
  73. // 处理极值
  74. value = Math.max(min, Math.min(max, value));
  75. this.setState({
  76. value,
  77. });
  78. // this.setState(({value: curValue}) => {
  79. // value = Math.abs(value - curValue) > diff / 4 ? curValue : value; // 避免直接从最小值变为最大值
  80. // return {value: Math.round(value)};
  81. // });
  82. this._fireChangeEvent('onChange');
  83. };
  84. _handlePanResponderEnd = (e, gestureState) => {
  85. if (this.props.disabled) {
  86. return;
  87. }
  88. this._fireChangeEvent('onComplete');
  89. };
  90. _fireChangeEvent = event => {
  91. if (this.props[event]) {
  92. this.props[event](this.state.value);
  93. }
  94. };
  95. /**
  96. * 极坐标转笛卡尔坐标
  97. * @param {number} radian - 弧度表示的极角
  98. */
  99. polarToCartesian(radian) {
  100. const {radius} = this.props;
  101. const distance = radius + this._getExtraSize() / 2; // 圆心距离坐标轴的距离
  102. const x = distance + radius * Math.sin(radian);
  103. const y = distance + radius * Math.cos(radian);
  104. return {x, y};
  105. }
  106. /**
  107. * 笛卡尔坐标转极坐标
  108. * @param {*} x
  109. * @param {*} y
  110. */
  111. cartesianToPolar(x, y) {
  112. const {radius} = this.props;
  113. const distance = radius + this._getExtraSize() / 2; // 圆心距离坐标轴的距离
  114. if (x === distance) {
  115. return y > distance ? 0 : Math.PI / 2;
  116. }
  117. const a = Math.atan((y - distance) / (x - distance)); // 计算点与圆心连线和 x 轴的夹角
  118. return (x < distance ? (Math.PI * 3) / 2 : Math.PI / 2) - a;
  119. }
  120. /**
  121. * 获取当前弧度
  122. */
  123. getCurrentRadian() {
  124. return this.getRadianByValue(this.state.value);
  125. }
  126. /**
  127. * 根据滑块的值获取弧度
  128. * @param {*} value
  129. */
  130. getRadianByValue(value) {
  131. const {openingRadian, min, max} = this.props;
  132. return (
  133. ((Math.PI - openingRadian) * 2 * (max - value)) / (max - min) +
  134. openingRadian
  135. );
  136. }
  137. /**
  138. * 获取除半径外额外的大小,返回线宽和按钮直径中较大的
  139. */
  140. _getExtraSize() {
  141. const {strokeWidth, buttonRadius, buttonStrokeWidth} = this.props;
  142. return Math.max(strokeWidth, (buttonRadius + buttonStrokeWidth) * 2);
  143. }
  144. _onLayout = () => {
  145. const ref = this._containerRef.current;
  146. if (ref) {
  147. ref.measure((x, y, width, height, pageX, pageY) => {
  148. this.vertexX = pageX;
  149. this.vertexY = pageY;
  150. });
  151. }
  152. };
  153. render() {
  154. const {
  155. radius,
  156. strokeWidth,
  157. backgroundTrackColor,
  158. openingRadian,
  159. linearGradient,
  160. buttonRadius,
  161. buttonBorderColor,
  162. buttonFillColor,
  163. buttonStrokeWidth,
  164. style,
  165. contentContainerStyle,
  166. children,
  167. } = this.props;
  168. const svgSize = radius * 2 + this._getExtraSize();
  169. const startRadian = 2 * Math.PI - openingRadian; // 起点弧度
  170. const startPoint = this.polarToCartesian(startRadian);
  171. const endPoint = this.polarToCartesian(openingRadian);
  172. const currentRadian = this.getCurrentRadian(); // 当前弧度
  173. const curPoint = this.polarToCartesian(currentRadian);
  174. const contentStyle = [styles.content, contentContainerStyle];
  175. return (
  176. <View
  177. onLayout={this._onLayout}
  178. ref={this._containerRef}
  179. style={[styles.container, style]}>
  180. <Svg width={svgSize} height={svgSize}>
  181. <Defs>
  182. <LinearGradient x1="0%" y1="100%" x2="100%" y2="0%" id="gradient">
  183. {linearGradient.map((item, index) => (
  184. <Stop key={index} offset={item.stop} stopColor={item.color} />
  185. ))}
  186. </LinearGradient>
  187. </Defs>
  188. <G rotation={0} origin={`${svgSize / 2}, ${svgSize / 2}`}>
  189. <Path
  190. strokeWidth={strokeWidth}
  191. stroke={backgroundTrackColor}
  192. fill="none"
  193. strokeLinecap="round"
  194. d={`M${startPoint.x},${startPoint.y} A ${radius},${radius},0,${
  195. startRadian - openingRadian >= Math.PI ? '1' : '0'
  196. },1,${endPoint.x},${endPoint.y}`}
  197. />
  198. <Path
  199. strokeWidth={strokeWidth}
  200. stroke="url(#gradient)"
  201. fill="none"
  202. strokeLinecap="round"
  203. d={`M${startPoint.x},${startPoint.y} A ${radius},${radius},0,${
  204. startRadian - currentRadian >= Math.PI ? '1' : '0'
  205. },1,${curPoint.x},${curPoint.y}`}
  206. />
  207. <Circle
  208. cx={curPoint.x}
  209. cy={curPoint.y}
  210. r={buttonRadius}
  211. fill={buttonFillColor || buttonBorderColor}
  212. stroke={buttonBorderColor}
  213. strokeWidth={buttonStrokeWidth}
  214. {...this._panResponder.panHandlers}
  215. />
  216. </G>
  217. </Svg>
  218. <View style={contentStyle} pointerEvents="box-none">
  219. {children}
  220. </View>
  221. </View>
  222. );
  223. }
  224. }
  225. const styles = StyleSheet.create({
  226. container: {
  227. justifyContent: 'center',
  228. alignItems: 'center',
  229. },
  230. content: {
  231. position: 'absolute',
  232. left: 0,
  233. top: 0,
  234. bottom: 0,
  235. right: 0,
  236. },
  237. });

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/65788
推荐阅读
相关标签
  

闽ICP备14008679号