這篇文章給大家分享的是有關(guān)如何使用kotlin實現(xiàn)一個餅圖的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
十余年的東興網(wǎng)站建設(shè)經(jīng)驗,針對設(shè)計、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。網(wǎng)絡(luò)營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整東興建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)建站從事“東興網(wǎng)站設(shè)計”,“東興網(wǎng)站推廣”以來,每個客戶項目都認(rèn)真落實執(zhí)行。
先看看做的是什么
看完圖,我們來整理下思路
餅圖居中,每塊區(qū)域都是一個扇形,需要canvas.drawArc根據(jù)角度來繪制
需要path.arcTo定位到扇形弧度的一半來繪制折線的起點
通過canvas.drawPath繪制折線,折線的長度根據(jù)餅圖大小來設(shè)置比例
通過canvas.drawText繪制文字,文字的大小根據(jù)餅圖的大小來設(shè)置比例,繪制文字的位置需要計算文字的寬度
思路清晰后就擼起袖子加油干
知識點
我們先來了解一個概念,我們在paint畫扇形的時候,對應(yīng)的度數(shù)是在哪個位置呢?
看到圖后應(yīng)該明白了吧
繪制餅圖
我們先來看看他的參數(shù),很明顯,左、上、右、下參數(shù)形成一個面板,startAngle 為起始的角度,sweepAngle 為從起始角度開始繪制多少度,useCenter為是否連接到圓心,paint為畫筆
public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint) { super.drawArc(left, top, right, bottom, startAngle, sweepAngle, useCenter, paint); }
我們以當(dāng)前控件的width、height為面板來畫一個圓形的餅圖
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.drawArc(0f, 0f, width, height, 0f, 360f, true, paintRed) }
哇塞,好丑哦,結(jié)果顯示的是一個橢圓,如果要繪制一個圓形的餅圖,我們必須得保證left=top=right=bottom
設(shè)置餅圖居中
/** * view的寬度 */ var width: Float = 0f /** * view的高度 */ var height: Float = 0f /** * drawArc距離左邊的距離 */ var left: Float = 0f /** * drawArc距離上邊的距離 */ var top: Float = 0f /** * drawArc距離右邊的距離 */ var right: Float = 0f /** * drawArc距離下邊的距離 */ var bottom: Float = 0f @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.drawArc(left, top, right, bottom, 0f, 360f, true, paint) } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) setBackgroundColor(resources.getColor(R.color.black)) width = w.toFloat() height = h.toFloat() left = width / 4f top = width / 4f right = width - left bottom = width - top }
完美居中
接下來,我們要把上面從0度到360度多分幾個步驟來繪制
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onDraw(canvas: Canvas) { super.onDraw(canvas) ... canvas.drawArc(left, top, right, bottom, 0f, 20f, true, paintPuple) canvas.drawArc(left, top, right, bottom, 20f, 10f, true, paintGray) canvas.drawArc(left, top, right, bottom, 30f, 40f, true, paintGreen) canvas.drawArc(left, top, right, bottom, 70f, 110f, true, paintBlue) canvas.drawArc(left, top, right, bottom, 180f, 110f, true, paintRed) canvas.drawArc(left, top, right, bottom, 290f, 70f, true, paintYellow) }
還不錯
上圖的度數(shù)是寫死的,現(xiàn)在我們來把他寫活
提供一個設(shè)置個數(shù)的集合,比如農(nóng)名伯伯賣水果,梨子賣了10個,香蕉賣了3個,蘋果賣了7個,那么這個個數(shù)的集合為pieList=(10,3,7)。
因為餅圖是根據(jù)角度來繪制的,我們必須將這個個數(shù)集合換算成角度集合,換算的過程中我們需要知道每一種水果所占總水果的比例,然后通過這個比例去乘上360度,就知道每一種水果所占的度數(shù)。
梨子的占比為10/(10+3+7)=1/2,可得梨子占餅圖的度數(shù)為1/2*360=180度,按照這種方式計算,香蕉和蘋果占餅圖的度數(shù)分別為54度和126度,那么,餅圖的分布也就出來了
現(xiàn)在,我們來定義一個個數(shù)集合,計算出比例的集合和度數(shù)的集合,下面是比例的集合,度數(shù)的集合我們在繪制的時候再去計算
/** * 個人分類集合 */ var pieList = arrayListOf(10f,3f,7f) /** * 餅圖所占的比例 */ var scaleList = arrayListOf() /** * 個數(shù)分類的總量 */ var total: Float = 0f override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) //計算個數(shù)的總和 total = pieList.sum() //存儲比例值 for (a in pieList) { scaleList.add(a.div(total)) } }
比例集合拿到了,接下來,我們?nèi)パh(huán)這個比例值,然后將比例值乘上360度,計算出角度值,供drawArc的sweepAngle使用,但是,我們還缺少一個startAngle起始角度,我們可以定義一個起始角度為0度,然后每次根據(jù)計算出的角度值sweepAngle去累加起始度數(shù),用代碼來實現(xiàn)下
/** * 記錄當(dāng)前畫餅圖的度數(shù) */ var currentDegree: Float = 0f /** * 累加餅圖的度數(shù)作為下一個繪制的起始度數(shù) */ var srctorDegree: Float = 0f @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onDraw(canvas: Canvas) { super.onDraw(canvas) for (scale in scaleList) { val paint = Paint() paint.strokeWidth = dip(10.0f).toFloat() paint.isAntiAlias = true //定義一個隨機生成的顏色數(shù),來區(qū)分不同的扇形區(qū)域 val hex = "#" + Integer.toHexString((-16777216 * Math.random()).toInt()) paint.color = Color.parseColor(hex) //角度數(shù) srctorDegree = scale * 360 canvas.drawArc(left, top, right, bottom, currentDegree, srctorDegree, true, paint) //累加角度 currentDegree += srctorDegree } }
ok,現(xiàn)在我們可以隨機的去定義個數(shù)來生成占比的餅圖了
繪制折線
接下來,我們來繪制折線,折線的起點是每個扇形弧上的一半,path的arcTo方法也可以繪制圓,且方法參數(shù)使用也是一樣,我們可以讓arcTo跟著canvas.drawArc一塊畫,arcTo的startAngle起始角度為canvas.drawArc起始角度加上sweepAngle度數(shù)的一半,這樣,就定位到了弧邊的一半,arcTo的sweepAngle為0就行了,我們只定位,不繪制
... canvas.drawArc(left, top, right, bottom, currentDegree, srctorDegree, true, paint) val path = Path() path.arcTo(left, top, right, bottom, currentDegree + srctorDegree / 2, 0f, false) ...
現(xiàn)在,path的位置定位到弧邊的一半了,接下來,我們要知道當(dāng)前path的坐標(biāo)然后根據(jù)坐標(biāo)去繪制折線,
val bounds = RectF() //將path當(dāng)前的坐標(biāo)賦值給bounds path.computeBounds(bounds, true)
現(xiàn)在拿到坐標(biāo)了,我們再來看看效果圖,折線和文字呈四個方向,我們不如把餅圖分成四個區(qū)域,以圓心為坐標(biāo)軸原點,切分四個象限:
第一象限:折線為右上,文字在折線右邊
第二象限:折線為左上,文字在折線左邊
第三象限:折線為左下,文字在折線左邊
第四象限:折線為右下,文字在折線右邊
那么,接下來就是如何判斷當(dāng)前起始點在哪個象限了,先以第一象限為例,如果當(dāng)前的坐標(biāo)大于餅圖橫軸方向一半,并且小于餅圖縱軸方向的一半,那么就是第一象限,其他依次類推
/** * 橫線的長度 */ var lineae: Int = 30 /** * 斜線的長度 */ var slantLine: Int = 30 override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) //計算橫線的比例 lineae = (width / 30f).toInt() //計算斜線的比例 slantLine = (width / 40f).toInt() } @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onDraw(canvas: Canvas) { super.onDraw(canvas) for (scale in scaleList) { ... val path = Path() path.arcTo(left, top, right, bottom, currentDegree + srctorDegree / 2, 0f, false) val bounds = RectF() path.computeBounds(bounds, true) //第一象限 if (bounds.left >= width / 2 && bounds.top <= width / 2) { path.lineTo(bounds.left + lineae, bounds.top) path.lineTo(bounds.left + lineae + slantLine, bounds.top - slantLine) canvas.drawPath(path, paintLine) //第二象限 } else if (bounds.left <= width / 2 && bounds.top <= width / 2) { path.lineTo(bounds.left - lineae, bounds.top) path.lineTo(bounds.left - lineae - slantLine, bounds.top - slantLine) canvas.drawPath(path, paintLine) //第三象限 } else if (bounds.left <= width / 2 && bounds.top >= width / 2) { path.lineTo(bounds.left - lineae, bounds.top) path.lineTo(bounds.left - lineae - slantLine, bounds.top + slantLine) canvas.drawPath(path, paintLine) //第四象限 } else { path.lineTo(bounds.left + lineae, bounds.top) path.lineTo(bounds.left + lineae + slantLine, bounds.top + slantLine) canvas.drawPath(path, paintLine) } } ... }
哎呀,出來了
繪制文字
接下來就是繪制文字了,第一、四象限還好,文字可以在折線后面跟著畫,但是二、三象限的文字就不允許了,我們必須往前移動文字寬度的距離才能完美銜接到折線上,所以,我們來定義一個計算文字的方法
/** * 獲取文字的寬度 */ private fun getStringWidth(str: String): Float = paintLine.measureText(str)
文字是會隨著餅圖的大小進(jìn)行改變的,所以設(shè)置文字大小的比例
paintLine.textSize = dip(width / 100).toFloat()
接下來就開始繪制文字吧
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onDraw(canvas: Canvas) { super.onDraw(canvas) ... //獲取當(dāng)前的百分比文字 val textStr = String.format("%.2f%%", scale * 100) //獲取文字的寬度 val textWidth = getStringWidth(textStr) //第一象限 if (bounds.left >= width / 2 && bounds.top <= width / 2) { ... canvas.drawText(textStr, bounds.left + lineae + slantLine, bounds.top - slantLine, paintText) ... //第二象限 } else if (bounds.left <= width / 2 && bounds.top <= width / 2) { ... canvas.drawText(textStr, bounds.left - lineae - slantLine - textWidth, bounds.top - slantLine, paintText) ... //第三象限 } else if (bounds.left <= width / 2 && bounds.top >= width / 2) { ... canvas.drawText(textStr, bounds.left - lineae - slantLine - textWidth, bounds.top + lineae, paintText) ... //第四象限 } else { ... canvas.drawText(textStr, bounds.left + lineae + slantLine, bounds.top + slantLine, paintText) ... } }
嗯,還不錯,
然后我們再看看效果圖,餅圖中間還有一塊與背景色一樣的黑圓,這不跟簡單了嘛
//定義中間黑圓的畫筆 paintCicle.color = resources.getColor(R.color.black) paintCicle.isAntiAlias = true paintCicle.style = Paint.Style.FILL @RequiresApi(Build.VERSION_CODES.LOLLIPOP) override fun onDraw(canvas: Canvas) { super.onDraw(canvas) ... //在循環(huán)結(jié)束餅圖的時候,以餅圖的原點為中心畫圓 canvas.drawCircle(width / 2, width / 2, width / 8, paintCicle) }
然后我們暴露一個方法,提供給Activity去調(diào)用
/** * 設(shè)置扇形參數(shù) */ fun setPieData(a: ArrayList) { pieList.clear() pieList.addAll(a) invalidate() }
那么,Activity就可以這么去調(diào)用了
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_second) pie1.setPieData(arrayListOf(1f,10f,15f,9f,15f)) pie2.setPieData(arrayListOf(3f,8f,15f,7f,9f)) pie3.setPieData(arrayListOf(9f,3f,7f,3f,4f,2f,1f)) }
感謝各位的閱讀!關(guān)于“如何使用kotlin實現(xiàn)一個餅圖”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!