ReactNative圆形进度条 ART Path arcTo 圆弧实现

首先来吐槽一番
Facebook 对ReactNative添加了ART库后 竟然没有官方文档说明这个咋用 简直可怕.... 并不是每个来做RN开发都是懂得前端常常使用SVG Path 这些东西的啊 大哥

同时相比于Android 等开发 RN还比较新 导致的结果是遇到问题后 能用的资料少啊。。。这种情况下 连最应该有的文档都没有 吐血三升先。。。

。。心里苦。。。

所以我就查了一下RN开发的绘图库ART 发现不是canvas之类的(Android中用canvas习惯了) 最可怕的是网上只有一些简单的关于ART的说明 和一个大兄弟封装了一个SVG基于ART的库 就和在前端使用SVG那样使用来绘制 我不想这样使用 就只能自己摸索着开始

在开始画圆弧进度条之前 我们可能要先熟悉一下SVG Path(RN的path内部工作也是一样的) 是如何工作的 以及 里面的一些参数的具体作用 这里 因为是对SVG Path的介绍 采用React.js画出一些效果 来看看path是如何绘制出我们想要的圆弧的 我这里也就根据几个效果简单说一下 具体可以自己去查相关的只是 SVG Path 关键词搜索就行

Path 圆弧绘制

d="M100,0 A50,50 0 0,1 100,100"

看看这段字符串 寓意
M开头表示在坐标轴中的起始位置x,y
A表示即将绘制圆弧 后面是绘制圆弧半径的意思 一个是x轴的半径 一个是y轴的半径 其实就是在用弧度把这两个点连起来的时候 x比y大 x方向就长一些 如果x=y=radius 就是一个圆的弧度
这里应该涉及到椭圆的相关知识 具体我也不太懂 大概猜是这个意思
继续 A50,50 0 0,1 100,100 这是一个完整圆弧的全部参数
先说最后的100,100 这个是结束点的坐标

150,70 结束坐标

0 0,1 第一个0是r轴的选择角度还是啥来着 默认是0不管他 具体我也不太清楚别人都是这么说的
第二个0是画大弧度还是小弧度 的意思 两个点可以画出弧度最小和最大 看图
0,1->小弧度

1,1->大弧度

再看0,1中的1 这个是镜像的意思 默认是顺时针方向 比如0->小弧度的这个图 是顺时针的情况 现在将1改成0 看图
0,0-->情况

1,0-->情况

还有一个渐变的使用 顺便也贴一个图吧
渐变效果

好了 A后面的几个参数的意思 这几个图应该能看懂了 如果我这里没有解释很清楚 (我也是刚会 可能解释不太清楚 ) 可以自己再去查查SVG Path相关的知识点


现在理解了这个Path的相关参数的含义 后面我们看RN的art的时候也就很好理解了 我们现在来看RN的ART怎么使用
从 ‘react-native中引入 ART’ ART中有一些模块
直接进去ReatART源码

ART源码提供的功能模块

东西还挺多的 这里我们用到Surface (Group(Shape(Path)))
然后这里我看见他有一个类 LinearGradient 这个应该是用来实现刚刚React中那种渐变效果的 但是这里我很努力地去把他这个和Shape对接起来
对于LinearGradient 这个对象 需要一个Colors集合 这个Color对象也是ReatART提供的
这里我不知道怎么构造这个LinearGradient对象 求教哪位会的 麻烦告诉我一下
LinearGradient

LinearGradient对象传入最后会走这个方法

现在转移到圆弧的进度条的中心思想来 先思考圆弧进度条怎么画

现在假设我们某个顶部的点是起始点(100,0)(top) 半径50
这样如果我们来画一个圆 四个最特殊的点的坐标如下
left-->(0,50)
bottom->(100,200)
right->(150,50)

圆的四个外围点

现在我们知道了这四个外围点坐标 这是特殊点 我们和容易画出来 现在问题是怎么画出剩余部分的 比如下图的 我们想滑到A点
A点的圆弧

想画到A点我们首先要找到A点的坐标 这里就要用到sin cos来计算 因为角度一定的情况下 确定了radius
那么在这个点就确定了 比如假设红色部分的角度是30度
那么 A点的坐标 就是 x=100+sin(角度) y=半径-cos(角度)
角度对于的值

因为sin cos涉及到正负 所以这里我们分层四部分处理
0-90 90-180 180-270 270-360
这里还要提醒一点 因为是圆弧 两点一个弧度 所以一整整圆不可能一个弧度完成(我是这么理解的 要是可以的话可以告诉我下 这样我代码部分也能简单点) 我们这里就转个弯 用两个半圆 左边半圆180-360 右边圆0-180 下面是计算公式 也是核心代码

/**
 * 计算目的坐标位置 右边 <180度的计算
 * @param progress
 * @param total
 * @param startX
 * @param startY
 */
function calTargetXY(progress, total, startX, startY, radius) {
    let degress = progress / total * 360;
    if (degress > 180) {
        //log(Tag, '强制 degress -> 180');
        degress = 180;
    }
    //log(Tag, "开始位置 " + startX + " " + startY + "  r: " + radius + " degress  " + degress);
    let target = [];
    if (degress <= 90) {
        degress = degress * 2 * Math.PI / 360;
        // log(Tag, "sin " + Math.sin(degress));
        let endx = startX + radius * Math.sin(degress);
        let endy = startY + radius - radius * Math.cos(degress);
        target.push(endx);
        target.push(endy);
        return target;
    }
    else if (degress <= 180) {
        degress = degress - 90;
        degress = degress * 2 * Math.PI / 360;
        //  log(Tag, "sin " + Math.sin(degress));
        let endx = startX + radius * Math.cos(degress);
        let endy = startY + radius + radius * Math.sin(degress);
        target.push(endx);
        target.push(endy);
        return target;
    }
}

/**
 * 左边圆的计算 >180度的计算
 * @param degress
 * @param startX
 * @param startY
 * @param radius
 */
function calTargetXY1(degress, startX, startY, radius) {
    let target = [];
    //log(Tag, "开始位置1 " + startX + " " + startY + "  r: " + radius + " degress  " + degress);
    if (degress > 360) {
        degress = 360;
    }
    if (degress <= 270) {
        degress = degress - 180;
        degress = degress * 2 * Math.PI / 360;
        //  log(Tag, Math.sin(degress));
        let endx = startX - radius * Math.sin(degress);
        let endy = startY - ( radius - +radius * Math.cos(degress));
        target.push(endx);
        target.push(endy);
        return target;
    } else if (degress <= 360) {
        degress = degress - 270;
        degress = degress * 2 * Math.PI / 360;
        let endx = startX - radius * Math.cos(degress);
        let endy = startY - radius - radius * Math.sin(degress);
        target.push(endx);
        target.push(endy);
        return target;
    }
}

然后用到Art中 我们先看看 Path提供的API 发现他自己封装了一层


Path源码

然后我们发现 他是继承了Path 然后自己实现了一些对外方法 我们看真实Path类 发现一个push方法


Path push方法

看到这里 push会先解析 说明传进来肯定是一个字符串
而且会带有m l s A M这些字母 然后这时候 是不是似曾相识 这东西不就是SVG path 里面 我们前面说的那一串字符串吗 "Mx,y Arx,ry 0 0,1 x1,y1"

这时候 我们看到A 他里面比较怪 顺序都是乱来的 我们比对前端的字符串 再来看看他的意思

"Arx,ry 0 0,1 x1,y1"
i=1;
case 'A': 
this.arcTo(p[i+5], p[i+6], p[i], p[i+1], p[i+3], !+p[i+4], p[i+2]); 
break;

>this.arcTo(p[i+5], p[i+6], p[i], p[i+1], p[i+3], !+p[i+4], p[i+2]); 
其实就是
this.arcTo(p[6], p[7], p[1], p[2], p[4], !+p[5], p[3]); 
也就是
this.arcTo(x1 ,y1, rx, ry, 0, 1, 0); 
0,1,0--》大小弧度,镜像与否,x轴旋转啥的默认不管,

然后这里我们发现其实push就已经能实现工作 只要我们在push里面传入想要的字符串就行
同理 我们直接用arcTo应该也是可以的
让我们来看看效果 先用push 看效果 画一个100,20起点 150,70,90度的圆弧


直接使用push

好 push 没问题 那我们在使用arcTo 刚刚上面已经分析过了


使用arcTo

然后看效果 一脸懵逼 卧槽 不按套路出牌
可怕。。。

artTo异常效果效果

是不是感觉要炸了 我也要炸了 不知道为什么他画出这个来 理论上来讲 两个点的圆弧 应该不会画出一个圆来 很奇怪 我也看了很久么看错有什么不对 希望有会的人告知一下 我们这里用push实现就行 也合理

其实说了那么多 重点实现进度的核心就两个
一个是根据角度计算终点坐标
一个是push方法
下面先看看整个的效果吧
因为进度条是一个单独的组件 中间区域留了一个位置 可以插入你想插入的View
效果图中间的数字动画使用Animated.createAnimatedComponent实现

  • 中间留空的区域 是根据传入的半径 获取到了圆的区域 在计算中间内切正方形的区域 在通过绝对布局left top实现 具体可以看代码


    效果图

    内容说明
  • 使用方法很简单


    一个无动画 一个有动画
  • 复杂配置


    很多的配置

代码就不贴了 github有
如果对RN的ART arcTo 圆弧 不太熟悉的 可以看看Demo 希望有帮助

github地址 点我点我 可以的话 给个star

star star star

当你在穿山越岭的另一边
我在孤独的路上没有尽头
一辈子有多少的来不及
发现已经失去
最重要的东西
恍然大悟早已远去
为何总是在犯错之后
开始相信错的是自己

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容