这个是曲线不动,画笔动,有点复杂,想了很久
后一根线覆盖前一根线,循环
View的代码:
/**
* 血氧仪心率View
* @author cym
*/
class PlethView(mContext: Context, mAttrs: AttributeSet?) : View(mContext, mAttrs) {
// 画笔
private val mPaint = Paint()
// 圆角画布Path
private val mCanvasPath = Path()
// 心率画布Path
private val mNewCanvasPath = Path()
private val mOldCanvasPath = Path()
// 两条心率曲线
private val mOldPath = Path()
private val mNewPath = Path()
// 颜色
private val mColorBlack = ContextCompat.getColor(mContext, R.color.bleBloodOxygenColorBlack)
private val mColorLine = ContextCompat.getColor(mContext, R.color.bleBloodOxygenColorShadowLine)
private val mColorBlue = ContextCompat.getColor(mContext, R.color.bleBloodOxygenColorBlue)
// 顶部和底部
private var mTop = 0F
private var mBottom = 0F
// 背景线每格间隔
private val mLineWeight = dp2px(10F)
// 每隔多久刷新一次
private val mRefreshStamp = 100L
// 每个屏幕最多显示多少ms的数据
private val mMaxStamp = 5000L
// 心率数据类
private data class HeartData(
var heart: Float = 0F,// 心率百分比 0F ~ 1F ,我也不知道数据长什么样
var stamp: Long = 0L// 心率的时间戳
)
// 心率数据列表
private var mList = ArrayList<HeartData>()
// 开始测量的时间
private var mStartStamp = 0L
init {
mPaint.isAntiAlias = true
mPaint.typeface = Typeface.DEFAULT_BOLD
mPaint.textSize = dp2px(12F)
mPaint.textAlign = Paint.Align.LEFT
// 循环添加数据
startTest(System.currentTimeMillis())
Thread {
var step = 0// 伪装成心跳。0,1:平;2:高;3:低
while (true) {
val heart: Float = when (step) {
0 -> {
step = 1
0.5F
}
1 -> {
step = 2
0.5F
}
2 -> {
step = 3
(80..100).random() / 100F
}
3 -> {
step = 0
(15..25).random() / 100F
}
else -> {
// 我觉得不应该
step = 0
0F
}
}
addHeart(heart, System.currentTimeMillis())
if (step == 1) {
Thread.sleep(500)
} else {
Thread.sleep(100)
}
}
}.start()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
// 初始化圆角画布Path
val roundRadius = dp2px(12.5F)
mCanvasPath.reset()
mCanvasPath.moveTo(roundRadius, 0F)
mCanvasPath.lineTo(w - roundRadius, 0F)
mCanvasPath.quadTo(w.toFloat(), 0F, w.toFloat(), roundRadius)
mCanvasPath.lineTo(w.toFloat(), h - roundRadius)
mCanvasPath.quadTo(w.toFloat(), h.toFloat(), w - roundRadius, h.toFloat())
mCanvasPath.lineTo(roundRadius, h.toFloat())
mCanvasPath.quadTo(0F, h.toFloat(), 0F, h - roundRadius)
mCanvasPath.lineTo(0F, roundRadius)
mCanvasPath.quadTo(0F, 0F, roundRadius, 0F)
// 设置曲线可以绘制的区域
mTop = h * 0.2F
mBottom = h * 0.8F
}
override fun onDraw(canvas: Canvas) {
// 因为背景是圆角的,所以裁剪画布,使绘制内容不超过圆角
canvas.save()
canvas.clipPath(mCanvasPath)
// 画背景线
mPaint.color = mColorLine
mPaint.strokeWidth = 1F
mPaint.style = Paint.Style.FILL
// 画背景线竖线
val yLineCount = (width / mLineWeight).toInt()
for (i in 0..yLineCount) {
canvas.drawLine(
i * mLineWeight - mLineWeight * 0.2F,
0F,
i * mLineWeight - mLineWeight * 0.2F,
height.toFloat(),
mPaint
)
}
// 画背景线横线
val xLineCount = (height / mLineWeight).toInt()
for (i in 0..xLineCount) {
canvas.drawLine(
0F,
i * mLineWeight - mLineWeight * 0.5F,
width.toFloat(),
i * mLineWeight - mLineWeight * 0.5F,
mPaint
)
}
// 画左上角文本
mPaint.color = mColorBlack
canvas.drawText("Pleth", dp2px(8F), dp2px(14F), mPaint)
canvas.restore()
// 画曲线
mPaint.color = mColorBlue
mPaint.strokeWidth = dp2px(1.5F)
mPaint.strokeCap = Paint.Cap.ROUND
mPaint.style = Paint.Style.STROKE
// 从左向右画,如果画到顶了,之后的覆盖上一条
// 线只画在中间,不会超出最大最小值
if (mStartStamp > 0 && mList.size > 0) {
// 最多显示两个屏幕长的数据
var startIndex = 0
for (i in mList.size - 1 downTo 0) {
if (mList[i].stamp > System.currentTimeMillis() - mMaxStamp - 1000L) {
startIndex = i
} else {
break
}
}
// 当前时间向前推一些,不然画的线会不流畅。因为数据还没来(最好推数据列表时间来的间隔)
val curStamp = System.currentTimeMillis() - 500L
// 当前时间在中间走,左右两边时间也要跟着变
val newLeftStamp = curStamp - (curStamp - mStartStamp) % mMaxStamp
val newRightStamp = newLeftStamp + mMaxStamp
val oldRightStamp = newLeftStamp
val oldLeftStamp = oldRightStamp - mMaxStamp
// 画数据曲线
mNewPath.reset()
mOldPath.reset()
var mIsFirstNew = true
var mIsFirstOld = true
for (i in startIndex until mList.size) {
val data = mList[i]
val y = mBottom - (mBottom - mTop) * data.heart
// 新曲线
val xNew = (data.stamp - newLeftStamp) * 1.0F / (newRightStamp - newLeftStamp) * width
if (mIsFirstNew) {
mNewPath.moveTo(xNew, y)
mIsFirstNew = false
} else {
mNewPath.lineTo(xNew, y)
}
// 旧曲线
if (data.stamp > mMaxStamp) {
val xOld = (data.stamp - oldLeftStamp) * 1.0F / (oldRightStamp - oldLeftStamp) * width
if (mIsFirstOld) {
mOldPath.moveTo(xOld, y)
mIsFirstOld = false
} else {
mOldPath.lineTo(xOld, y)
}
}
}
// 裁剪画布
val padding = dp2px(3F)
val curX = (curStamp - newLeftStamp) * 1.0F / (newRightStamp - newLeftStamp) * (width + padding)
// 新的只能画在当前时间左边
mNewCanvasPath.reset()
mNewCanvasPath.moveTo(curX - padding, 0F)
mNewCanvasPath.lineTo(curX - padding, height.toFloat())
mNewCanvasPath.lineTo(0F, height.toFloat())
mNewCanvasPath.lineTo(0F, 0F)
canvas.save()
canvas.clipPath(mNewCanvasPath)
canvas.drawPath(mNewPath, mPaint)
canvas.restore()
// 旧的只能画在当前时间右边
// 并且第一个时间段不绘制旧的数据。不然一开始绘制,最右边会有一个小点
if (curStamp - mStartStamp > mMaxStamp) {
mOldCanvasPath.reset()
mOldCanvasPath.moveTo(curX + padding, 0F)
mOldCanvasPath.lineTo(curX + padding, height.toFloat())
mOldCanvasPath.lineTo(width.toFloat(), height.toFloat())
mOldCanvasPath.lineTo(width.toFloat(), 0F)
canvas.save()
canvas.clipPath(mOldCanvasPath)
canvas.drawPath(mOldPath, mPaint)
canvas.restore()
}
}
// 不断重绘
invalidate()
}
/**
* 添加一条心率值
*/
fun addHeart(heart: Float, stamp: Long) {
mList.add(HeartData(heart, stamp))
}
/**
* 开始测试
*/
fun startTest(startStamp: Long) {
mStartStamp = startStamp
}
/**
* dp转px
* @param dpValue dp
*/
private fun dp2px(dpValue: Float): Float {
val scale = resources.displayMetrics.density
return dpValue * scale + 0.5f
}
}