1. 程式人生 > >ReactNative 圓形進度條 ART Path arcTo 圓弧實現

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); 
010--》大小弧度,映象與否,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 希望有幫助