自定義View之kotlin繪制折線圖實(shí)例教程
什么是Kotlin
Kotlin,它是JetBrains開發(fā)的基于JVM的面向?qū)ο蟮恼Z言。2017年的時候被Google推薦Android的官方語言,同時Android studio 3.0正式支持這門語言,在這個編譯器上創(chuàng)建一個Kotlin項(xiàng)目,非常方便,甚至可以Java轉(zhuǎn)為Kotlin。
引言
早上看到有個童鞋在群里面發(fā)牢騷,說這個自定義view怎么畫,不太會,ok,正好我也沒事,那我就花兩個小時幫你搞定他吧,先看下他要的效果;

再來看下我實(shí)現(xiàn)的效果

實(shí)現(xiàn)過程
主要分為x軸和y軸,由效果圖可以看出,x軸主要分為7份,y軸主要分為4份,這樣劃分就比較簡單的知道每條線的位置,每個位置的位置了,繪制起來就很簡單;
先繪制y軸的四條線和文字
文字有三個,放到一個list里面,然后我們均分高度,然后遍歷文字集合,根絕不同的高度繪制文字和橫線
看下代碼:
/**
* 繪制邊框線和邊框文本
*/
private fun drawBorderLineAndText(canvas: Canvas) {
when {
valueTextY.size > 0 -> {
val averageHeight = mNeedDrawHeight / (valueTextY.size + 1)
(1..valueTextY.size + 1).forEach { i ->
val nowadayHeight = averageHeight * (valueTextY.size + 1 - i)
canvas.drawLine(mBrokenLineLeft, nowadayHeight + mBrokenLineTop, mNeedDrawWidth, nowadayHeight + mBrokenLineTop, mBorderLinePaint)
if (i < valueTextY.size + 1) {
val fm = mTextPaint.fontMetrics
val mTxtHeight = Math.ceil((fm.leading - fm.ascent).toDouble()).toInt()
canvas.drawText(valueTextY[valueTextY.size - i].toString() + "萬", mBrokenLineLeft, nowadayHeight + mBrokenLineTop - averageHeight / 2 + mTxtHeight / 2, mTextPaint)
}
}
}
}
}
然后繪制x軸的文字
由于文字有6個,我們分為七份,從第一份之后開始繪制:
代碼如下:
private fun drawMonthText(canvas: Canvas) {
when {
valueOld.size > 0 -> {
var month = defaultStartMonth
for (i in 1..valueOld.size) {
val averageWidth = (mNeedDrawWidth / (valueOld.size + 1)).toInt()
val fm = mTextPaint.fontMetrics
val mTxtHeight = Math.ceil((fm.leading - fm.ascent).toDouble()).toInt()
canvas.drawText(month.toString() + "月", (averageWidth * i).toFloat(), mNeedDrawHeight - mTxtHeight / 2, mTextPaint)
month++
}
}
}
}
最后繪制折線和折現(xiàn)上面的小球
這里我們需要把數(shù)據(jù)放進(jìn)兩個集合傳入,然后根據(jù)數(shù)據(jù)算出每個點(diǎn)的坐標(biāo),最后根據(jù)path把每個點(diǎn)連接起來就ok了;
代碼如下:
/**
* 設(shè)置點(diǎn)的值
*/
fun setPointValues(valueNew: ArrayList<Int>, valueOld: ArrayList<Int>) {
this.valueNew = valueNew
this.valueOld = valueOld
}
/**
* 根據(jù)值計算在該值的 x,y坐標(biāo)
*/
fun getPoints(list: ArrayList<Int>): ArrayList<Point> {
val avaregwidth = mNeedDrawWidth / (list.size + 1)
val points = ArrayList<Point>(list.size)
list.indices.forEach { i ->
val valueY = list[i].toFloat()
val averaHeight = (mNeedDrawHeight * 3 / 4 / maxValue).toDouble()
val drawHeight = mNeedDrawHeight * 3 / 4 - (valueY * averaHeight).toFloat() + mBrokenLineTop
val pointY = drawHeight.toInt()
val pointX = ((avaregwidth * (i + 1)).toInt() + mBrokenLineLeft).toInt()
val point = Point(pointX, pointY)
points.add(point)
}
return points
}
/**
* 根據(jù)值繪制折線
*/
private fun drawBrokenLine(canvas: Canvas) {
when {
valueOld.size > 0 && valueNew.size > 0 -> {
val mPathOld = Path()
val mPathNew = Path()
val mPathOldshadow = Path()
val mPathNewshadow = Path()
val pointsOld = getPoints(valueOld)
val pointsNew = getPoints(valueNew)
for (i in 0 until mAnimatorValue) {
val pointOld = pointsOld[i]
val pointNew = pointsNew[i]
if (i == 0) {
mPathOld.moveTo(pointOld.x.toFloat(), pointOld.y.toFloat())
mPathNew.moveTo(pointNew.x.toFloat(), pointNew.y.toFloat())
mPathOldshadow.moveTo(pointOld.x.toFloat(), pointOld.y.toFloat())
mPathNewshadow.moveTo(pointNew.x.toFloat(), pointNew.y.toFloat())
} else {
mPathOld.lineTo(pointOld.x.toFloat(), pointOld.y.toFloat())
mPathNew.lineTo(pointNew.x.toFloat(), pointNew.y.toFloat())
mPathOldshadow.lineTo(pointOld.x.toFloat(), pointOld.y.toFloat())
mPathNewshadow.lineTo(pointNew.x.toFloat(), pointNew.y.toFloat())
}
}
mBrokenLinePaint.color = Color.parseColor("#ff5400")
canvas.drawPath(mPathOld, mBrokenLinePaint)
mBrokenLinePaint.color = Color.parseColor("#ffff00")
canvas.drawPath(mPathNew, mBrokenLinePaint)
}
}
}
/**
* 繪制線上的圓
*/
private fun drawLineCircle(canvas: Canvas) {
when {
valueOld.size > 0 && valueNew.size > 0 -> {
val pointsOld = getPoints(valueOld)
val pointsNew = getPoints(valueNew)
for (i in 0 until mAnimatorValue) {
val pointOld = pointsOld[i]
val pointNew = pointsNew[i]
mCirclePaint.color = Color.parseColor("#ff5400")
canvas.drawCircle(pointOld.x.toFloat(), pointOld.y.toFloat(), radius, mCirclePaint)
mCirclePaint.color = Color.parseColor("#ffff00")
canvas.drawCircle(pointNew.x.toFloat(), pointNew.y.toFloat(), radius, mCirclePaint)
}
}
}
}
最后就是添加動畫了,可有可無,看需求吧
代碼如下:
private fun initAnimator( size:Int) {
valueAnimator = ValueAnimator.ofInt(0, size).setDuration(defaultDuration.toLong())
mUpdateListener = ValueAnimator.AnimatorUpdateListener { animation ->
mAnimatorValue = animation.animatedValue as Int
invalidate()
}
valueAnimator.addUpdateListener(mUpdateListener)
valueAnimator.start()
}
fun setPointValues(valueNew: ArrayList<Int>, valueOld: ArrayList<Int>) {
this.valueNew = valueNew
this.valueOld = valueOld
initAnimator(valueNew.size)
}
這樣大體效果就已經(jīng)實(shí)現(xiàn)了,最后加陰影,就更簡單了,就直接給畫筆設(shè)置陰影就行了
喜歡請到github點(diǎn)個贊啦
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
Bootstrap 下拉菜單.dropdown的具體使用方法
這篇文章主要介紹了Bootstrap 下拉菜單.dropdown的具體使用方法,詳細(xì)講解下拉菜單的交互,有興趣的可以了解一下2017-10-10
Android 自定義SeekBar 實(shí)現(xiàn)分段顯示不同背景顏色的示例代碼
這篇文章主要介紹了Android 自定義SeekBar 實(shí)現(xiàn)分段顯示不同背景顏色,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
Android實(shí)現(xiàn)下載進(jìn)度條效果
vivo商店在下載應(yīng)用的時候,底部有一個圓角矩形的下載進(jìn)度條,中間有一個進(jìn)度文字,而且進(jìn)度和文字交匯的時候,交匯部分的文字會從藍(lán)色邊為白色,會有一種一半白色字,一半藍(lán)色字的效果。本文將仿照該樣式實(shí)現(xiàn)一個2021-06-06
Android LayoutInflater深入分析及應(yīng)用
這篇文章主要介紹了Android LayoutInflater分析的相關(guān)資料,需要的朋友可以參考下2017-02-02
Android Accessibility 輔助功能簡單介紹
這篇文章主要介紹了Android Accessibility 輔助功能簡單介紹的相關(guān)資料,文字轉(zhuǎn)語音,觸覺反饋,手勢操作,軌跡球和手柄操作,需要的朋友可以參考下2016-11-11

