渐变心率折线图

package com.example.myapplication.mycustom

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.View
import kotlin.math.min

class SportHeartRateChart @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    private var mWidth: Int = 0
    private var mHeight: Int = 0
    private val yValueTextRect = Rect()
    private val yValueUnitTextRect = Rect()
    private val xValueUnitTextRect = Rect()
    private val chart = Rect()
    private var axisLineWidth = 5
    private var polyLineWidth = 4
    private var axisValueTextDistanceAxis = 22
    private var axisValueTextSize = 26f
    private var yAxisValueUnitTextSize = 26f
    private var xAxisValueUnitTextSize = 26f
    private val maxHeartRate = 160
    private val yAxisStrList = listOf("0", "40", "80", "120", "160")
    private val xAxisStrList = listOf("0", "40", "80", "120", "160")
    private val data = mutableListOf()
    private val paint = Paint()
    private val shaderPaint = Paint()
    private val path = Path()
    private val polyPathList = mutableListOf()
    private val shaderPathList = mutableListOf()
    private val minYList = mutableListOf()
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        calculateRect()
        canvas?.let {
            drawChartText(it)
            drawAxisLine(it)
            drawPolyline(it)
        }
    }

    private fun drawPolyline(canvas: Canvas) {
        getPolyPath()
        if ((polyPathList.size == 0)) {
            return
        }
        paint.reset()
        paint.color = Color.GRAY
        paint.strokeCap = Paint.Cap.ROUND
        paint.strokeJoin = Paint.Join.ROUND
        paint.strokeWidth = polyLineWidth.toFloat()
        paint.style = Paint.Style.STROKE
        for (index in polyPathList.indices) {
            var path = shaderPathList[index]
            path?.let {
                //第1、2参数是渐变起点坐标,2、3参数是渐变终点坐标,4参数是沿着渐变线分布的 sRGB 颜色,5可能为空。颜色数组中每种对应颜色的相对位置 [0..1]。如果为 null,则颜色沿渐变线均匀分布,6着色器平铺模式
                shaderPaint.shader = LinearGradient(
                    0f,
                    0f,
                    0f,
                    500f,
                    intArrayOf(Color.GREEN, Color.TRANSPARENT),
                    null,
                    Shader.TileMode.CLAMP
                )
                shaderPaint.strokeWidth = 1f
                canvas.drawPath(it, shaderPaint)
            }
            path = polyPathList[index]
            path?.let { canvas.drawPath(it, paint) }
        }
    }

    private fun getPolyPath() {
        val chartW = chart.right - chart.left
        val xOffset = chartW.toFloat() / (data.size - 1)
        val chartH = chart.bottom - chart.top
        val yPerH = chartH.toFloat() / maxHeartRate
        var minY = 0f
        val localPath = Path()
        val localShaderPath = Path()
        polyPathList.clear()
        shaderPathList.clear()
        minYList.clear()
        var localX = 0f
        var localY: Float
        data.forEachIndexed { index, pointData ->
            localX = (chart.left + xOffset * index.toFloat())
            localY = chart.bottom - pointData.yValue * yPerH
            minY = min(minY, localY)
            if ((localY == 0f)) {
                if (!localPath.isEmpty) {
                    polyPathList.add(Path(localPath))
                    localShaderPath.lineTo(localX, chart.bottom.toFloat())
                    shaderPathList.add(Path(localShaderPath))
                    minYList.add(minY)
                }
                minY = 0f
                localPath.reset()
                localShaderPath.reset()
            } else {
                if (localPath.isEmpty) {
                    localPath.moveTo(localX, localY)
                } else {
                    localPath.lineTo(localX, localY)
                }
                if (localShaderPath.isEmpty) {
                    localShaderPath.moveTo(localX, chart.bottom.toFloat())
                    localShaderPath.lineTo(localX, localY)
                } else {
                    localShaderPath.lineTo(localX, localY)
                }
            }
        }
        if (polyPathList.isEmpty()) {
            polyPathList.add(Path(localPath))
            localShaderPath.lineTo(localX, chart.bottom.toFloat())
            shaderPathList.add(Path(localShaderPath))
            minYList.add(minY)
        }
    }

    private fun drawAxisLine(canvas: Canvas) {
        paint.reset()
        paint.color = Color.GRAY
        paint.strokeWidth = axisLineWidth.toFloat()
        paint.isAntiAlias = true
        paint.style = Paint.Style.FILL_AND_STROKE
        path.reset()
        path.moveTo(chart.left.toFloat() + axisLineWidth / 2, chart.top.toFloat())
        path.lineTo(chart.left.toFloat() + axisLineWidth / 2, chart.bottom.toFloat())
        canvas.drawPath(path, paint)
        path.reset()
        path.moveTo(chart.left.toFloat(), chart.bottom.toFloat() - axisLineWidth / 2)
        path.lineTo(chart.right.toFloat(), chart.bottom.toFloat() - axisLineWidth / 2)
        canvas.drawPath(path, paint)
    }

    private fun drawChartText(canvas: Canvas) {
        val chartW = chart.right - chart.left
        paint.reset()
        paint.textSize = axisValueTextSize
        paint.color = Color.BLACK
        paint.isAntiAlias = true
        paint.style = Paint.Style.FILL
        paint.textAlign = Paint.Align.RIGHT
        //纵轴显示值
        val chartH = chart.bottom - chart.top
        val yValueTextPerH = chartH / (yAxisStrList.size - 1)
        yAxisStrList.forEachIndexed { index, s ->
            run {
                var y = chart.bottom - yValueTextPerH * index
                if (index == yAxisStrList.size - 1) {
                    y += yValueTextRect.bottom - yValueTextRect.top
                }
                canvas.drawText(
                    s, yValueTextRect.right.toFloat() + paddingStart,
                    y.toFloat(),
                    paint
                )
            }
        }
        paint.textAlign = Paint.Align.LEFT
        //横轴显示值
        val xValueTextPerH = chartW / (xAxisStrList.size - 1)
        xAxisStrList.forEachIndexed { index, s ->
            run {
                var x = chart.left + xValueTextPerH * index
                if (index == yAxisStrList.size - 1) {
                    x += yValueTextRect.right - 60
                }
                canvas.drawText(s, x.toFloat(), mHeight.toFloat() - paddingBottom, paint)
            }
        }
        //纵轴单位
        val h = yValueUnitTextRect.bottom - yValueUnitTextRect.top + paddingTop
        paint.textSize = yAxisValueUnitTextSize
        canvas.drawText("次/分钟", 0f + paddingStart, h.toFloat(), paint)
    }

    fun setData(data: List?) {
        data?.let {
            this.data.clear()
            this.data.addAll(it)
            postInvalidate()
        }
    }

    private fun calculateRect() {
        Log.i("DBFF", "calculateRect: mWidth=$mWidth-----mHeight=$mHeight")
        mWidth = measuredWidth
        mHeight = measuredHeight
        val yValueStr = yAxisStrList[yAxisStrList.lastIndex]
        paint.textSize = axisValueTextSize
        paint.getTextBounds(yValueStr, 0, yValueStr.length, yValueTextRect)
        var l = yValueTextRect.left
        var t = yValueTextRect.top
        var r = yValueTextRect.right
        var b = yValueTextRect.bottom
        var w = r - l
        var h = b - t
        Log.i("DBFF", "calculateRect: w=$w-----h=$h--l=$l----t=$t---r=$r---b=$b")
        chart.left = yValueTextRect.right + axisValueTextDistanceAxis + paddingStart
        val yValueUnitStr = "次/分钟"
        paint.textSize = yAxisValueUnitTextSize
        paint.getTextBounds(yValueUnitStr, 0, yValueUnitStr.length, yValueUnitTextRect)
        l = yValueUnitTextRect.left
        t = yValueUnitTextRect.top
        r = yValueUnitTextRect.right
        b = yValueUnitTextRect.bottom
        w = r - l
        h = b - t
        Log.i("DBFF", "calculateRect: w=$w-----h=$h--l=$l----t=$t---r=$r---b=$b")
        chart.top = h + axisValueTextDistanceAxis + paddingTop
        val xValueUnitStr = "分钟"
        paint.textSize = xAxisValueUnitTextSize
        paint.getTextBounds(xValueUnitStr, 0, xValueUnitStr.length, xValueUnitTextRect)
        l = xValueUnitTextRect.left
        t = xValueUnitTextRect.top
        r = xValueUnitTextRect.right
        b = xValueUnitTextRect.bottom
        w = r - l
        h = b - t
        Log.i("DBFF", "calculateRect: w=$w-----h=$h--l=$l----t=$t---r=$r---b=$b")
        chart.right = mWidth - w - axisValueTextDistanceAxis - paddingEnd
        chart.bottom = mHeight - h - axisValueTextDistanceAxis - paddingBottom
    }

    data class PointData(val yValue: Int, val xOffset: Int)
}

你可能感兴趣的:(渐变心率折线图)