ReactNative实现弧形拖动条的代码案例

 更新时间:2024年02月05日 11:17:44   作者:xvzhengyang  
本文介绍了ReactNative实现弧形拖动条,本组件使用到了react-native-svg和PanResponder,结合示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

我们直接看效果

先看下面的使用代码

     <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,
  },
});

到此这篇关于ReactNative实现弧形拖动条的文章就介绍到这了,更多相关ReactNative弧形拖动条内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Redux DevTools不能显示数据问题

    Redux DevTools不能显示数据问题

    这篇文章主要介绍了Redux DevTools不能显示数据问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • 每天一个hooks学习之useUnmount

    每天一个hooks学习之useUnmount

    这篇文章主要为大家介绍了每天一个hooks学习之useUnmount,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • 利用ES6语法重构React组件详解

    利用ES6语法重构React组件详解

    这篇文章主要介绍了利用ES6语法重构React组件的相关资料,文中通过示例代码介绍的很详细,对大家具有一定的参考借鉴价值,需要的朋友们下面来一起看看吧。
    2017-03-03
  • 官方推荐react-navigation的具体使用详解

    官方推荐react-navigation的具体使用详解

    本篇文章主要介绍了官方推荐react-navigation的具体使用详解,react-navigation是致力于解决导航卡顿,数据传递,Tabbar和navigator布局,支持redux。非常具有实用价值,需要的朋友可以参考下
    2018-05-05
  • React深入分析更新的创建源码

    React深入分析更新的创建源码

    React组件分为函数组件与class组件;函数组件是无状态组件,class称为类组件;函数组件只有props,没有自己的私有数据和生命周期函数;class组件有自己私有数据(this.state)和生命周期函数
    2023-01-01
  • React中常见的TypeScript定义实战教程

    React中常见的TypeScript定义实战教程

    这篇文章主要介绍了React中常见的TypeScript定义实战,本文介绍了Fiber结构,Fiber的生成过程,调和过程,以及 render 和 commit 两大阶段,需要的朋友可以参考下
    2022-10-10
  • react + vite + ts项目中优雅使用.svg文件

    react + vite + ts项目中优雅使用.svg文件

    这篇文章主要为大家介绍了react + vite + ts项目中优雅使用.svg文件,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • React应用中避免白屏现象的方法小结

    React应用中避免白屏现象的方法小结

    在开发React应用程序时,我们都曾遇到过这样的场景:一个未被捕获的异常突然中断了组件的渲染流程,导致用户界面呈现出一片空白,也就是俗称的“白屏”现象,本文将探讨如何在React应用中有效捕获并处理这些错误,避免白屏现象的发生,需要的朋友可以参考下
    2024-06-06
  • React事件处理和表单的绑定详解

    React事件处理和表单的绑定详解

    这篇文章主要介绍了React事件处理和表单的绑定,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • React之关于Promise的用法

    React之关于Promise的用法

    这篇文章主要介绍了React之关于Promise的用法及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02

最新评论