Android运动健康睡眠自定义控件的实现
目录
- 效果图
- 代码
效果图
文章图片
代码
/** * * 日图表 * zrj 2020/8/25 */class SleepDayChart(context: Context, attrs: AttributeSet?) : View(context, attrs) {//屏幕宽高private var scrWidth = 0fprivate var scrHeight = 0fprivate var xData: Array = arrayOf("20:00", "02:00", "08:00", "14:00", "20:00")private var sleepsData: Sleep? = nullprivate lateinit var paintLine: Paintprivate lateinit var paintGradientLine: Paintprivate lateinit var paintXText: Paintprivate lateinit var paintSleep: Paintprivate lateinit var paintPillar: Paintprivate lateinit var paintRound: Paintprivate lateinit var paintBessel: Paintprivate var xSlider = 0f //滑块的x轴位置private var mPath: Pathprivate val curveCircleRadius = 12f.dp// the coordinates of the first curveprivate val mFirstCurveStartPoint = Point()private val mFirstCurveEndPoint = Point()private val mFirstCurveControlPoint1 = Point()private val mFirstCurveControlPoint2 = Point()//the coordinates of the second curveprivate var mSecondCurveStartPoint = Point()private val mSecondCurveEndPoint = Point()private val mSecondCurveControlPoint1 = Point()private val mSecondCurveControlPoint2 = Point()init {setLayerType(LAYER_TYPE_SOFTWARE, null)mPath = Path()initPaint()}/*** 初始化画笔*/private fun initPaint() {paintLine = Paint()paintLine.style = Paint.Style.STROKEpaintLine.strokeWidth = 1fpaintLine.color = context.colorCompat(R.color.e6e6e6_2e2e2e)paintGradientLine = Paint()paintGradientLine.style = Paint.Style.STROKEpaintGradientLine.strokeWidth = 1fpaintXText = Paint()paintXText.isAntiAlias = truepaintXText.strokeWidth = 1fpaintXText.textSize = 12f.sppaintXText.textAlign = Paint.Align.CENTERpaintXText.color = context.colorCompat(R.color.color_on_surface)paintSleep = Paint()paintSleep.style = Paint.Style.FILLpaintSleep.isAntiAlias = truepaintSleep.color = context.colorCompat(R.color.blue_7fbeff)paintPillar = Paint()paintPillar.style = Paint.Style.FILLpaintPillar.isAntiAlias = truepaintPillar.color = context.colorCompat(R.color.blue_7fbeff)paintRound = Paint()paintRound.style = Paint.Style.FILLpaintRound.isAntiAlias = truepaintRound.color = context.colorCompat(R.color.ffffff_6e6e6e)paintBessel = Paint()paintBessel.style = Paint.Style.FILLpaintBessel.isAntiAlias = truepaintBessel.color = context.colorCompat(R.color.f2f2f2_1d1d1d)}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份//底部圆滑块可以滑动的范围xWithStart = margin * 3xWithEnd = scrWidth - margin * 3xSlider = scrWidth / 2xSpacing = (xWithEnd - xWithStart) / (xData.size - 1)}override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {parent.requestDisallowInterceptTouchEvent(true)return super.dispatchTouchEvent(ev)}private var mDownX = 0fprivate var mDownY = 0fprivate var isSlider = false@SuppressLint("ClickableViewAccessibility")override fun onTouchEvent(event: MotionEvent): Boolean {when (event.action) {MotionEvent.ACTION_DOWN -> {mDownX = event.xmDownY = event.yisSlider = abs(event.x - xSlider) < 60f && abs(event.y - ySpacing * 7) < 60freturn isSlider}MotionEvent.ACTION_MOVE ->if (abs(event.y - mDownY) < abs(event.x - mDownX)) {if (isSlider) {xSlider = event.xif (xSlider < xWithStart) {xSlider = xWithStart}if (xSlider > xWithEnd) {xSlider = xWithEnd}invalidate()}}MotionEvent.ACTION_UP -> {if (!isSlider) {if (abs(event.x - mDownX) < curveCircleRadius) {xSlider = event.xinvalidate()}}}}return true}private val margin = 20f.dp //左右两边距离private var xWithStart = 0f //x轴的起始点private var xWithEnd = 0f //x轴结束点private var ySpacing = 0f //高度分割份数后间距private var xSpacing = 0f //x轴分割份数后间距@SuppressLint("DrawAllocation")override fun onDraw(canvas: Canvas) {super.onDraw(canvas)//画柱子drawPillar(canvas)//垂直渐变线drawGradientLine(canvas)//底部drawBessel(canvas)//画x轴方向文字drawX(canvas)}private fun drawX(canvas: Canvas) {if (sleepsData =https://www.it610.com/article/= null) {xData.forEachIndexed { index, s ->val x = xWithStart + xSpacing * indexval dis = abs(x - xSlider)var y = ySpacing * 7 - 10f.dpif (dis < xSpacing / 2) {paintXText.typeface = Typeface.DEFAULT_BOLDy -= 10f.dp * (1 - dis / xSpacing)} else {paintXText.typeface = Typeface.DEFAULT}canvas.drawText(s, x, y, paintXText)if (index == 0) {canvas.drawText(startDay, x, y - 12f.dp, paintXText)}if (index == xData.size - 1) {canvas.drawText(endDay, x, y - 12f.dp, paintXText)}}} else {sleepsData?.let {val start = DateTime(it.items[0].timeStamp * 1000)val asleep = start.hourOfDay * 60 + start.minuteOfHourval end = DateTime(it.items.last().timeStamp * 1000)val wakeUp = end.hourOfDay * 60 + end.minuteOfHour + it.items.last().durationval s1 ="${context.getString(R.string.bed_time)} ${asleep / 60}:${if (asleep % 60 < 10) "0" else ""}${asleep % 60}"val dis1 = abs(xWithStart + paintXText.measureText(s1) / 2 - xSlider)var y1 = ySpacing * 7 - 10fif (dis1 < curveCircleRadius * 3) {paintXText.typeface = Typeface.DEFAULT_BOLDvar temp = 1 - dis1 / curveCircleRadius * 2if (temp < 0f || temp > 1f) {temp = 1f}y1 -= 60f * temp} else {paintXText.typeface = Typeface.DEFAULT}canvas.drawText(s1, xWithStart, y1, paintXText)canvas.drawText(startDay, xWithStart, y1 - 40f, paintXText)val hour = "${if (wakeUp / 60 < 10) "0" else ""}${wakeUp / 60}"val minute = "${if (wakeUp % 60 < 10) "0" else ""}${wakeUp % 60}"val s2 ="${context.getString(R.string.rise_time)} $hour:$minute"val dis2 = abs(xWithEnd - paintXText.measureText(s2) / 2 - xSlider)var y2 = ySpacing * 7 - 10fif (dis2 < curveCircleRadius * 3) {paintXText.typeface = Typeface.DEFAULT_BOLDy2 -= 60f * (1 - dis2 / (xSlider - curveCircleRadius * 3))} else {paintXText.typeface = Typeface.DEFAULT}canvas.drawText(s2, xWithEnd, y2, paintXText)canvas.drawText(endDay, xWithEnd, y2 - 40f, paintXText)}}}private fun drawPillar(canvas: Canvas) {var top = 0fvar bottom = 0fvar preDuration = 0 //前一状态时长var duration = 0 //时间累加var tempTop = 0fvar tempBottom: Floatvar startColor = 0var endColor = 0val colors = intArrayOf(startColor, endColor)sleepsData?.let {it.items.forEachIndexed { index, item ->when (item.status) {3, 4 -> { //清醒endColor = Color.parseColor("#fdc221")paintSleep.color = Color.parseColor("#fdc221")paintPillar.color = Color.parseColor("#f9eec1")top = 1fbottom = 2f}12 -> { //快速眼动endColor = Color.parseColor("#fd817c")paintSleep.color = Color.parseColor("#fd817c")paintPillar.color = Color.parseColor("#4dfd817c")top = 2fbottom = 3f}0, 1 -> { //浅endColor = Color.parseColor("#c64be4")paintSleep.color = Color.parseColor("#c64be4")paintPillar.color = Color.parseColor("#e8c3f1")top = 3fbottom = 4f}2 -> { //深endColor = Color.parseColor("#8a2be2")paintSleep.color = Color.parseColor("#8a2be2")paintPillar.color = Color.parseColor("#d6b9f1")top = 4fbottom = 5f}}if (xSlider < xWithStart + xSpacing * (duration + item.duration) && xSlider > xWithStart + xSpacing * duration) {onDaySelectListener?.invoke(index, item)canvas.drawRect(RectF(xWithStart + xSpacing * duration,ySpacing * top + 10f,xWithStart + xSpacing * (duration + item.duration),ySpacing * 7), paintPillar)}canvas.drawRoundRect(RectF(xWithStart + xSpacing * duration - 1f,ySpacing * top,xWithStart + xSpacing * (duration + item.duration) + 1f,ySpacing * bottom), 10f, 10f, paintSleep)if (index > 0 && index < it.items.size) {if (tempTop < top) {tempTop += 0.9ftempBottom = bottom - 0.9fcolors[0] = startColorcolors[1] = endColorif (xSpacing * preDuration > 10f) {val path1 = Path()path1.moveTo(xWithStart + xSpacing * duration, ySpacing * tempTop)path1.lineTo(xWithStart + xSpacing * duration - 8f,ySpacing * tempTop + 6f)path1.lineTo(xWithStart + xSpacing * duration, ySpacing * tempTop + 12f)path1.close()paintSleep.color = startColorcanvas.drawPath(path1, paintSleep)}if (xSpacing * item.duration > 10f) {val path2 = Path()path2.moveTo(xWithStart + xSpacing * duration, ySpacing * tempBottom)path2.lineTo(xWithStart + xSpacing * duration + 8f,ySpacing * tempBottom - 6f)path2.lineTo(xWithStart + xSpacing * duration,ySpacing * tempBottom - 12f)path2.close()paintSleep.color = endColorcanvas.drawPath(path2, paintSleep)}} else {tempBottom = tempTop + 0.1ftempTop = bottom - 0.1fcolors[0] = endColorcolors[1] = startColorif (xSpacing * preDuration > 10f) {val path1 = Path()path1.moveTo(xWithStart + xSpacing * duration, ySpacing * tempBottom)path1.lineTo(xWithStart + xSpacing * duration - 8f,ySpacing * tempBottom - 6f)path1.lineTo(xWithStart + xSpacing * duration,ySpacing * tempBottom - 12f)path1.close()paintSleep.color = startColorcanvas.drawPath(path1, paintSleep)}if (xSpacing * item.duration > 10f) {val path2 = Path()path2.moveTo(xWithStart + xSpacing * duration, ySpacing * tempTop)path2.lineTo(xWithStart + xSpacing * duration + 8f,ySpacing * tempTop + 6f)path2.lineTo(xWithStart + xSpacing * duration, ySpacing * tempTop + 12f)path2.close()paintSleep.color = endColorcanvas.drawPath(path2, paintSleep)}}val mLinearGradient = LinearGradient(xWithStart + xSpacing * duration,ySpacing * tempTop,xWithStart + xSpacing * duration,ySpacing * tempBottom, colors, null, Shader.TileMode.MIRROR)paintGradientLine.shader = mLinearGradientcanvas.drawLine(xWithStart + xSpacing * duration,ySpacing * tempTop,xWithStart + xSpacing * duration,ySpacing * tempBottom,paintGradientLine)}tempTop = toptempBottom = bottompreDuration = item.durationduration += item.durationstartColor = endColor}}}private fun drawBessel(canvas: Canvas) {// 第一条曲线开始点mFirstCurveStartPoint[(xSlider - curveCircleRadius * 3).toInt()] = (ySpacing * 7).toInt()// 第一条曲线结束点mFirstCurveEndPoint[xSlider.toInt()] =(ySpacing * 7 - curveCircleRadius - curveCircleRadius / 4).toInt()// 第二条开始点mSecondCurveStartPoint = mFirstCurveEndPointmSecondCurveEndPoint[(xSlider + curveCircleRadius * 3).toInt()] = (ySpacing * 7).toInt()// 第一条控制点mFirstCurveControlPoint1[(mFirstCurveStartPoint.x + curveCircleRadius + curveCircleRadius / 4).toInt()] =mFirstCurveStartPoint.ymFirstCurveControlPoint2[(mFirstCurveEndPoint.x - curveCircleRadius * 2 + curveCircleRadius).toInt()] =mFirstCurveEndPoint.y// 第二条控制点mSecondCurveControlPoint1[(mSecondCurveStartPoint.x + curveCircleRadius * 2 - curveCircleRadius).toInt()] =mSecondCurveStartPoint.ymSecondCurveControlPoint2[(mSecondCurveEndPoint.x - curveCircleRadius - curveCircleRadius / 4).toInt()] =mSecondCurveEndPoint.ymPath.reset()mPath.moveTo(0f, ySpacing * 7)mPath.lineTo(mFirstCurveStartPoint.x.toFloat(), mFirstCurveStartPoint.y.toFloat())mPath.cubicTo(mFirstCurveControlPoint1.x.toFloat(), mFirstCurveControlPoint1.y.toFloat(),mFirstCurveControlPoint2.x.toFloat(), mFirstCurveControlPoint2.y.toFloat(),mFirstCurveEndPoint.x.toFloat(), mFirstCurveEndPoint.y.toFloat())mPath.cubicTo(mSecondCurveControlPoint1.x.toFloat(), mSecondCurveControlPoint1.y.toFloat(),mSecondCurveControlPoint2.x.toFloat(), mSecondCurveControlPoint2.y.toFloat(),mSecondCurveEndPoint.x.toFloat(), mSecondCurveEndPoint.y.toFloat())mPath.lineTo(scrWidth, ySpacing * 7)mPath.lineTo(scrWidth, scrHeight)mPath.lineTo(0f, scrHeight)mPath.close()//底部灰色canvas.drawPath(mPath, paintBessel)//底部滑块canvas.drawCircle(xSlider, ySpacing * 7 + 5f, curveCircleRadius, paintRound)}private var startDay = ""private var endDay = ""fun setValue(value: Sleep?, startDay: String, endDay: String): SleepDayChart {this.startDay = startDaythis.endDay = endDaythis.sleepsData = https://www.it610.com/article/valueif (sleepsData == null) {xSpacing = (xWithEnd - xWithStart) / (xData.size - 1)} else {sleepsData?.let {xSpacing = (xWithEnd - xWithStart) / it.total //时间段分割成分钟}}postInvalidate()return this}private fun drawGradientLine(canvas: Canvas) {if (sleepsData == null) {canvas.drawText(context.getString(R.string.no_sleep_data),scrWidth / 2f,scrHeight / 2f,paintXText)} else {val mLinearGradient = LinearGradient(xSlider, ySpacing, xSlider, ySpacing * 6,intArrayOf(context.colorCompat(R.color.ffffff_262626), Color.parseColor("#0e83ff"),context.colorCompat(R.color.ffffff_262626)), null, Shader.TileMode.MIRROR)paintGradientLine.shader = mLinearGradientif (ySpacing > 0) {canvas.drawLine(xSlider, ySpacing, xSlider, ySpacing * 6, paintGradientLine)}}}private var onDaySelectListener: ((index: Int, item: SleepItem) -> Unit)? = nullfun setOnDaySelectListener(l: ((index: Int, item: SleepItem) -> Unit)): SleepDayChart {this.onDaySelectListener = lreturn this}}
以上就是Android实现运动健康睡眠自定义控件的详细内容,更多关于Android 实现自定义控件的资料请关注脚本之家其它相关文章!
推荐阅读
- 大健康|13个月后,新版新冠防控方案终于发布
- 医疗健康周报|【医疗健康周报】第25周:AI技术新药开发商“华深智药”获得约5亿人民币A轮融资,“DEM BioPharma”获得7000万美元融资,加速研发癌症新药物
- 大健康|百万级人才缺口,疫情凸显“护士荒”与“护士漂”
- Flutter|【Flutter】Android原生WebView(非Flutter WebView)与FlutterWeb交互
- 程序员|Flutter(实战技巧,最新阿里Android高级面试题及答案)
- Android开发|Flutter 小技巧之 MediaQuery 和 build 优化你不知道的秘密
- 运动App如何实现端侧后台保活,让运动记录更完整()
- java|第十六课(Android打包发布)
- Android基础入门|【毕业季_进击的技术er】送别过去两年迷茫的自己。重整旗鼓,大三我来啦
- linux软件怎么打包,如何发布打包并发布自己的Android应用(APP)