自定義View之kotlin繪製折線圖
引言
早上看到有個童鞋在群裡面發牢騷,說這個自定義view怎麼畫,不太會,ok,正好我也沒事,那我就花兩個小時幫你搞定他吧,先看下他要的效果;

微信圖片_20180920104634.png
再來看下我實現的效果

aa.gif
實現過程
主要分為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個,我們分為七份,從第一份之後開始繪製:
程式碼如下:
privatefun 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++ } } } }
最後繪製折線和折現上面的小球
這裡我們需要把資料放進兩個集合傳入,然後根據資料算出每個點的座標,最後根據path把每個點連線起來就ok了;
程式碼如下:
/** * 設定點的值 */ fun setPointValues(valueNew: ArrayList<Int>, valueOld: ArrayList<Int>) { this.valueNew = valueNew this.valueOld = valueOld } /** * 根據值計算在該值的 x,y座標 */ 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 } /** * 根據值繪製折線 */ 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) }
這樣大體效果就已經實現了,最後加陰影,就更簡單了,就直接給畫筆設定陰影就行了
喜歡請到github點個贊啦
地址: ofollow,noindex">github