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)
}
网友评论