Android運(yùn)動(dòng)健康睡眠自定義控件的實(shí)現(xiàn)
效果圖
代碼
/** * * 日?qǐng)D表 * zrj 2020/8/25 */ class SleepDayChart(context: Context, attrs: AttributeSet?) : View(context, attrs) { //屏幕寬高 private var scrWidth = 0f private var scrHeight = 0f private var xData: Array<String> = arrayOf("20:00", "02:00", "08:00", "14:00", "20:00") private var sleepsData: Sleep? = null private lateinit var paintLine: Paint private lateinit var paintGradientLine: Paint private lateinit var paintXText: Paint private lateinit var paintSleep: Paint private lateinit var paintPillar: Paint private lateinit var paintRound: Paint private lateinit var paintBessel: Paint private var xSlider = 0f //滑塊的x軸位置 private var mPath: Path private val curveCircleRadius = 12f.dp // the coordinates of the first curve private val mFirstCurveStartPoint = Point() private val mFirstCurveEndPoint = Point() private val mFirstCurveControlPoint1 = Point() private val mFirstCurveControlPoint2 = Point() //the coordinates of the second curve private 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.STROKE paintLine.strokeWidth = 1f paintLine.color = context.colorCompat(R.color.e6e6e6_2e2e2e) paintGradientLine = Paint() paintGradientLine.style = Paint.Style.STROKE paintGradientLine.strokeWidth = 1f paintXText = Paint() paintXText.isAntiAlias = true paintXText.strokeWidth = 1f paintXText.textSize = 12f.sp paintXText.textAlign = Paint.Align.CENTER paintXText.color = context.colorCompat(R.color.color_on_surface) paintSleep = Paint() paintSleep.style = Paint.Style.FILL paintSleep.isAntiAlias = true paintSleep.color = context.colorCompat(R.color.blue_7fbeff) paintPillar = Paint() paintPillar.style = Paint.Style.FILL paintPillar.isAntiAlias = true paintPillar.color = context.colorCompat(R.color.blue_7fbeff) paintRound = Paint() paintRound.style = Paint.Style.FILL paintRound.isAntiAlias = true paintRound.color = context.colorCompat(R.color.ffffff_6e6e6e) paintBessel = Paint() paintBessel.style = Paint.Style.FILL paintBessel.isAntiAlias = true paintBessel.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份 //底部圓滑塊可以滑動(dòng)的范圍 xWithStart = margin * 3 xWithEnd = scrWidth - margin * 3 xSlider = scrWidth / 2 xSpacing = (xWithEnd - xWithStart) / (xData.size - 1) } override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { parent.requestDisallowInterceptTouchEvent(true) return super.dispatchTouchEvent(ev) } private var mDownX = 0f private var mDownY = 0f private var isSlider = false @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { when (event.action) { MotionEvent.ACTION_DOWN -> { mDownX = event.x mDownY = event.y isSlider = abs(event.x - xSlider) < 60f && abs(event.y - ySpacing * 7) < 60f return isSlider } MotionEvent.ACTION_MOVE -> if (abs(event.y - mDownY) < abs(event.x - mDownX)) { if (isSlider) { xSlider = event.x if (xSlider < xWithStart) { xSlider = xWithStart } if (xSlider > xWithEnd) { xSlider = xWithEnd } invalidate() } } MotionEvent.ACTION_UP -> { if (!isSlider) { if (abs(event.x - mDownX) < curveCircleRadius) { xSlider = event.x invalidate() } } } } return true } private val margin = 20f.dp //左右兩邊距離 private var xWithStart = 0f //x軸的起始點(diǎn) private var xWithEnd = 0f //x軸結(jié)束點(diǎn) private var ySpacing = 0f //高度分割份數(shù)后間距 private var xSpacing = 0f //x軸分割份數(shù)后間距 @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 == null) { xData.forEachIndexed { index, s -> val x = xWithStart + xSpacing * index val dis = abs(x - xSlider) var y = ySpacing * 7 - 10f.dp if (dis < xSpacing / 2) { paintXText.typeface = Typeface.DEFAULT_BOLD y -= 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.minuteOfHour val end = DateTime(it.items.last().timeStamp * 1000) val wakeUp = end.hourOfDay * 60 + end.minuteOfHour + it.items.last().duration val 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 - 10f if (dis1 < curveCircleRadius * 3) { paintXText.typeface = Typeface.DEFAULT_BOLD var temp = 1 - dis1 / curveCircleRadius * 2 if (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 - 10f if (dis2 < curveCircleRadius * 3) { paintXText.typeface = Typeface.DEFAULT_BOLD y2 -= 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 = 0f var bottom = 0f var preDuration = 0 //前一狀態(tài)時(shí)長(zhǎng) var duration = 0 //時(shí)間累加 var tempTop = 0f var tempBottom: Float var startColor = 0 var endColor = 0 val 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 = 1f bottom = 2f } 12 -> { //快速眼動(dòng) endColor = Color.parseColor("#fd817c") paintSleep.color = Color.parseColor("#fd817c") paintPillar.color = Color.parseColor("#4dfd817c") top = 2f bottom = 3f } 0, 1 -> { //淺 endColor = Color.parseColor("#c64be4") paintSleep.color = Color.parseColor("#c64be4") paintPillar.color = Color.parseColor("#e8c3f1") top = 3f bottom = 4f } 2 -> { //深 endColor = Color.parseColor("#8a2be2") paintSleep.color = Color.parseColor("#8a2be2") paintPillar.color = Color.parseColor("#d6b9f1") top = 4f bottom = 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.9f tempBottom = bottom - 0.9f colors[0] = startColor colors[1] = endColor if (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 = startColor canvas.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 = endColor canvas.drawPath(path2, paintSleep) } } else { tempBottom = tempTop + 0.1f tempTop = bottom - 0.1f colors[0] = endColor colors[1] = startColor if (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 = startColor canvas.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 = endColor canvas.drawPath(path2, paintSleep) } } val mLinearGradient = LinearGradient( xWithStart + xSpacing * duration, ySpacing * tempTop, xWithStart + xSpacing * duration, ySpacing * tempBottom, colors, null, Shader.TileMode.MIRROR ) paintGradientLine.shader = mLinearGradient canvas.drawLine( xWithStart + xSpacing * duration, ySpacing * tempTop, xWithStart + xSpacing * duration, ySpacing * tempBottom, paintGradientLine ) } tempTop = top tempBottom = bottom preDuration = item.duration duration += item.duration startColor = endColor } } } private fun drawBessel(canvas: Canvas) { // 第一條曲線開(kāi)始點(diǎn) mFirstCurveStartPoint[(xSlider - curveCircleRadius * 3).toInt()] = (ySpacing * 7).toInt() // 第一條曲線結(jié)束點(diǎn) mFirstCurveEndPoint[xSlider.toInt()] = (ySpacing * 7 - curveCircleRadius - curveCircleRadius / 4).toInt() // 第二條開(kāi)始點(diǎn) mSecondCurveStartPoint = mFirstCurveEndPoint mSecondCurveEndPoint[(xSlider + curveCircleRadius * 3).toInt()] = (ySpacing * 7).toInt() // 第一條控制點(diǎn) mFirstCurveControlPoint1[(mFirstCurveStartPoint.x + curveCircleRadius + curveCircleRadius / 4).toInt()] = mFirstCurveStartPoint.y mFirstCurveControlPoint2[(mFirstCurveEndPoint.x - curveCircleRadius * 2 + curveCircleRadius).toInt()] = mFirstCurveEndPoint.y // 第二條控制點(diǎn) mSecondCurveControlPoint1[(mSecondCurveStartPoint.x + curveCircleRadius * 2 - curveCircleRadius).toInt()] = mSecondCurveStartPoint.y mSecondCurveControlPoint2[(mSecondCurveEndPoint.x - curveCircleRadius - curveCircleRadius / 4).toInt()] = mSecondCurveEndPoint.y mPath.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 = startDay this.endDay = endDay this.sleepsData = value if (sleepsData == null) { xSpacing = (xWithEnd - xWithStart) / (xData.size - 1) } else { sleepsData?.let { xSpacing = (xWithEnd - xWithStart) / it.total //時(shí)間段分割成分鐘 } } 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 = mLinearGradient if (ySpacing > 0) { canvas.drawLine(xSlider, ySpacing, xSlider, ySpacing * 6, paintGradientLine) } } } private var onDaySelectListener: ((index: Int, item: SleepItem) -> Unit)? = null fun setOnDaySelectListener(l: ((index: Int, item: SleepItem) -> Unit)): SleepDayChart { this.onDaySelectListener = l return this } }
以上就是Android實(shí)現(xiàn)運(yùn)動(dòng)健康睡眠自定義控件的詳細(xì)內(nèi)容,更多關(guān)于Android 實(shí)現(xiàn)自定義控件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Android自定義控件實(shí)現(xiàn)通用驗(yàn)證碼輸入框(二)
- Android自定義控件實(shí)現(xiàn)通用驗(yàn)證碼輸入框
- Android自定義控件橫向柱狀統(tǒng)計(jì)圖
- Android自定義控件之圓形進(jìn)度條動(dòng)畫
- Android自定義控件之水平圓點(diǎn)加載進(jìn)度條
- Android自定義控件之刻度尺控件
- Android Studio 創(chuàng)建自定義控件的方法
- Android開(kāi)發(fā)自定義控件之折線圖實(shí)現(xiàn)方法詳解
- Android自定義控件實(shí)現(xiàn)時(shí)鐘效果
- Android自定義控件仿ios下拉回彈效果
相關(guān)文章
Android音頻可視化開(kāi)發(fā)案例說(shuō)明
最近移植Android,當(dāng)Android能夠在設(shè)備上面運(yùn)行之后,首先想到的是讓音頻設(shè)備跑起來(lái)?!皼](méi)有聲音,再好的戲也出不來(lái)”接下來(lái)介紹Android音頻可視化開(kāi)發(fā)流程2012-12-12Android 實(shí)現(xiàn)圓角圖片的簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android 實(shí)現(xiàn)圓角圖片的簡(jiǎn)單實(shí)例的相關(guān)資料,Android 圓角圖片的實(shí)現(xiàn)形式,包括用第三方、也有系統(tǒng),需要的朋友可以參考下2017-07-07Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)攔截讀取功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)攔截讀取功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08android實(shí)現(xiàn)圖片橡皮擦和快速染色功能
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)圖片橡皮擦和快速染色功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12Android DrawableTextView圖片文字居中顯示實(shí)例
在我們開(kāi)發(fā)中,TextView設(shè)置Android:drawableLeft一定使用的非常多,但Drawable和Text同時(shí)居中顯示可能不好控制,小編想到通過(guò)自定義TextView實(shí)現(xiàn),具體詳情大家參考下本文2017-03-03Flutter上線項(xiàng)目實(shí)戰(zhàn)記錄之路由篇
這篇文章主要給大家介紹了關(guān)于Flutter上線項(xiàng)目實(shí)戰(zhàn)記錄之路由篇的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09通過(guò)FancyView提供 Android 酷炫的開(kāi)屏動(dòng)畫實(shí)例代碼
這篇文章主要介紹了通過(guò)FancyView提供 Android 酷炫的開(kāi)屏動(dòng)畫的實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-12-12