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 相关文章推荐
硬盘浏览程序,保存成网页格式便可使用
Dec 03 Javascript
JS中简单的实现像C#中using功能(有源码下载)
Jan 09 Javascript
js中widow.open()方法使用详解
Jul 30 Javascript
js判断横竖屏及禁止浏览器滑动条示例
Apr 29 Javascript
JavaScript获取网页中第一个图片id的方法
Apr 03 Javascript
jQuery实现页面内锚点平滑跳转特效的方法总结
May 11 Javascript
JavaScript返回上一页的三种方法及区别介绍
Jul 04 Javascript
JavaScript高级教程5.6之基本包装类型(详细)
Nov 23 Javascript
js学使用setTimeout实现轮循动画
Jul 17 Javascript
Angular4编程之表单响应功能示例
Dec 13 Javascript
基于 flexible 的 Vue 组件:Toast -- 显示框效果
Dec 26 Javascript
原生JS实现前端本地文件上传
Sep 08 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
如何获知PHP程序占用多少内存(memory_get_usage)
2012/09/23 PHP
PHP实现将浏览历史页面网址保存到cookie的方法
2015/01/26 PHP
教你php如何实现验证码
2016/01/20 PHP
可兼容php5与php7的cURL文件上传功能实例分析
2018/05/11 PHP
js 取时间差去掉周六周日实现代码
2012/12/25 Javascript
jQuery Pagination Ajax分页插件(分页切换时无刷新与延迟)中文翻译版
2013/01/11 Javascript
javascript:文字不间断向左移动的实例代码
2013/08/08 Javascript
给before和after伪元素设置js效果的方法
2015/12/04 Javascript
jQuery焦点图插件SaySlide
2015/12/21 Javascript
基于jQuery实现仿搜狐辩论投票动画代码(附源码下载)
2016/02/18 Javascript
Google 地图事件实例讲解
2016/08/06 Javascript
jQuery插件WebUploader实现文件上传
2016/11/07 Javascript
微信小程序 封装http请求实例详解
2017/01/16 Javascript
基于HTML5+JS实现本地图片裁剪并上传功能
2017/03/24 Javascript
基于JavaScript实现焦点图轮播效果
2017/03/27 Javascript
微信小程序之前台循环数据绑定
2017/08/18 Javascript
详解redis在nodejs中的应用
2018/05/02 NodeJs
简单了解JavaScript异步
2019/05/23 Javascript
javascript导出csv文件(excel)的方法示例
2019/08/25 Javascript
NodeJS开发人员常见五个错误理解
2020/10/14 NodeJs
python爬虫爬取某站上海租房图片
2018/02/04 Python
Python实现matplotlib显示中文的方法详解
2018/02/06 Python
Python浅复制中对象生存周期实例分析
2018/04/02 Python
转换科学计数法的数值字符串为decimal类型的方法
2018/07/16 Python
python高级特性和高阶函数及使用详解
2018/10/17 Python
python中logging模块的一些简单用法的使用
2019/02/22 Python
Pytorch 数据加载与数据预处理方式
2019/12/31 Python
python matplotlib包图像配色方案分享
2020/03/14 Python
解决IDEA 的 plugins 搜不到任何的插件问题
2020/05/04 Python
CSS3制作日历实现代码
2012/01/21 HTML / CSS
含精油的天然有机化妆品:Indemne
2019/08/27 全球购物
会计专业自我鉴定范文
2013/12/29 职场文书
事业单位考核材料
2014/05/21 职场文书
优秀团员事迹材料2000字
2014/08/20 职场文书
公司授权委托书样本
2014/09/15 职场文书
2015年大班保育员工作总结
2015/05/18 职场文书