美文网首页
渐变心率折线图

渐变心率折线图

作者: 面向星辰大海的程序员 | 来源:发表于2022-03-27 23:54 被阅读0次
    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<PointData>()
        private val paint = Paint()
        private val shaderPaint = Paint()
        private val path = Path()
        private val polyPathList = mutableListOf<Path?>()
        private val shaderPathList = mutableListOf<Path?>()
        private val minYList = mutableListOf<Float>()
        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<PointData>?) {
            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)
    }
    

    相关文章

      网友评论

          本文标题:渐变心率折线图

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