ReactNative圓形進(jìn)度條 ART Path arcTo 圓弧實(shí)現(xiàn)

首先來吐槽一番
Facebook 對ReactNative添加了ART庫后 竟然沒有官方文檔說明這個咋用 簡直可怕.... 并不是每個來做RN開發(fā)都是懂得前端常常使用SVG Path 這些東西的啊 大哥

同時相比于Android 等開發(fā) RN還比較新 導(dǎo)致的結(jié)果是遇到問題后 能用的資料少啊。。。這種情況下 連最應(yīng)該有的文檔都沒有 吐血三升先。。。

。。心里苦。。。

所以我就查了一下RN開發(fā)的繪圖庫ART 發(fā)現(xiàn)不是canvas之類的(Android中用canvas習(xí)慣了) 最可怕的是網(wǎng)上只有一些簡單的關(guān)于ART的說明 和一個大兄弟封裝了一個SVG基于ART的庫 就和在前端使用SVG那樣使用來繪制 我不想這樣使用 就只能自己摸索著開始

在開始畫圓弧進(jìn)度條之前 我們可能要先熟悉一下SVG Path(RN的path內(nèi)部工作也是一樣的) 是如何工作的 以及 里面的一些參數(shù)的具體作用 這里 因?yàn)槭菍VG Path的介紹 采用React.js畫出一些效果 來看看path是如何繪制出我們想要的圓弧的 我這里也就根據(jù)幾個效果簡單說一下 具體可以自己去查相關(guān)的只是 SVG Path 關(guān)鍵詞搜索就行

Path 圓弧繪制

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

看看這段字符串 寓意
M開頭表示在坐標(biāo)軸中的起始位置x,y
A表示即將繪制圓弧 后面是繪制圓弧半徑的意思 一個是x軸的半徑 一個是y軸的半徑 其實(shí)就是在用弧度把這兩個點(diǎn)連起來的時候 x比y大 x方向就長一些 如果x=y=radius 就是一個圓的弧度
這里應(yīng)該涉及到橢圓的相關(guān)知識 具體我也不太懂 大概猜是這個意思
繼續(xù) A50,50 0 0,1 100,100 這是一個完整圓弧的全部參數(shù)
先說最后的100,100 這個是結(jié)束點(diǎn)的坐標(biāo)

150,70 結(jié)束坐標(biāo)

0 0,1 第一個0是r軸的選擇角度還是啥來著 默認(rèn)是0不管他 具體我也不太清楚別人都是這么說的
第二個0是畫大弧度還是小弧度 的意思 兩個點(diǎn)可以畫出弧度最小和最大 看圖
0,1->小弧度

1,1->大弧度

再看0,1中的1 這個是鏡像的意思 默認(rèn)是順時針方向 比如0->小弧度的這個圖 是順時針的情況 現(xiàn)在將1改成0 看圖
0,0-->情況

1,0-->情況

還有一個漸變的使用 順便也貼一個圖吧
漸變效果

好了 A后面的幾個參數(shù)的意思 這幾個圖應(yīng)該能看懂了 如果我這里沒有解釋很清楚 (我也是剛會 可能解釋不太清楚 ) 可以自己再去查查SVG Path相關(guān)的知識點(diǎn)


現(xiàn)在理解了這個Path的相關(guān)參數(shù)的含義 后面我們看RN的art的時候也就很好理解了 我們現(xiàn)在來看RN的ART怎么使用
從 ‘react-native中引入 ART’ ART中有一些模塊
直接進(jìn)去ReatART源碼

ART源碼提供的功能模塊

東西還挺多的 這里我們用到Surface (Group(Shape(Path)))
然后這里我看見他有一個類 LinearGradient 這個應(yīng)該是用來實(shí)現(xiàn)剛剛React中那種漸變效果的 但是這里我很努力地去把他這個和Shape對接起來
對于LinearGradient 這個對象 需要一個Colors集合 這個Color對象也是ReatART提供的
這里我不知道怎么構(gòu)造這個LinearGradient對象 求教哪位會的 麻煩告訴我一下
LinearGradient

LinearGradient對象傳入最后會走這個方法

現(xiàn)在轉(zhuǎn)移到圓弧的進(jìn)度條的中心思想來 先思考圓弧進(jìn)度條怎么畫

現(xiàn)在假設(shè)我們某個頂部的點(diǎn)是起始點(diǎn)(100,0)(top) 半徑50
這樣如果我們來畫一個圓 四個最特殊的點(diǎn)的坐標(biāo)如下
left-->(0,50)
bottom->(100,200)
right->(150,50)

圓的四個外圍點(diǎn)

現(xiàn)在我們知道了這四個外圍點(diǎn)坐標(biāo) 這是特殊點(diǎn) 我們和容易畫出來 現(xiàn)在問題是怎么畫出剩余部分的 比如下圖的 我們想滑到A點(diǎn)
A點(diǎn)的圓弧

想畫到A點(diǎn)我們首先要找到A點(diǎn)的坐標(biāo) 這里就要用到sin cos來計(jì)算 因?yàn)榻嵌纫欢ǖ那闆r下 確定了radius
那么在這個點(diǎn)就確定了 比如假設(shè)紅色部分的角度是30度
那么 A點(diǎn)的坐標(biāo) 就是 x=100+sin(角度) y=半徑-cos(角度)
角度對于的值

因?yàn)閟in cos涉及到正負(fù) 所以這里我們分層四部分處理
0-90 90-180 180-270 270-360
這里還要提醒一點(diǎn) 因?yàn)槭菆A弧 兩點(diǎn)一個弧度 所以一整整圓不可能一個弧度完成(我是這么理解的 要是可以的話可以告訴我下 這樣我代碼部分也能簡單點(diǎn)) 我們這里就轉(zhuǎn)個彎 用兩個半圓 左邊半圓180-360 右邊圓0-180 下面是計(jì)算公式 也是核心代碼

/**
 * 計(jì)算目的坐標(biāo)位置 右邊 <180度的計(jì)算
 * @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, '強(qiáng)制 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;
    }
}

/**
 * 左邊圓的計(jì)算 >180度的計(jì)算
 * @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 發(fā)現(xiàn)他自己封裝了一層


Path源碼

然后我們發(fā)現(xiàn) 他是繼承了Path 然后自己實(shí)現(xiàn)了一些對外方法 我們看真實(shí)Path類 發(fā)現(xiàn)一個push方法


Path push方法

看到這里 push會先解析 說明傳進(jìn)來肯定是一個字符串
而且會帶有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]); 
其實(shí)就是
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軸旋轉(zhuǎn)啥的默認(rèn)不管,

然后這里我們發(fā)現(xiàn)其實(shí)push就已經(jīng)能實(shí)現(xiàn)工作 只要我們在push里面?zhèn)魅胂胍淖址托?br> 同理 我們直接用arcTo應(yīng)該也是可以的
讓我們來看看效果 先用push 看效果 畫一個100,20起點(diǎn) 150,70,90度的圓弧


直接使用push

好 push 沒問題 那我們在使用arcTo 剛剛上面已經(jīng)分析過了


使用arcTo

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

artTo異常效果效果

是不是感覺要炸了 我也要炸了 不知道為什么他畫出這個來 理論上來講 兩個點(diǎn)的圓弧 應(yīng)該不會畫出一個圓來 很奇怪 我也看了很久么看錯有什么不對 希望有會的人告知一下 我們這里用push實(shí)現(xiàn)就行 也合理

其實(shí)說了那么多 重點(diǎn)實(shí)現(xiàn)進(jìn)度的核心就兩個
一個是根據(jù)角度計(jì)算終點(diǎn)坐標(biāo)
一個是push方法
下面先看看整個的效果吧
因?yàn)檫M(jìn)度條是一個單獨(dú)的組件 中間區(qū)域留了一個位置 可以插入你想插入的View
效果圖中間的數(shù)字動畫使用Animated.createAnimatedComponent實(shí)現(xiàn)

  • 中間留空的區(qū)域 是根據(jù)傳入的半徑 獲取到了圓的區(qū)域 在計(jì)算中間內(nèi)切正方形的區(qū)域 在通過絕對布局left top實(shí)現(xiàn) 具體可以看代碼


    效果圖

    內(nèi)容說明
  • 使用方法很簡單


    一個無動畫 一個有動畫
  • 復(fù)雜配置


    很多的配置

代碼就不貼了 github有
如果對RN的ART arcTo 圓弧 不太熟悉的 可以看看Demo 希望有幫助

github地址 點(diǎn)我點(diǎn)我 可以的話 給個star

star star star

當(dāng)你在穿山越嶺的另一邊
我在孤獨(dú)的路上沒有盡頭
一輩子有多少的來不及
發(fā)現(xiàn)已經(jīng)失去
最重要的東西
恍然大悟早已遠(yuǎn)去
為何總是在犯錯之后
開始相信錯的是自己

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容