使用
var bpList = mutableListOf()
av_specific_bp.setBPData(this.bpList)
<com.oplayer.orunningplus.view.HomePageBPVIew
android:id="@+id/av_specific_bp"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="60dp" />
HomePageBPVIew.kt
package com.example.myapplication
import android.app.Service
import android.content.Context
import android.graphics.*
import android.text.TextPaint
import android.util.AttributeSet
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.widget.OverScroller
import androidx.core.content.ContextCompat
import androidx.core.view.GestureDetectorCompat
import androidx.core.view.ViewCompat
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
import java.text.SimpleDateFormat
import java.util.*
class HomePageBPVIew(
private var mContext: Context,
attrs: AttributeSet?,
defStyleAttr: Int
) : View(mContext, attrs, defStyleAttr) {
private var mBound: Rect? = null
private var textStart = 0f
private var mHeight = 0
private var mWidth = 0
private var ecgList: List<BPBean> = ArrayList()
private val verticalWidth = 100f
private var chartWidth=0f //表的总宽度,除过外间距 = 0f
private val outSpace = verticalWidth // 柱子与纵轴的距离
private var startChart = verticalWidth //柱子开始的横坐标
private var interval=0f //柱子之间的间隔 = 0f
private var barWidth=0f //柱子的宽度 = 0f
private val bottomHeight = 100f //底部横坐标高度
private val maxValue = "230" //默认最大值
private val middleValue = "1"
private val paddingTop = 20f
private val noDataPaint: Paint? = null
private var textXpaint: TextPaint? = null
private var linePaint: Paint? = null
private val textColor = UIUtils.getColor(R.color.gray_date_text_color)
private val bgColor = ContextCompat.getColor(mContext,R.color.colorPrimary)
private val mDuriation = 3000
private var textYpaint: TextPaint? = null
// private ChartAnimator mAnimator;
private var mGestureDetector: GestureDetectorCompat? = null
private var mScroller: OverScroller? = null
private var yBackgroundPaint: Paint? = null
private var weight = 0f
private var height = 0f
enum class Direction {
NONE, LEFT, RIGHT, VERTICAL
}
//正常滑动方向
private var mCurrentScrollDirection = Direction.NONE
//快速滑动方向
private var mCurrentFlingDirection = Direction.NONE
private val mHorizontalFlingEnabled = true
private val mCurrentOrigin = PointF(0f, 0f)
private val mScrollDuration = 250
//滑动速度
private val mXScrollingSpeed = 1f
private var mMinimumFlingVelocity = 0
///
// 滑动相关
///
private val mGestureListener: SimpleOnGestureListener = object : SimpleOnGestureListener() {
//手指按下
override fun onDown(e: MotionEvent): Boolean {
goToNearestBar()
return true
}
//有效的滑动
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
when (mCurrentScrollDirection) {
Direction.NONE -> // 只允许在一个方向上滑动
mCurrentScrollDirection = if (Math.abs(distanceX) > Math.abs(distanceY)) {
if (distanceX > 0) {
Direction.LEFT
} else {
Direction.RIGHT
}
} else {
Direction.VERTICAL
}
Direction.LEFT -> // Change direction if there was enough change.
if (Math.abs(distanceX) > Math.abs(distanceY) && distanceX < 0) {
mCurrentScrollDirection =
Direction.RIGHT
}
Direction.RIGHT -> // Change direction if there was enough change.
if (Math.abs(distanceX) > Math.abs(distanceY) && distanceX > 0) {
mCurrentScrollDirection =
Direction.LEFT
}
}
when (mCurrentScrollDirection) {
Direction.LEFT, Direction.RIGHT -> {
mCurrentOrigin.x -= distanceX * mXScrollingSpeed
ViewCompat.postInvalidateOnAnimation(this@HomePageBPVIew)
}
}
return true
}
//快速滑动
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
if (mCurrentFlingDirection == Direction.LEFT && !mHorizontalFlingEnabled ||
mCurrentFlingDirection == Direction.RIGHT && !mHorizontalFlingEnabled
) {
return true
}
mCurrentFlingDirection = mCurrentScrollDirection
mScroller!!.forceFinished(true)
when (mCurrentFlingDirection) {
Direction.LEFT, Direction.RIGHT -> mScroller!!.fling(
mCurrentOrigin.x.toInt(),
mCurrentOrigin.y.toInt(),
(velocityX * mXScrollingSpeed).toInt(),
0,
Int.MIN_VALUE,
Int.MAX_VALUE,
0,
0
)
Direction.VERTICAL -> {
}
}
ViewCompat.postInvalidateOnAnimation(this@HomePageBPVIew)
return true
}
//单击事件
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
return super.onSingleTapConfirmed(e)
}
//长按
override fun onLongPress(e: MotionEvent) {
super.onLongPress(e)
}
}
override fun computeScroll() {
super.computeScroll()
if (mScroller!!.isFinished) { //当前滚动是否结束
if (mCurrentFlingDirection != Direction.NONE) {
goToNearestBar()
}
} else {
if (mCurrentFlingDirection != Direction.NONE && forceFinishScroll()) { //惯性滑动时保证最左边条目展示正确
goToNearestBar()
} else if (mScroller!!.computeScrollOffset()) { //滑动是否结束 记录最新的滑动的点 惯性滑动处理
mCurrentOrigin.y = mScroller!!.currY.toFloat()
mCurrentOrigin.x = mScroller!!.currX.toFloat()
ViewCompat.postInvalidateOnAnimation(this)
}
}
}
/**
* Check if scrolling should be stopped.
*
* @return true if scrolling should be stopped before reaching the end of animation.
*/
private fun forceFinishScroll(): Boolean {
return mScroller!!.currVelocity <= mMinimumFlingVelocity
}
override fun onTouchEvent(event: MotionEvent): Boolean {
//将view的OnTouchEvent事件交给手势监听器处理
val `val` = mGestureDetector!!.onTouchEvent(event)
// 正常滑动结束后 处理最左边的条目
if (event.action == MotionEvent.ACTION_UP && mCurrentFlingDirection == Direction.NONE) {
if (mCurrentScrollDirection == Direction.RIGHT || mCurrentScrollDirection == Direction.LEFT) {
goToNearestBar()
}
mCurrentScrollDirection = Direction.NONE
}
return `val`
}
private fun goToNearestBar() {
//让最左边的条目 显示出来
var leftBar = mCurrentOrigin.x / (barWidth + interval).toDouble()
leftBar = if (mCurrentFlingDirection != Direction.NONE) {
// 跳到最近一个bar
Math.round(leftBar).toDouble()
} else if (mCurrentScrollDirection == Direction.LEFT) {
// 跳到上一个bar
Math.floor(leftBar)
} else if (mCurrentScrollDirection == Direction.RIGHT) {
// 跳到下一个bar
Math.ceil(leftBar)
} else {
// 跳到最近一个bar
Math.round(leftBar).toDouble()
}
val nearestOrigin = (mCurrentOrigin.x - leftBar * (barWidth + interval)).toInt()
if (nearestOrigin != 0) {
// 停止当前动画
mScroller!!.forceFinished(true)
//开始滚动
mScroller!!.startScroll(
mCurrentOrigin.x.toInt(),
mCurrentOrigin.y.toInt(),
-nearestOrigin,
0,
(Math.abs(nearestOrigin) / (barWidth + interval) * mScrollDuration).toInt()
)
ViewCompat.postInvalidateOnAnimation(this@HomePageBPVIew)
}
//重新设置滚动方向.
mCurrentFlingDirection = Direction.NONE
mCurrentScrollDirection = mCurrentFlingDirection
}
constructor(context: Context) : this(context, null) {
mContext = context
init()
}
constructor(
context: Context,
attrs: AttributeSet?
) : this(context, attrs, 0) {
mContext = context
init()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//宽度的模式
val mWidthModle = MeasureSpec.getMode(widthMeasureSpec)
//宽度大小
val mWidthSize = MeasureSpec.getSize(widthMeasureSpec)
val mHeightModle = MeasureSpec.getMode(heightMeasureSpec)
val mHeightSize = MeasureSpec.getSize(heightMeasureSpec)
//如果明确大小,直接设置大小
if (mWidthModle == MeasureSpec.EXACTLY) {
mWidth = mWidthSize
} else {
//计算宽度,可以根据实际情况进行计算
mWidth = paddingLeft + paddingRight
//如果为AT_MOST, 不允许超过默认宽度的大小
if (mWidthModle == MeasureSpec.AT_MOST) {
mWidth = Math.min(mWidth, mWidthSize)
}
}
if (mHeightModle == MeasureSpec.EXACTLY) {
mHeight = mHeightSize
} else {
mHeight = getPaddingTop() + paddingBottom
if (mHeightModle == MeasureSpec.AT_MOST) {
mHeight = Math.min(mHeight, mHeightSize)
}
}
//设置测量完成的宽高
setMeasuredDimension(mWidth, mHeight)
}
override fun onLayout(
changed: Boolean,
left: Int,
top: Int,
right: Int,
bottom: Int
) {
super.onLayout(changed, left, top, right, bottom)
mWidth = width
mHeight = (getHeight() - paddingTop).toInt()
chartWidth = mWidth - outSpace
barWidth = 100f
interval = 30f
startChart = outSpace
//横坐标
textStart = startChart + barWidth / 2f
}
private fun init() {
//初始化手势
mGestureDetector = GestureDetectorCompat(mContext, mGestureListener)
// 解决长按屏幕后无法拖动的现象 但是 长按 用不了
mGestureDetector!!.setIsLongpressEnabled(false)
mScroller = OverScroller(mContext, FastOutLinearInInterpolator())
mMinimumFlingVelocity = ViewConfiguration.get(mContext).scaledMinimumFlingVelocity
mBound = Rect()
//线画笔
linePaint = Paint()
linePaint!!.isAntiAlias = true
linePaint!!.color = UIUtils.getColor(R.color.bp_chart_line_bg)
//x纵坐标 画笔
textXpaint = TextPaint()
textXpaint!!.isAntiAlias = true
textXpaint!!.textSize = 27f
textXpaint!!.textAlign = Paint.Align.CENTER
//Y纵坐标 画笔
textYpaint = TextPaint()
textYpaint!!.isAntiAlias = true
textYpaint!!.textSize = 28f
textYpaint!!.textAlign = Paint.Align.LEFT
if (themeColor?.grayTextColor != null) {
textYpaint!!.color = Color.parseColor(themeColor?.grayTextColor)
}else {
textYpaint!!.color = textColor
}
//Y轴背景
yBackgroundPaint = Paint()
yBackgroundPaint!!.color = UIUtils.getColor(R.color.bp_chart_line)
// //无数据时的画笔
// noDataPaint = new Paint();
// noDataPaint.setAntiAlias(true);
// noDataPaint.setColor(noDataColor);
// noDataPaint.setStyle(Paint.Style.FILL);
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// canvas.drawColor(getColor(R.color.bt_grey_color))
canvas.drawColor(bgColor)
val lineInterval = (mHeight - bottomHeight) / 6f //横线之间的间距 纵向
val textHeight = mHeight + paddingTop - bottomHeight //横坐标高度
if (mCurrentOrigin.x < width - (ecgList.size * barWidth + (ecgList.size - 1) * interval + outSpace)) mCurrentOrigin.x =
width - (ecgList.size * barWidth + (ecgList.size - 1) * interval + outSpace)
if (mCurrentOrigin.x > 0) mCurrentOrigin.x = 0f
drawYtext(canvas, lineInterval, textHeight)
val textTempStart = textStart
drawXtext(canvas, textTempStart)
val chartTempStart = startChart
var size = (mHeight - bottomHeight) / 150f //比例
drawBar(canvas, chartTempStart, size)
}
private fun drawBar(
canvas: Canvas,
chartTempStart: Float,
size: Float
) {
var chartTempStart = chartTempStart
canvas.clipRect(outSpace - 10f, 0f, mWidth.toFloat(), getHeight().toFloat())
// canvas.clipRect(outSpace, getHeight() - bottomHeight, mWidth, getHeight());
val mPaint = Paint()
mPaint.color = UIUtils.getColor(R.color.bp_chart_point)
mPaint.style = Paint.Style.FILL
mPaint.strokeWidth = 30f
val LinePaint = Paint()
LinePaint.color = UIUtils.getColor(R.color.bp_chart_line)
LinePaint.style = Paint.Style.FILL
LinePaint.strokeWidth = 13f
for (i in ecgList.indices) {
//每个数据点所占的Y轴高度
val barHeight =
ecgList[i].dbp / java.lang.Float.valueOf(maxValue) * 200f * size
// float realBarHeight = barHeight * mAnimator.getPhaseY();
val left = chartTempStart + mCurrentOrigin.x
val top = mHeight - bottomHeight + paddingTop
val right = chartTempStart + barWidth + mCurrentOrigin.x
val bottom = mHeight + paddingTop - bottomHeight
val topLow = mHeight - ecgList[i].dbp * size
val topHigeht = mHeight - ecgList[i].sbp * size
val startX = (left + right) / 2
canvas.drawLine(startX, topHigeht - 8f, startX, topLow + 8f, LinePaint)
canvas.drawCircle(startX, topLow + 8f, 15f, mPaint) //坐标修改偏移量等于半径的议案
canvas.drawCircle(startX, topHigeht - 8f, 15f, mPaint)
chartTempStart += barWidth + interval
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
weight = 0.85f * w
height = 0.3f * h
}
private val timeFormat = SimpleDateFormat("HH:mm:ss")
/**
* 格式化时间 时间格式HH:mm:ss
*
* @return
*/
fun formatTime(date: Date?): String {
return timeFormat.format(date)
}
/**
* 画x轴
*
* @param canvas
* @param textTempStart
*/
private fun drawXtext(
canvas: Canvas,
textTempStart: Float
) {
var textTempStart = textTempStart
canvas.clipRect(outSpace - 10f, 0f, mWidth.toFloat(), getHeight().toFloat())
for (i in ecgList.indices) {
canvas.drawText(
formatTime(
ecgList[i].date
),
textTempStart + mCurrentOrigin.x,
//文字top值10f
mHeight + paddingTop - 10f + mBound!!.height() / 2f,
textXpaint!!
)
textTempStart += barWidth + interval
}
}
private val y_title =
arrayOf("", "", "", "", "")
/**
* 画Y轴
*
* @param canvas
* @param lineInterval
* @param textHeight
*/
private fun drawYtext(
canvas: Canvas,
lineInterval: Float,
textHeight: Float
) {
val min_height = textHeight / 4
// canvas.drawLine(
// 42f,
// 52 + textHeight ,
// mWidth.toFloat()-42,
// 52 + textHeight ,
// textYpaint!!
// )
canvas.drawText(y_title[0], 0f, 32 + textHeight , textYpaint!!)
// 虚线
var mPaint :Paint = Paint(Paint.ANTI_ALIAS_FLAG)
mPaint.color = resources.getColor(R.color.gray_date_text_color)
mPaint.strokeWidth = 0.1f
mPaint.pathEffect = DashPathEffect(floatArrayOf(3f, 3f), 0f)
val centerY = height / 20
val centerY2 = height
// setLayerType(LAYER_TYPE_SOFTWARE, null)
canvas.drawLine(
42f,
52 + textHeight ,
mWidth.toFloat()-42,
52 + textHeight ,
mPaint
)
// canvas.drawLine(0f+60f, centerY.toFloat()-13f, width.toFloat()-55f, centerY.toFloat()-13f, mPaint)
canvas.drawLine(0f+60f, centerY2.toFloat()-37f, width.toFloat()-55f, centerY2.toFloat()-37f, mPaint)
//限制线
// canvas.drawLine(
// outSpace - 10f,
// 32 + min_height*2 ,
// mWidth.toFloat(),
// 32 + min_height*2 ,
// textYpaint!!
// )
// canvas.drawText(y_title[0], 0f, 32 + min_height*2 , textYpaint!!)
// for (i in 1 downTo 0) {
// canvas.drawLine(
// outSpace - 10f,
// 32 + textHeight * i,
// mWidth.toFloat(),
// 32 + textHeight * i,
// textYpaint!!
// )
// canvas.drawText(y_title[0], 0f, 32 + min_height * i, textYpaint!!)
// }
}
/**
* 重新指定起始位置
*
* @param verticalList
*/
private fun measureWidthShort(verticalList: List<Float>) {
startChart = outSpace
textStart = startChart + barWidth / 2f
}
fun setBPData(verticalList: List<BPBean>) {
//过滤最大值和最小值
ecgList = verticalList
invalidate()
}
companion object {
private const val TAG = "ScrollBar"
}
init {
init()
}
}
BPBean.kt
package com.example.myapplication
import io.realm.RealmObject
import io.realm.annotations.RealmClass
import java.util.*
open class BPBean(
open var macAddress: String = "",
open var sbp: Int=0,//高压
open var dbp: Int=0,//低压
open var isHistory: Boolean=false,
open var date: Date? = null,
open var year : Int=0,
open var month: Int=0,
open var week: Int=0,
open var day: Int=0)
{
override fun toString(): String {
return "BPBean(macAddress='$macAddress', sbp=$sbp, dbp=$dbp, isHistory=$isHistory, date=$date, year=$year, month=$month, week=$week, day=$day)"
}
}