美文网首页
安卓自定义曲线折线图

安卓自定义曲线折线图

作者: Android小豆渣 | 来源:发表于2022-12-27 15:23 被阅读0次

    先看下效果图~是不是你需要的(Y轴的文字也有,只是需求需要把它隐藏了)


    Screenshot_20221227_151816.jpg

    代码如下:

    class CurveLineChartView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
        /**
         * X轴文字画笔
         */
        private val mXTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
    
        /**
         * Y轴文字画笔
         */
        private val mYTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
    
        /**
         * 虚线画笔
         */
        private val mDottedLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)
    
        /**
         * 曲线画笔
         */
        private val mCurvePaint = Paint(Paint.ANTI_ALIAS_FLAG)
    
        /**
         * X轴文字
         */
        private val mXText = arrayListOf("Day1", "Day2", "Day3", "Day4", "Day5", "Day6", "Day7")
    
        /**
         * X轴边距
         */
        private var startX = 50f
    
        /**
         * X轴文本间隔
         */
        private var mXTextInterval = 0f
    
        /**
         * Y轴文字
         */
        private var mYText = arrayListOf("0", "5", "10", "15", "20", "25")
    
        /**
         * Y轴到X轴边距
         */
        private var yMarginX = dp2px(6f)
    
        /**
         * X轴到Y轴边距
         */
        private var xMarginY = dp2px(12f)
    
        /**
         * Y轴0点坐标
         */
        private var mStartY = 0f
    
        /**
         * Y轴文本间隔
         */
        private var mYTextInterval = 0f
    
        /**
         * 每个X轴中心点坐标
         */
        private val mXCentre = ArrayList<Float>()
    
        /**
         * Y轴总高度
         */
        private var mYHeight = 0f
    
        /**
         *  折线的弯曲率
         */
        private val smoothness = 0.36f
    
        /**
         * Y轴最大值 默认取25
         */
        private var maxYValue = 25
    
        private val mData = ArrayList<Float>()
    
        private val mData2 = ArrayList<Float>()
    
        init {
            //初始化各个画笔
            mXTextPaint.color = Color.parseColor("#999999")
            mXTextPaint.strokeWidth = 1f
            mXTextPaint.textSize = TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                10f,
                resources.displayMetrics
            )
    
            mYTextPaint.color = Color.parseColor("#999999")
            mYTextPaint.strokeWidth = 1f
            mYTextPaint.textSize = TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                12f,
                resources.displayMetrics
            )
    
            mDottedLinePaint.color = Color.parseColor("#F2F2F2")
            mDottedLinePaint.strokeWidth = dp2px(1f).toFloat()
            mDottedLinePaint.style = Paint.Style.STROKE
            //意思是所画虚线规则是先画10个长度的实线,留下5个长度的空白
            //数组规则:虚、实、虚、实---先虚再实,每个数代表长度
            val effects = DashPathEffect(floatArrayOf(5f, 10f), 0f)
            mDottedLinePaint.pathEffect = effects
    
            mCurvePaint.strokeWidth = dp2px(4f).toFloat()
            mCurvePaint.style = Paint.Style.STROKE
            mCurvePaint.strokeCap = Paint.Cap.ROUND
        }
    
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            super.onSizeChanged(w, h, oldw, oldh)
            startX = getTextWidth(mYTextPaint, maxYValue.toString()) + xMarginY
            //文字总宽
            var mTextTotalWidth = 0f
            for (i in mXText) {
                mTextTotalWidth += getTextWidth(mXTextPaint, i)
            }
            //文本间隔
            mXTextInterval = (width - startX - mTextTotalWidth) / (mXText.size - 1)
    
            var mTextTotalHeight = 0f
            for (i in mYText) {
                mTextTotalHeight += getTextHeight(mYTextPaint, i)
            }
            mYTextInterval = (height - yMarginX - getTextHeight(
                mXTextPaint,
                "Day1"
            ) - mTextTotalHeight) / (mYText.size - 1)
    
        }
    
    
        override fun onDraw(canvas: Canvas) {
            drawXText(canvas)
    
            drawYText(canvas)
    
            mCurvePaint.color = Color.parseColor("#688CFD")
            drawCurveLine(canvas, mData)
            mCurvePaint.color = Color.parseColor("#FAA264")
            drawCurveLine(canvas, mData2)
        }
    
    
        /**
         * 画曲线
         */
        private fun drawCurveLine(canvas: Canvas, data: List<Float>) {
            //一个数据画点
            if (data.size == 1) {
                canvas.drawCircle(
                    mXCentre[0],
                    mStartY - mYHeight / maxYValue * data[0],
                    dp2px(1f).toFloat(),
                    mCurvePaint
                )
                return
            }
            val path = Path()
            var lX = 0f
            var lY = 0f
            for ((index, item) in data.withIndex()) {
                if (index == 0) {
                    path.moveTo(mXCentre[index], mStartY - mYHeight / maxYValue * item)
                } else {
                    //当前点坐标
                    val px = mXCentre[index]
                    val py = mStartY - mYHeight / maxYValue * item
                    //上个点坐标
                    val firstX = mXCentre[index - 1]
                    val firstY = mStartY - mYHeight / maxYValue * data[index - 1]
    
                    val x1: Float = firstX + lX
                    val y1: Float = firstY + lY
                    //下个点坐标
                    val secondX = mXCentre[if ((index + 1) < data.size) index + 1 else index]
                    val secondY =
                        mStartY - mYHeight / maxYValue * data[if ((index + 1) < data.size) index + 1 else index]
                    lX = (secondX - firstX) / 2 * smoothness
                    lY = (secondY - firstY) / 2 * smoothness
    
                    val x2: Float = px - lX
                    var y2: Float = py - lY
                    if (y1 == py) {
                        y2 = y1
                    }
                    //和之前Y点坐标相等直接画直线
                    if (firstY == py) {
                        path.lineTo(px, py)
                    } else {
                        path.cubicTo(x1, y1, x2, y2, px, py)
                    }
                }
            }
    
            canvas.drawPath(path, mCurvePaint)
        }
    
        /**
         * 画Y轴文字和虚线
         */
        private fun drawYText(canvas: Canvas) {
            //最大文字宽度
            val maxTextWidth = getTextWidth(mYTextPaint, mYText[mYText.size - 1])
            //开始Y轴坐标点
            var starY = getTextHeight(mYTextPaint, mYText[mYText.size - 1]).toFloat()
            var minHeight = 0f
            //倒叙输出
            for ((index, text) in mYText.reversed().withIndex()) {
                //居中画出Y轴数字
                var starX = 0f
                val mWidthDifference = maxTextWidth - getTextWidth(mYTextPaint, text)
                if (mWidthDifference > 0) {
                    starX = mWidthDifference / 2
                }
                canvas.drawText(
                    text,
                    starX,
                    starY,
                    mYTextPaint
                )
                //画虚线
                canvas.drawLine(
                    startX,
                    starY - (getTextHeight(mYTextPaint, text) / 2),
                    width.toFloat(),
                    starY - (getTextHeight(mYTextPaint, text) / 2),
                    mDottedLinePaint
                )
    
                if (index == 0) {
                    minHeight = starY - (getTextHeight(mYTextPaint, text) / 2)
                }
                if (index == mYText.size - 1) {
                    mStartY = starY - (getTextHeight(mYTextPaint, text) / 2)
                }
                starY += getTextHeight(mYTextPaint, text) + mYTextInterval
            }
            mYHeight = mStartY - minHeight
        }
    
    
        /**
         * 画X轴文字
         */
        private fun drawXText(canvas: Canvas) {
            mXCentre.clear()
            var currentX = startX
            for (text in mXText) {
                canvas.drawText(
                    text,
                    currentX,
                    height - mXTextPaint.fontMetrics.descent,
                    mXTextPaint
                )
                mXCentre.add(currentX + getTextWidth(mXTextPaint, text) / 2)
                currentX += getTextWidth(mXTextPaint, text) + mXTextInterval
            }
        }
    
        /**
         * 获取文字的宽
         */
        private fun getTextWidth(paint: Paint, str: String): Float {
            return paint.measureText(str)
        }
    
        /**
         * 获取文字的高
         */
        private fun getTextHeight(paint: Paint, str: String): Int {
            val rect = Rect()
            paint.getTextBounds(str, 0, str.length, rect)
            return rect.height()
        }
    
        private fun dp2px(dp: Float): Int {
            val scale = context.resources.displayMetrics.density
            return (dp * scale + 0.5f).toInt()
        }
    
        /**
         * 设置数据源
         */
        fun setData(data1: List<Float>, data2: List<Float>) {
            //先找出最大值
            var maxValue = 0f
            for (i in data1) {
                if (i > maxValue) {
                    maxValue = i
                }
            }
            for (i in data2) {
                if (i > maxValue) {
                    maxValue = i
                }
            }
            if (maxValue <= 0) {
                maxYValue = 25
                mYText = arrayListOf("0", "5", "10", "15", "20", "25")
            } else {
                //算出Y轴的最大值 使其最大值不能大于第五条虚线
                while (maxValue.toInt() % 4 != 0) {
                    maxValue += 1
                }
                maxYValue = (maxValue + (maxValue.toInt() / 4)).toInt()
                mYText.clear()
                for (i in 0..maxYValue step (maxYValue / 5)) {
                    mYText.add(i.toString())
                }
            }
            mData.clear()
            mData2.clear()
            mData.addAll(data1)
            mData2.addAll(data2)
    
            invalidate()
        }
    }
    

    相关文章

      网友评论

          本文标题:安卓自定义曲线折线图

          本文链接:https://www.haomeiwen.com/subject/pjpnqdtx.html