一塊兒擼個環形 Android 圖表

本文首發掘金(沒寫完orz),現重寫並補全。java

本文已受權鴻洋公衆號轉載git

在Android中,說到圖表,咱們每每都會選擇找庫,好比MPAndroidChart。程序員

然而更多的時候,咱們每每只須要某一類型的圖表,爲了這個類型的圖表而不得不把整個庫(包含全部圖表邏輯)導入進來,仍是感受有點重的。github

俗話說得好,本身動手,豐衣足食。編程

因而就有了今天的甜甜圈。(爲什麼叫甜甜圈。。。不以爲環形餅圖好像個甜甜圈嗎哈哈)api


起源

做爲程序員,一個新的需求/控件的起源,不少時候都是來源於產品,因此,此次控件的誕生,其實很簡單,來源於一張優化點設計圖 (只截取部分,其他部分不宜公開)服務器

咋一看,這張圖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正半軸爲起始,逆時針(一、2象限)則是-180° ~ 0,順時針(三、4象限)是0 ~ 180°。而咱們的甜甜圈在開始的時候也說過,是無限角度的,固然,咱們處理到0 ~ 360°,然而即使收束了起來,仍是與咱們計算出來的-180° ~ 180°對不上,所以咱們對計算出來的角度須要作一下處理。

在這裏我選擇當點擊的角度小於0的時候,加上360°。這裏可能會有小夥伴問我爲何不是加上180°,而是加360°。

這個問題很簡單,由於咱們的甜甜圈在轉換爲0 ~ 360°以後,在一、2象限表現的是180° ~ 360°的範圍,而咱們點擊的角度在一、2象限是-180° ~ 0,若是加上180,則是0 ~ 180°,仍是沒法知足咱們的甜甜圈判斷,所以在數值小於0的狀況下,咱們加上360°。

接着咱們根據角度尋找每一段甜甜圈裏匹配的角度進行查詢,直到找到爲止。

在查找的時候咱們還須要注意轉換爲0 ~ 360的狀況中有一種特殊狀況,就是某段甜甜圈跨越了0和360的界限。好比說圖中的狀況:

其餘的部分好比點擊的動畫實現等,則是由Animator計算並不斷重繪。這裏就再也不詳細說明。

文字

文字的繪製相對簡單,咱們須要肯定的是文字的位置就能夠了。

從效果圖上咱們知道,文字繪製有個引導線,而文字要麼在引導線上,要麼在引導線下,要麼上下都有。

爲了擴展,此處我粗暴的給出了四種文字屬性:

  • 文字都在引導線上
  • 文字都在引導線下
  • 文字在一、2象限在引導線上,在三、4象限處於引導線下
  • 文字與引導線對齊

計算文字的位置首當其衝咱們得確認文字所處象限,然而咱們僅僅知道的條件只有這段甜甜圈的起始、結束角度、甜甜圈半徑,所以咱們須要把角度換算爲距離。

那咱們須要怎麼作呢?實際上這也是三角函數的簡單運用,根據效果圖,文字指引線的起始點是在甜甜圈的中間,所以咱們能夠根據甜甜圈的中心點到某段甜甜圈的中間連線做爲三角形的斜邊,根據三角函數sin/cos求出x,y的座標便可。

求出了文字指引線的起始點,咱們就清楚該文字屬於哪一個象限了,簡單的判斷x,y便可

最後關於文字引導線的繪製,咱們能夠簡單的PathMeasure使Path動態生長。

總結

對於甜甜圈,這個項目的主要難點在本篇已經說出,這個工程說複雜其實也不是很複雜,但說簡單我我的認爲也不能說分分鐘完事,其實仍是有挺多細節須要琢磨的。

誠然,這個庫仍是頗有進步空間,好比issue裏面所說的圖例以及數據過多時的文字重疊等等(提及來,數據過多本就不適合餅圖啊。。。。)。

但只要我還收到issue,我就不會放棄更新,一直迭代下去~

更多的話就很少說了,歡迎你們閱覽項目:github.com/razerdp/Ani…

同時也順便推薦個人另外一個項目:BasePopup:github.com/razerdp/Bas…

歡迎交流哈-V-

相關文章
相關標籤/搜索