react-native 圆弧拖动进度条实现的示例代码


Posted in Javascript onApril 12, 2018

本文介绍了react-native 圆弧拖动进度条实现的示例代码,分享给大家,具体如下:

先上效果图

react-native 圆弧拖动进度条实现的示例代码

因为需求需要实现这个效果图 非原生实现,

  1. 难点1:绘制 使用svg
  2. 难点2:点击事件的处理
  3. 难点3:封装

由于绘制需要是使用svg

此处自行百度 按照svg以及api 教学

视图代码块

render() {
 return (
  <View pointerEvents={'box-only'}
  //事件处理
  {...this._panResponder.panHandlers}>
  //实际圆环
  {this._renderCircleSvg()}
  // 计算中心距离
  <View
   style={{
   position: 'relative',
   top: -this.props.height / 2 - this.props.r,
   left: this.props.width / 2 - this.props.r,
   flex: 1,
   }}>
   // 暴露给外部渲染圆环中心的接口
   {this.props.renderCenterView(this.state.temp)}
  </View>
  </View>
 );


 _renderCircleSvg() {
 //中心点
 const cx = this.props.width / 2;
 const cy = this.props.height / 2;
 //计算是否有偏差角 对应图就是下面缺了一块的
 const prad = this.props.angle / 2 * (Math.PI / 180);
 //三角计算起点
 const startX = -(Math.sin(prad) * this.props.r) + cx;
 const startY = cy + Math.cos(prad) * this.props.r; 
 //终点
 const endX = Math.sin(prad) * this.props.r + cx;
 const endY = cy + Math.cos(prad) * this.props.r;

 // 计算进度点
 const progress = parseInt(
  this._circlerate() * (360 - this.props.angle) / 100,
  10
 );
 // 根据象限做处理 苦苦苦 高中数学全忘了,参考辅助线
 const t = progress + this.props.angle / 2;
 const progressX = cx - Math.sin(t * (Math.PI / 180)) * this.props.r;
 const progressY = cy + Math.cos(t * (Math.PI / 180)) * this.props.r;

// SVG的描述 这里百度下就知道什么意思
 const descriptions = [
  'M',
  startX,
  startY,
  'A',
  this.props.r,
  this.props.r,
  0,
  1,
  1,
  endX,
  endY,
 ].join(' ');

 const progressdescription = [
  'M',
  startX,
  startY,
  'A',
  this.props.r,
  this.props.r,
  0,
  //根据角度是否是0,1 看下效果就知道了
  t >= 180 + this.props.angle / 2 ? 1 : 0,
  1,
  progressX,
  progressY,
 ].join(' ');
 return (
  <Svg
  height={this.props.height}
  width={this.props.width}
  style={styles.svg}>
  <Path
   d={descriptions}
   fill="none"
   stroke={this.props.outArcColor}
   strokeWidth={this.props.strokeWidth} />
  <Path
   d={progressdescription}
   fill="none"
   stroke={this.props.progressvalue}
   strokeWidth={this.props.strokeWidth} />
  <Circle
   cx={progressX}
   cy={progressY}
   r={this.props.tabR}
   stroke={this.props.tabStrokeColor}
   strokeWidth={this.props.tabStrokeWidth}
   fill={this.props.tabColor} />
  </Svg>
 );
 }
}

事件处理代码块

// 参考react native 官网对手势的讲解
 iniPanResponder() {
 this.parseToDeg = this.parseToDeg.bind(this);
 this._panResponder = PanResponder.create({
  // 要求成为响应者:
  onStartShouldSetPanResponder: () => true,
  onStartShouldSetPanResponderCapture: () => true,
  onMoveShouldSetPanResponder: () => true,
  onMoveShouldSetPanResponderCapture: () => true,
  onPanResponderGrant: evt => {
  // 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!
  if (this.props.enTouch) {
   this.lastTemper = this.state.temp;
   const x = evt.nativeEvent.locationX;
   const y = evt.nativeEvent.locationY;
   this.parseToDeg(x, y);
  }
  },
  onPanResponderMove: (evt, gestureState) => {
  if (this.props.enTouch) {
   let x = evt.nativeEvent.locationX;
   let y = evt.nativeEvent.locationY;
   if (Platform.OS === 'android') {
   x = evt.nativeEvent.locationX + gestureState.dx;
   y = evt.nativeEvent.locationY + gestureState.dy;
   }
   this.parseToDeg(x, y);
  }
  },
  onPanResponderTerminationRequest: () => true,
  onPanResponderRelease: () => {
  if (this.props.enTouch) this.props.complete(this.state.temp);
  },
  // 另一个组件已经成为了新的响应者,所以当前手势将被取消。
  onPanResponderTerminate: () => {},
  // 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者
  // 默认返回true。目前暂时只支持android。
  onShouldBlockNativeResponder: () => true,
 });
 }

//画象限看看就知道了 就是和中线点计算角度
parseToDeg(x, y) {
 const cx = this.props.width / 2;
 const cy = this.props.height / 2;
 let deg;
 let temp;
 if (x >= cx && y <= cy) {
  deg = Math.atan((cy - y) / (x - cx)) * 180 / Math.PI;
  temp =
  (270 - deg - this.props.angle / 2) /
   (360 - this.props.angle) *
   (this.props.max - this.props.min) +
  this.props.min;
 } else if (x >= cx && y >= cy) {
  deg = Math.atan((cy - y) / (cx - x)) * 180 / Math.PI;
  temp =
  (270 + deg - this.props.angle / 2) /
   (360 - this.props.angle) *
   (this.props.max - this.props.min) +
  this.props.min;
 } else if (x <= cx && y <= cy) {
  deg = Math.atan((x - cx) / (y - cy)) * 180 / Math.PI;
  temp =
  (180 - this.props.angle / 2 - deg) /
   (360 - this.props.angle) *
   (this.props.max - this.props.min) +
  this.props.min;
 } else if (x <= cx && y >= cy) {
  deg = Math.atan((cx - x) / (y - cy)) * 180 / Math.PI;
  if (deg < this.props.angle / 2) {
  deg = this.props.angle / 2;
  }
  temp =
  (deg - this.props.angle / 2) /
   (360 - this.props.angle) *
   (this.props.max - this.props.min) +
  this.props.min;
 }
 if (temp <= this.props.min) {
  temp = this.props.min;
 }
 if (temp >= this.props.max) {
  temp = this.props.max;
 }
 //因为提供步长,所欲需要做接近步长的数
 temp = this.getTemps(temp);
 this.setState({
  temp,
 });
 this.props.valueChange(this.state.temp);
 }

 getTemps(tmps) {
 const k = parseInt((tmps - this.props.min) / this.props.step, 10);
 const k1 = this.props.min + this.props.step * k;
 const k2 = this.props.min + this.props.step * (k + 1);
 if (Math.abs(k1 - tmps) > Math.abs(k2 - tmps)) return k2;
 return k1;
 }

完整代码块

import React, { Component } from 'react';
import { View, StyleSheet, PanResponder, Platform, Text } from 'react-native';
import Svg, { Circle, Path } from 'react-native-svg';

export default class CircleView extends Component {
 static propTypes = {
 height: React.PropTypes.number,
 width: React.PropTypes.number,
 r: React.PropTypes.number,
 angle: React.PropTypes.number,
 outArcColor: React.PropTypes.object,
 progressvalue: React.PropTypes.object,
 tabColor: React.PropTypes.object,
 tabStrokeColor: React.PropTypes.object,
 strokeWidth: React.PropTypes.number,
 value: React.PropTypes.number,
 min: React.PropTypes.number,
 max: React.PropTypes.number,
 tabR: React.PropTypes.number,
 step: React.PropTypes.number,
 tabStrokeWidth: React.PropTypes.number,
 valueChange: React.PropTypes.func,
 renderCenterView: React.PropTypes.func,
 complete: React.PropTypes.func,
 enTouch: React.PropTypes.boolean,
 };

 static defaultProps = {
 width: 300,
 height: 300,
 r: 100,
 angle: 60,
 outArcColor: 'white',
 strokeWidth: 10,
 value: 20,
 min: 10,
 max: 70,
 progressvalue: '#ED8D1B',
 tabR: 15,
 tabColor: '#EFE526',
 tabStrokeWidth: 5,
 tabStrokeColor: '#86BA38',
 valueChange: () => {},
 complete: () => {},
 renderCenterView: () => {},
 step: 1,
 enTouch: true,
 };
 constructor(props) {
 super(props);
 this.state = {
  temp: this.props.value,
 };
 this.iniPanResponder();
 }
 iniPanResponder() {
 this.parseToDeg = this.parseToDeg.bind(this);
 this._panResponder = PanResponder.create({
  // 要求成为响应者:
  onStartShouldSetPanResponder: () => true,
  onStartShouldSetPanResponderCapture: () => true,
  onMoveShouldSetPanResponder: () => true,
  onMoveShouldSetPanResponderCapture: () => true,
  onPanResponderGrant: evt => {
  // 开始手势操作。给用户一些视觉反馈,让他们知道发生了什么事情!
  if (this.props.enTouch) {
   this.lastTemper = this.state.temp;
   const x = evt.nativeEvent.locationX;
   const y = evt.nativeEvent.locationY;
   this.parseToDeg(x, y);
  }
  },
  onPanResponderMove: (evt, gestureState) => {
  if (this.props.enTouch) {
   let x = evt.nativeEvent.locationX;
   let y = evt.nativeEvent.locationY;
   if (Platform.OS === 'android') {
   x = evt.nativeEvent.locationX + gestureState.dx;
   y = evt.nativeEvent.locationY + gestureState.dy;
   }
   this.parseToDeg(x, y);
  }
  },
  onPanResponderTerminationRequest: () => true,
  onPanResponderRelease: () => {
  if (this.props.enTouch) this.props.complete(this.state.temp);
  },
  // 另一个组件已经成为了新的响应者,所以当前手势将被取消。
  onPanResponderTerminate: () => {},
  // 返回一个布尔值,决定当前组件是否应该阻止原生组件成为JS响应者
  // 默认返回true。目前暂时只支持android。
  onShouldBlockNativeResponder: () => true,
 });
 }
 componentWillReceiveProps(nextProps) {
 if (nextProps.value != this.state.temp) {
  this.state = {
  temp: nextProps.value,
  };
 }
 }
 parseToDeg(x, y) {
 const cx = this.props.width / 2;
 const cy = this.props.height / 2;
 let deg;
 let temp;
 if (x >= cx && y <= cy) {
  deg = Math.atan((cy - y) / (x - cx)) * 180 / Math.PI;
  temp =
  (270 - deg - this.props.angle / 2) /
   (360 - this.props.angle) *
   (this.props.max - this.props.min) +
  this.props.min;
 } else if (x >= cx && y >= cy) {
  deg = Math.atan((cy - y) / (cx - x)) * 180 / Math.PI;
  temp =
  (270 + deg - this.props.angle / 2) /
   (360 - this.props.angle) *
   (this.props.max - this.props.min) +
  this.props.min;
 } else if (x <= cx && y <= cy) {
  deg = Math.atan((x - cx) / (y - cy)) * 180 / Math.PI;
  temp =
  (180 - this.props.angle / 2 - deg) /
   (360 - this.props.angle) *
   (this.props.max - this.props.min) +
  this.props.min;
 } else if (x <= cx && y >= cy) {
  deg = Math.atan((cx - x) / (y - cy)) * 180 / Math.PI;
  if (deg < this.props.angle / 2) {
  deg = this.props.angle / 2;
  }
  temp =
  (deg - this.props.angle / 2) /
   (360 - this.props.angle) *
   (this.props.max - this.props.min) +
  this.props.min;
 }
 if (temp <= this.props.min) {
  temp = this.props.min;
 }
 if (temp >= this.props.max) {
  temp = this.props.max;
 }

 temp = this.getTemps(temp);
 this.setState({
  temp,
 });
 this.props.valueChange(this.state.temp);
 }

 getTemps(tmps) {
 const k = parseInt((tmps - this.props.min) / this.props.step, 10);
 const k1 = this.props.min + this.props.step * k;
 const k2 = this.props.min + this.props.step * (k + 1);
 if (Math.abs(k1 - tmps) > Math.abs(k2 - tmps)) return k2;
 return k1;
 }


 render() {
 return (
  <View pointerEvents={'box-only'} {...this._panResponder.panHandlers}>
  {this._renderCircleSvg()}
  <View
   style={{
   position: 'relative',
   top: -this.props.height / 2 - this.props.r,
   left: this.props.width / 2 - this.props.r,
   flex: 1,
   }}>
   {this.props.renderCenterView(this.state.temp)}
  </View>
  </View>
 );
 }

 _circlerate() {
 let rate = parseInt(
  (this.state.temp - this.props.min) *
  100 /
  (this.props.max - this.props.min),
  10
 );
 if (rate < 0) {
  rate = 0;
 } else if (rate > 100) {
  rate = 100;
 }
 return rate;
 }
 _renderCircleSvg() {
 const cx = this.props.width / 2;
 const cy = this.props.height / 2;
 const prad = this.props.angle / 2 * (Math.PI / 180);
 const startX = -(Math.sin(prad) * this.props.r) + cx;
 const startY = cy + Math.cos(prad) * this.props.r; // // 最外层的圆弧配置
 const endX = Math.sin(prad) * this.props.r + cx;
 const endY = cy + Math.cos(prad) * this.props.r;

 // 计算进度点
 const progress = parseInt(
  this._circlerate() * (360 - this.props.angle) / 100,
  10
 );
 // 根据象限做处理 苦苦苦 高中数学全忘了,参考辅助线
 const t = progress + this.props.angle / 2;
 const progressX = cx - Math.sin(t * (Math.PI / 180)) * this.props.r;
 const progressY = cy + Math.cos(t * (Math.PI / 180)) * this.props.r;

 const descriptions = [
  'M',
  startX,
  startY,
  'A',
  this.props.r,
  this.props.r,
  0,
  1,
  1,
  endX,
  endY,
 ].join(' ');

 const progressdescription = [
  'M',
  startX,
  startY,
  'A',
  this.props.r,
  this.props.r,
  0,
  t >= 180 + this.props.angle / 2 ? 1 : 0,
  1,
  progressX,
  progressY,
 ].join(' ');
 return (
  <Svg
  height={this.props.height}
  width={this.props.width}
  style={styles.svg}>
  <Path
   d={descriptions}
   fill="none"
   stroke={this.props.outArcColor}
   strokeWidth={this.props.strokeWidth} />
  <Path
   d={progressdescription}
   fill="none"
   stroke={this.props.progressvalue}
   strokeWidth={this.props.strokeWidth} />
  <Circle
   cx={progressX}
   cy={progressY}
   r={this.props.tabR}
   stroke={this.props.tabStrokeColor}
   strokeWidth={this.props.tabStrokeWidth}
   fill={this.props.tabColor} />
  </Svg>
 );
 }
}

const styles = StyleSheet.create({
 svg: {},
});

外部调用

<View style={styles.container}>
  <CircleProgress
   width={width}
   height={height}
   r={r}
   angle={60}
   min={5}
   max={35}
   step={0.5}
   value={22}
   complete={temp => {

   }}
   valueChange={temp => {}}
   renderCenterView={temp => (
   <View style={{ flex: 1 }}>

   </View>
   )}
   enTouch={true} />
  </View>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript对talbe进行动态添加、删除、验证实现代码
Mar 29 Javascript
jquery获取iframe中的dom对象(两种方法)
Jul 02 Javascript
js 3种归并操作的实例代码
Oct 30 Javascript
12306 刷票脚本及稳固刷票脚本(防挂)
Jan 04 Javascript
防止重复发送 Ajax 请求
Feb 15 Javascript
js上传图片预览的实现方法
May 09 Javascript
vue2.0的contextmenu右键弹出菜单的实例代码
Jul 24 Javascript
使用vue-cli编写vue插件的方法
Feb 26 Javascript
微信小程序常用简易小函数总结
Feb 01 Javascript
Vue从TodoList中学父子组件通信
Feb 05 Javascript
小程序中手机号识别的示例
Dec 14 Javascript
Canvas三种动态画圆实现方法说明(小结)
Apr 16 Javascript
关于vue中 $emit的用法详解
Apr 12 #Javascript
Vue 页面跳转不用router-link的实现代码
Apr 12 #Javascript
JS中promise化微信小程序api
Apr 12 #Javascript
vue配置请求本地json数据的方法
Apr 11 #Javascript
jQuery实现判断上传图片类型和大小的方法示例
Apr 11 #jQuery
JS实现的合并多个数组去重算法示例
Apr 11 #Javascript
JS实现的JSON数组去重算法示例
Apr 11 #Javascript
You might like
windows7下安装php的imagick和imagemagick扩展教程
2014/07/04 PHP
phpcms手机内容页面添加上一篇和下一篇
2015/06/05 PHP
PHP的CURL方法curl_setopt()函数案例介绍(抓取网页,POST数据)
2016/12/14 PHP
Yii框架弹出框功能示例
2017/01/07 PHP
php使用PDO执行SQL语句的方法分析
2017/02/16 PHP
PHP实现的观察者模式实例
2017/06/21 PHP
PHP实现找出有序数组中绝对值最小的数算法分析
2017/08/07 PHP
在phpstudy集成环境下的nginx服务器下配置url重写
2019/12/02 PHP
laravel中Redis队列监听中断的分析
2020/09/14 PHP
根据地区不同显示时间的javascript代码
2007/08/13 Javascript
Jquery ajax不能解析json对象,报Invalid JSON错误的原因和解决方法
2010/03/27 Javascript
jQuery提交多个表单的小例子
2013/06/30 Javascript
JQuery实现表格动态增加行并对新行添加事件
2014/07/30 Javascript
jquery任意位置浮动固定层插件用法实例
2015/05/29 Javascript
新手快速学习JavaScript免费教程资源汇总
2015/06/25 Javascript
JavaScript实现99乘法表及隔行变色实例代码
2016/02/24 Javascript
AngularJs表单校验功能实例代码
2017/02/09 Javascript
jQuery ajax读取本地json文件的实例
2017/10/31 jQuery
一个简单的node.js界面实现方法
2018/06/01 Javascript
实现vuex与组件data之间的数据同步更新方式
2019/11/12 Javascript
VSCode写vue项目一键生成.vue模版,修改定义其他模板的方法
2020/04/17 Javascript
JavaScript如何实现防止重复的网络请求的示例
2021/01/28 Javascript
[02:20]DOTA2中文配音宣传片
2013/05/22 DOTA
[03:55]TI9战队采访——TNC Predator
2019/08/22 DOTA
详解Python中的from..import绝对导入语句
2016/06/21 Python
Bottle框架中的装饰器类和描述符应用详解
2017/10/28 Python
Python干货:分享Python绘制六种可视化图表
2018/08/27 Python
如何在Django中添加没有微秒的 DateTimeField 属性详解
2019/01/30 Python
详解Python sys.argv使用方法
2019/05/10 Python
美国LOGO设计公司:The Logo Company
2018/07/16 全球购物
翻译学院毕业生自荐书
2014/02/02 职场文书
《尊严》教学反思
2014/02/11 职场文书
亲子运动会的活动方案
2014/08/17 职场文书
Html5新增了哪些功能
2021/04/16 HTML / CSS
Python正则表达式中flags参数的实例详解
2022/04/01 Python
html中两种获取标签内的值的方法
2022/06/16 jQuery