ReactNative

ReactNative圆形进度条 ART Path arcTo

2016-12-18  本文已影响2246人  Daemon1993

首先来吐槽一番
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实现


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

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

star star star

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

上一篇下一篇

猜你喜欢

热点阅读