react-native 圆弧拖动进度条实现的示例代码
程序员文章站
2022-12-02 17:46:23
本文介绍了react-native 圆弧拖动进度条实现的示例代码,分享给大家,具体如下:
先上效果图
因为需求需要实现这个效果图 非原生实现,
难点1:绘...
本文介绍了react-native 圆弧拖动进度条实现的示例代码,分享给大家,具体如下:
先上效果图
因为需求需要实现这个效果图 非原生实现,
- 难点1:绘制 使用svg
- 难点2:点击事件的处理
- 难点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>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 揭秘:三国时期的凉州在现在的哪个地方呢?
下一篇: Koa项目搭建过程详细记录