rendering
code
/**
* 混合图表
* zrj 2020/9/7
*/
class MixingChart(context: Context, attrs: AttributeSet?) : View(context, attrs) {
//屏幕宽高
private var scrWidth = 0f
private var scrHeight = 0f
private lateinit var paintPolyline: Paint //心率折线
private lateinit var paintPolyShadow: Paint //心率折线阴影
private var mLinePath: Path //折线路径
private var mRectF: RectF
private var mStartAngle = 270F
private var mSweepAngle = 360F
private lateinit var mBgCirPaint: Paint
private var mBgCirWidth = 28f//宽度
private var mPercent = 0f
@Type.Project
private var type = 0 //对应的项目
init {
setLayerType(LAYER_TYPE_SOFTWARE, null)
mRectF = RectF()
mLinePath = Path()
initPaint()
}
/**
* 初始化画笔
*/
private fun initPaint() {
paintPolyShadow = Paint()
paintPolyShadow.style = Paint.Style.FILL
paintPolyline = Paint()
paintPolyline.style = Paint.Style.FILL
paintPolyline.strokeWidth = 2f
paintPolyline.textSize = 12f.sp
paintPolyline.isAntiAlias = true
paintPolyline.color = context.colorCompat(R.color.fc355c_fc3159)
mBgCirPaint = Paint()
mBgCirPaint.isAntiAlias = true
mBgCirPaint.style = Paint.Style.STROKE
mBgCirPaint.strokeWidth = mBgCirWidth
mBgCirPaint.strokeCap = Paint.Cap.ROUND
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
scrWidth = width.toFloat()
scrHeight = height.toFloat()
ySpacing = scrHeight / 8f //y轴分8份
val minWidth =
(w - paddingLeft - paddingRight - 2 * mBgCirWidth).coerceAtMost(h - paddingBottom - paddingTop - 2 * mBgCirWidth)
val radius = minWidth / 2
mRectF.left = w / 2 - radius - mBgCirWidth / 2
mRectF.top = h / 2 - radius - mBgCirWidth / 2
mRectF.right = w / 2 + radius + mBgCirWidth / 2
mRectF.bottom = h / 2 + radius + mBgCirWidth / 2
}
private var ySpacing = 0f //高度分割份数后间距
private var xSpacing = 0f //x轴柱子分割份数后间距
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
when (type) {
Type.EXERCISE_RECORDS -> drawPoints(canvas)
Type.HEART_RATE -> drawHeart(canvas)
Type.SLEEP -> drawSleep2(canvas)
Type.WEIGHT -> {
}
Type.SPO2 -> drawSpo2(canvas)
Type.STRESS -> {
}
Type.BLOOD_PRESSURE -> drawBloodPressure(canvas)
Type.BLOOD_SUGAR -> {
}
Type.OTHER -> drawOther(canvas)
else -> {
}
}
}
private fun drawSpo2(canvas: Canvas) {
val paint = Paint()
paint.style = Paint.Style.FILL
paint.isAntiAlias = true
paint.color = context.colorCompat(R.color.secondary_666666_808080)
val path = Path()
path.moveTo(scrWidth * (spo2 / 100f) - 10f, scrHeight / 2f - 10f)
path.lineTo(scrWidth * (spo2 / 100f) - 20f, scrHeight / 2f - 30f)
path.lineTo(scrWidth * (spo2 / 100f), scrHeight / 2f - 30f)
path.close()
canvas.drawPath(path, paint)
var outerR = floatArrayOf(5f, 5f, 0f, 0f, 0f, 0f, 5f, 5f)
var mDrawables = ShapeDrawable(RoundRectShape(outerR, null, null))
mDrawables.paint.color = Color.parseColor("#fc844d")
mDrawables.setBounds(
0, scrHeight.toInt() / 2 - 5,
(scrWidth * 0.7).toInt() - 1,
scrHeight.toInt() / 2 + 6
)
mDrawables.draw(canvas)
outerR = floatArrayOf(0f, 0f, 5f, 5f, 5f, 5f, 0f, 0f)
mDrawables = ShapeDrawable(RoundRectShape(outerR, null, null))
mDrawables.paint.color = Color.parseColor("#54c054")
mDrawables.setBounds(
(scrWidth * 0.7).toInt() + 1, scrHeight.toInt() / 2 - 5, scrWidth.toInt(),
scrHeight.toInt() / 2 + 6
)
mDrawables.draw(canvas)
}
private fun drawHeart(canvas: Canvas) {
var x0: Float
var x1: Float
var y0: Float
var y1: Float
xSpacing = scrWidth / (heartData.size - 1)
//画折线阴影
heartData.forEachIndexed { index, i ->
if (index < heartData.size * mPercent) {
if (index < heartData.size - 1) {
x0 = xSpacing * index
y0 = ySpacing * 5f - ySpacing * ((i - 40) / 45f)
x1 = xSpacing * (index + 1)
y1 = ySpacing * 5f - ySpacing * ((heartData[index + 1] - 40) / 45f)
if (i > 0) {
if (heartData[index + 1] > 0) {
drawHeartShadow(x0, y0, x1, y1, canvas)
} else {
//单点圆
if (index == 0 || heartData[index - 1] < 0) {
drawHeartShadow(x0 - 4f, y0, x0 + 4f, y0, canvas)
}
}
}
}
}
}
paintPolyline.color = context.colorCompat(R.color.fc355c_fc3159)
//画折线
heartData.forEachIndexed { index, i ->
if (index < heartData.size * mPercent) {
if (index < heartData.size - 1) {
x0 = xSpacing * index
y0 = ySpacing * 5f - ySpacing * ((i - 40) / 45f)
x1 = xSpacing * (index + 1)
y1 = ySpacing * 5f - ySpacing * ((heartData[index + 1] - 40) / 45f)
if (i > 0) {
if (heartData[index + 1] > 0) {
canvas.drawLine(x0, y0, x1, y1, paintPolyline)
} else {
//单点圆
if (index == 0 || heartData[index - 1] < 0) {
canvas.drawCircle(x0, y0, 2f, paintPolyline)
}
}
}
}
}
}
}
private fun drawPoints(canvas: Canvas) {
val latitudeMax = points.map { it[0] }.maxOrNull() ?: 0.0
val latitudeMin = points.map { it[0] }.minOrNull() ?: 0.0
val longitudeMax = points.map { it[1] }.maxOrNull() ?: 0.0
val longitudeMin = points.map { it[1] }.minOrNull() ?: 0.0
val temp0 = latitudeMax - latitudeMin
val temp1 = longitudeMax - longitudeMin
val pointX = points.map { scrWidth * (1 - (longitudeMax - it[1]) / temp1) }
val pointY = points.map { scrHeight * (latitudeMax - it[0]) / temp0 }
val path = Path()
paintPolyline.color = Color.parseColor("#54c054")
paintPolyline.style = Paint.Style.STROKE
//画折线
var offsetX: Float
var offsetY: Float
points.forEachIndexed { index, _ ->
if (index < points.size * mPercent) {
if (index < points.size - 1) {
path.moveTo(pointX[index].toFloat(), pointY[index].toFloat())
path.lineTo(pointX[index + 1].toFloat(), pointY[index + 1].toFloat())
}
if (index == 0 || index == points.size - 1) {
//单点圆
offsetX = if (abs(pointX[index].toFloat() - scrWidth) < 10) -5f else 5f
offsetY = if (abs(pointY[index].toFloat() - scrHeight) < 10) -5f else 5f
canvas.drawCircle(
pointX[index].toFloat() + offsetX,
pointY[index].toFloat() + offsetY,
4f,
paintPolyline
)
}
}
}
canvas.drawPath(path, paintPolyline)
}
private fun drawBloodPressure(canvas: Canvas) {
val mLinearGradient = LinearGradient(
0f, scrHeight / 2f - 5f, scrWidth, scrHeight / 2f + 5f,
intArrayOf(
Color.parseColor("#42cfeb"),
Color.parseColor("#f1df57"),
Color.parseColor("#ff634d")
), null, Shader.TileMode.MIRROR
)
val paintGradientLine = Paint()
paintGradientLine.style = Paint.Style.FILL
paintGradientLine.shader = mLinearGradient
canvas.drawRoundRect(
RectF(0f, scrHeight / 2f - 5f, scrWidth, scrHeight / 2f + 5f), 5f, 5f, paintGradientLine
)
val paint = Paint()
paint.style = Paint.Style.FILL
paint.isAntiAlias = true
paint.color = context.colorCompat(R.color.secondary_666666_808080)
val path = Path()
path.moveTo(scrWidth * (sys / 270f), scrHeight / 2f - 10f)
path.lineTo(scrWidth * (sys / 270f) - 10f, scrHeight / 2f - 30f)
path.lineTo(scrWidth * (sys / 270f) + 10f, scrHeight / 2f - 30f)
path.close()
canvas.drawPath(path, paint)
}
private fun drawOther(canvas: Canvas) {
val mLinearGradient = LinearGradient(
0f, scrHeight / 2f - 5f, scrWidth, scrHeight / 2f + 5f,
intArrayOf(
Color.parseColor("#04d657"),
Color.parseColor("#ebbd1f"),
Color.parseColor("#e8361b")
), null, Shader.TileMode.MIRROR
)
val paintGradientLine = Paint()
paintGradientLine.style = Paint.Style.FILL
paintGradientLine.shader = mLinearGradient
canvas.drawRoundRect(
RectF(0f, scrHeight / 2f - 5f, scrWidth, scrHeight / 2f + 5f), 5f, 5f, paintGradientLine
)
paintPolyline.color = Color.parseColor("#04d657")
paintPolyline.textAlign = Paint.Align.LEFT
canvas.drawText(
"${context.getString(R.string.slowest)} ${min / 60}'${min % 60}''",
0f,
scrHeight / 2f - 20f,
paintPolyline
)
paintPolyline.color = Color.parseColor("#e8361b")
paintPolyline.textAlign = Paint.Align.RIGHT
canvas.drawText(
"${context.getString(R.string.fastest)} ${max / 60}'${max % 60}''",
scrWidth,
scrHeight / 2f - 20f,
paintPolyline
)
}
private fun drawHeartShadow(x0: Float, y0: Float, x1: Float, y1: Float, canvas: Canvas) {
mLinePath.reset()
mLinePath.moveTo(x0, ySpacing * 7f)
mLinePath.lineTo(x0, y0)
mLinePath.lineTo(x1, y1)
mLinePath.lineTo(x1, ySpacing * 7f)
mLinePath.lineTo(x0, ySpacing * 7f)
mLinePath.close()
val mLinearGradient = LinearGradient(
0f,
ySpacing * 5f - ySpacing * ((heartData.maxOrNull()?.minus(40))?.div(45f) ?: 0f),
0f,
ySpacing * 7f,
intArrayOf(
context.colorCompat(R.color.fecbd5_400c17),
context.colorCompat(R.color.color_secondary)
),
null,
Shader.TileMode.MIRROR
)
paintPolyShadow.shader = mLinearGradient
canvas.drawPath(mLinePath, paintPolyShadow)
}
private fun drawSleep2(canvas: Canvas) {
//深睡
mBgCirPaint.color = Color.WHITE
mBgCirPaint.strokeWidth = mBgCirWidth
canvas.drawArc(mRectF, mStartAngle, mSweepAngle, false, mBgCirPaint)
mBgCirPaint.color = Color.parseColor("#8a2be2")
mBgCirPaint.strokeWidth = mBgCirWidth - 8f
canvas.drawArc(mRectF, mStartAngle, mSweepAngle * mPercent, false, mBgCirPaint)
//浅睡
mBgCirPaint.color = Color.WHITE
mBgCirPaint.strokeWidth = mBgCirWidth
canvas.drawArc(
mRectF,
mStartAngle,
mSweepAngle * (1f - sleep.deep / sleep.total.toFloat()) * mPercent,
false,
mBgCirPaint
)
mBgCirPaint.color = Color.parseColor("#c64be4")
mBgCirPaint.strokeWidth = mBgCirWidth - 8f
canvas.drawArc(
mRectF,
mStartAngle,
mSweepAngle * (1f - sleep.deep / sleep.total.toFloat()) * mPercent,
false,
mBgCirPaint
)
//rem
mBgCirPaint.color = Color.WHITE
mBgCirPaint.strokeWidth = mBgCirWidth
canvas.drawArc(
mRectF,
mStartAngle,
mSweepAngle * (1f - (sleep.deep + sleep.light) / sleep.total.toFloat()) * mPercent,
false,
mBgCirPaint
)
mBgCirPaint.color = Color.parseColor("#fd817c")
mBgCirPaint.strokeWidth = mBgCirWidth - 8f
canvas.drawArc(
mRectF,
mStartAngle,
mSweepAngle * (1f - (sleep.deep + sleep.light) / sleep.total.toFloat()) * mPercent,
false,
mBgCirPaint
)
//清醒
mBgCirPaint.color = Color.WHITE
mBgCirPaint.strokeWidth = mBgCirWidth
canvas.drawArc(
mRectF,
mStartAngle,
mSweepAngle * (1f - (sleep.deep + sleep.light + sleep.rem) / sleep.total.toFloat()) * mPercent,
false,
mBgCirPaint
)
mBgCirPaint.color = Color.parseColor("#fdc221")
mBgCirPaint.strokeWidth = mBgCirWidth - 8f
canvas.drawArc(
mRectF,
mStartAngle,
mSweepAngle * (1f - (sleep.deep + sleep.light + sleep.rem) / sleep.total.toFloat()) * mPercent,
false,
mBgCirPaint
)
}
private var sys = 0
fun setBloodPressure(sys: Int) {
type = Type.BLOOD_PRESSURE
this.sys = sys
postInvalidate()
}
private lateinit var sleep: Sleep
fun setSleep(sleep: Sleep) {
type = Type.SLEEP
this.sleep = sleep
startAnim()
}
private fun startAnim() {
val mAnimator = ValueAnimator.ofFloat(0f, 1f)
mAnimator.duration = 1000L
mAnimator.addUpdateListener {
mPercent = it.animatedValue as Float
postInvalidate()
}
mAnimator.start()
}
private var spo2 = 0
fun setSpo2(spo2: Int) {
type = Type.SPO2
this.spo2 = spo2
postInvalidate()
}
private var heartData = mutableListOf<Int>()
fun setHeart(value: List<Int>) {
type = Type.HEART_RATE
heartData.clear()
heartData.addAll(value)
startAnim()
}
private var points = mutableListOf<List<Double>>()
fun setExercise(value: List<List<Double>>) {
type = Type.EXERCISE_RECORDS
points.clear()
points.addAll(value)
startAnim()
}
private var max = 0
private var min = 0
fun setOther(min: Int, max: Int) {
type = Type.OTHER
this.min = min
this.max = max
postInvalidate()
}
}
Copy the code