本文首發掘金(沒寫完orz),現重寫並補全。
本文已授權鴻洋公眾號轉載
在Android中,說到圖表,我們往往都會選擇找庫,比如MPAndroidChart。
然而更多的時候,我們往往只需要某一型別的圖表,為了這個型別的圖表而不得不把整個庫(包含所有圖表邏輯)匯入進來,還是感覺有點重的。
俗話說得好,自己動手,豐衣足食。
於是就有了今天的甜甜圈。(為何叫甜甜圈。。。不覺得環形餅圖好像個甜甜圈嗎哈哈)
起源
作為程式設計師,一個新的需求/控制元件的起源,很多時候都是來源於產品,所以,這次控制元件的誕生,其實很簡單,來源於一張優化點設計圖 (只擷取部分,其餘部分不宜公開):
咋一看,這張圖so easy啦~ 3只paint,3個顏色,隨便畫畫,搞定~
可是,不知道為啥,看著這個甜甜圈還挺漂亮的,不甘心就這麼簡單的畫出來就完事。。。
於是心裡有個魅惑的聲音告訴我:“既然這個不急,只是優化點,那為何不優化徹底一點,做的比這貨更漂亮呢”
被魅惑的我,立馬熟悉的開啟AS,新建Project,填上Name,弄個類繼承View,然後。。。。
然後。。。他喵的然後怎麼幹啊!!!!
迷茫
相信很多人寫自定義控制元件都會有這個疑惑。。。我建好類了~ 我繼承好了View了~ 接下來,,,我不會做了TAT
其實不僅僅你們,就是我寫過不少的控制元件,現在建好一個類,我也偶爾會有這個情況。。。(嗯,可能寫的還是不夠多)。
原因很簡單:所有的元素,都是心裡所想,並沒有做出一個具體的動畫效果和預覽,所以方向太多,一下子很迷茫。
因為當我們想完成一個控制元件或者動畫效果是,我們往往很快就能在心裡確定好我們想要什麼效果,然而當我們真的要實施的時候,就會發現似乎不知道該怎麼把效果變成一行行的程式碼。
在迷茫的時候,我的做法是,把我的期望寫下來:
- 我的甜甜圈會動
- 我的甜甜圈可配置引數要多(自由度要高)
- 我的甜甜圈點選的時候要有個效果,至於效果,大概是浮上來呈現出Z軸上移的樣子,最好能加上陰影
- 我的甜甜圈要簡單好上手。。。
當需求寫了下來,我們就可以逐步擊破。
針對上面的需求點,我們逐步確定我們的方案:
- 甜甜圈
- 繼承View,自己畫
- 會動的甜甜圈
- Animation走起,反正就是可以逐步計算進度並讓我根據值來進行不同的繪製即可
- 可配置多
- 因為引數比較多,因此歸併到一個config類裡面,採取Builder模式,形成鏈式配置,保持清爽的編碼風格
- 點選效果
- 甜甜圈可以通過大小變化來造成z軸上浮的偽效果,加上BlurMaskFilter或者ShadowLayer
- 簡單上手
- 暴露的api儘可能少,以及面向介面程式設計
資料
受限於篇幅,這裡僅貼出核心程式碼,其餘地方以思路講解為主。
首先由簡入繁,我們先嚐試畫出一個簡單的甜甜圈:
so easy~依然是開頭所說的,3只筆,3個角度,完事。
然而,如果我們允許外部配置更多的甜甜圈的話,那豈不是要修改這個類?如果有一個方法能夠提供給我動態增刪就好了。
說做就做,我們看一下目前甜甜圈需要的元素:
- 顏色
- 描述
- 資料(計算比例用)
也就是說,如果我們跟使用者約定一個方式,約定好由使用者提供這些元素,我們僅負責渲染的話,那麼我們就可以實現動態增刪的需求了。
那麼,該如何約定呢?
假如我們要求使用一個類,也就是使用者必須傳給我們這個類,那麼對於使用者來說,是一個麻煩的使用,因為我們想繪製在圖示上的資料往往都是伺服器請求回來的,如果想畫出甜甜圈,還得做多一步轉換,這無疑是增大了複雜度,也就不符合我們簡單好用的期望了。
考慮到這個,我採取了介面的方式。介面約束要求返回以上三個元素,這樣做的好處是不用破壞使用者原來的資料,他甚至可以用原來的資料bean來實現甜甜圈的需求,另一個好處是我可以很方便的進行擴充套件。
事實上,這一點也得到了使用者的認可(ps,在國外本庫被評為2018年初值得關注的25個庫之一,因此國外issue提出的比較多,郵件聯絡的也是主要是外國友人):
根據以上條件,我們初步定義出一個介面:IPieInfo
在這之後,我們計算都可以依靠介面獲取:
- 獲取顏色決定本段甜甜圈的取色
- 獲取數值來計算本段甜甜圈的
- 獲取描述來繪製甜甜圈的描述
這樣做可以不限制使用者的資料型別,只需要實現我們的介面並對各個方法進行返回即可。
在繪製的時候,我們把使用者傳入的資料存到一個List中,在存入的時候,根據獲取的value進行計算,得到其開始角度和結束角度,並把資料包裝在一個類裡面供控制元件內部使用。而這一切,對外部來說都是不透明的,外部使用僅僅關注的是配置,而不是計算。
點選
甜甜圈的點選是一個比較麻煩的點,主要原因如下:
- 甜甜圈支援起始角度設定,而對起始角度並沒有做要求,也就是傳入-3600°也是可以的
- 點選的時候需要精確判定點選的區域在哪個甜甜圈裡
- 甜甜圈被點選後的動作,以及上一次點選的甜甜圈動畫和本次點選的甜甜圈動畫需要切換(一個還原一個上浮)
首先看看第一個問題,我們的甜甜圈雖然可以設定無限角度,但實際上其實歸根結底可以歸到0 ~ 360°之間,即便傳入一個很大的值,其實也是一定倍數 * 360 + 偏移量而已,所以針對任意角度,我們需要將其收束到0 ~ 360°之間:
在點選觸發的時候,我們先判斷點選的位置是否在甜甜圈(或者餅圖)內,判斷的方法也很簡單,就是初中的技巧計算兩點之間的直線距離。
我們獲取觸控點的x,y,計算其到中心的距離,假如當前是甜甜圈模式(環形餅圖),則需要甜甜圈內徑≤距離≤甜甜圈外徑則判定在甜甜圈內。
計算完距離後,我們需要計算角度,我們通過角度來獲取我們當前點選的是哪一段的甜甜圈。
目前我們已知點選的xy,以及中心點,這時候我們可以用atan2方法反計算出角度
//得到角度
double touchAngle = Math.toDegrees(Math.atan2(y - centerY, x - centerX));
複製程式碼
這裡計算出來的是-180° ~ 180°範圍內的值,即以x正半軸為起始,逆時針(1、2象限)則是-180° ~ 0,順時針(3、4象限)是0 ~ 180°。而我們的甜甜圈在開始的時候也說過,是無限角度的,當然,我們處理到0 ~ 360°,然而即便收束了起來,還是與我們計算出來的-180° ~ 180°對不上,因此我們對計算出來的角度需要做一下處理。
在這裡我選擇當點選的角度小於0的時候,加上360°。這裡可能會有小夥伴問我為什麼不是加上180°,而是加360°。
這個問題很簡單,因為我們的甜甜圈在轉換為0 ~ 360°之後,在1、2象限表現的是180° ~ 360°的範圍,而我們點選的角度在1、2象限是-180° ~ 0,如果加上180,則是0 ~ 180°,還是無法滿足我們的甜甜圈判斷,因此在數值小於0的情況下,我們加上360°。
接著我們根據角度尋找每一段甜甜圈裡匹配的角度進行查詢,直到找到為止。
在查詢的時候我們還需要注意轉換為0 ~ 360的情況中有一種特殊情況,就是某段甜甜圈跨越了0和360的界限。比如說圖中的情況:
其他的部分比如點選的動畫實現等,則是由Animator計算並不斷重繪。這裡就不再詳細說明。
文字
文字的繪製相對簡單,我們需要確定的是文字的位置就可以了。
從效果圖上我們知道,文字繪製有個引導線,而文字要麼在引導線上,要麼在引導線下,要麼上下都有。
為了擴充套件,此處我粗暴的給出了四種文字屬性:
- 文字都在引導線上
- 文字都在引導線下
- 文字在1、2象限在引導線上,在3、4象限處於引導線下
- 文字與引導線對齊
計算文字的位置首當其衝我們得確認文字所處象限,然而我們僅僅知道的條件只有這段甜甜圈的起始、結束角度、甜甜圈半徑,因此我們需要把角度換算為距離。
那我們需要怎麼做呢?實際上這也是三角函式的簡單運用,根據效果圖,文字指引線的起始點是在甜甜圈的中間,因此我們可以根據甜甜圈的中心點到某段甜甜圈的中間連線作為三角形的斜邊,根據三角函式sin/cos求出x,y的座標即可。
求出了文字指引線的起始點,我們就清楚該文字屬於哪個象限了,簡單的判斷x,y即可
最後關於文字引導線的繪製,我們可以簡單的PathMeasure使Path動態生長。
總結
對於甜甜圈,這個專案的主要難點在本篇已經說出,這個工程說複雜其實也不是很複雜,但說簡單我個人認為也不能說分分鐘完事,其實還是有挺多細節需要琢磨的。
誠然,這個庫還是很有進步空間,比如issue裡面所說的圖例以及資料過多時的文字重疊等等(說起來,資料過多本就不適合餅圖啊。。。。)。
但只要我還收到issue,我就不會放棄更新,一直迭代下去~
更多的話就不多說了,歡迎大家閱覽專案:github.com/razerdp/Ani…
同時也順便推薦我的另一個專案:BasePopup:github.com/razerdp/Bas…
歡迎交流哈-V-