Android自定義View實(shí)現(xiàn)時(shí)鐘功能
最近在練習(xí)自定義view, 想起之前面試的時(shí)候筆試有道題是寫出自定義一個(gè)時(shí)鐘的關(guān)鍵代碼. 今天就來實(shí)現(xiàn)一下. 步驟依然是先分析, 再上代碼.
實(shí)現(xiàn)效果
View分析
時(shí)鐘主要分為五個(gè)部分:
1、中心點(diǎn): 圓心位置
2、圓盤: 以中心點(diǎn)為圓心,drawCircle畫個(gè)圓
3、刻度:
paint有個(gè)aip, setPathEffect可以根據(jù)path畫特效, 那么刻度就可以根據(jù)圓的path畫一個(gè)矩形path的特效, 并且這個(gè)api只會(huì)畫特效, 不會(huì)畫出圓.
/** * shape: 特效的path, 這里傳一個(gè)矩形 * advance: 兩個(gè)特效path之間的間距, 即兩個(gè)矩形的left間距 * phase: 特效起始位置的偏移 * style: 原始path拐彎的時(shí)候特效path的轉(zhuǎn)換方式,這里用ROTATE跟著旋轉(zhuǎn)即可 */ PathDashPathEffect(Path shape, float advance, float phase, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Style style)
刻度又分兩種, 粗一點(diǎn)刻度: 3、6、9、12, 和細(xì)一點(diǎn)的刻度. 兩種特效又可以用SumPathEffect合起來畫
SumPathEffect(PathEffect first, PathEffect second)?
4、時(shí)分秒指針
時(shí)分秒指針都是一個(gè)圓角矩形, 先把他們的位置計(jì)算出來, 然后旋轉(zhuǎn)圓心去繪制不同角度的指針.
5、動(dòng)畫效果
TimerTask每隔一秒計(jì)算時(shí)間, 根據(jù)時(shí)間去換算當(dāng)前時(shí)分秒指針的角度, 動(dòng)態(tài)變量只有三個(gè)角度.
實(shí)現(xiàn)源碼
// // Created by skylar on 2022/4/19. // class ClockView : View { ? ? private var mTimer: Timer? = null ? ? private val mCirclePaint: Paint = Paint() ? ? private val mPointerPaint: Paint = Paint() ? ? private val mTextPaint: Paint = Paint() ? ? private val mCirclePath: Path = Path() ? ? private val mHourPath: Path = Path() ? ? private val mMinutePath: Path = Path() ? ? private val mSecondPath: Path = Path() ? ? private lateinit var mPathMeasure: PathMeasure ? ? private lateinit var mSumPathEffect: SumPathEffect ? ? private var mViewWidth = 0 ? ? private var mViewHeight = 0 ? ? private var mCircleWidth = 6f ? ? private var mRadius = 0f ? ? private var mRectRadius = 20f ? ? private var mHoursDegree = 0f ? ? private var mMinutesDegree = 0f ? ? private var mSecondsDegree = 0f ? ? private var mCurrentTimeInSecond = 0L ? ? constructor(context: Context) : super(context) ? ? constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) ? ? constructor( ? ? ? ? context: Context, attrs: AttributeSet?, ? ? ? ? defStyleAttr: Int ? ? ) : super(context, attrs, defStyleAttr) ? ? init { ? ? ? ? mCirclePaint.color = Color.BLACK ? ? ? ? mCirclePaint.isAntiAlias = true ? ? ? ? mCirclePaint.style = Paint.Style.STROKE ? ? ? ? mCirclePaint.strokeWidth = mCircleWidth ? ? ? ? mPointerPaint.color = Color.BLACK ? ? ? ? mPointerPaint.isAntiAlias = true ? ? ? ? mPointerPaint.style = Paint.Style.FILL ? ? ? ? mTextPaint.color = Color.BLACK ? ? ? ? mTextPaint.isAntiAlias = true ? ? ? ? mTextPaint.style = Paint.Style.FILL ? ? ? ? mTextPaint.textSize = 40f ? ? } ? ? override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { ? ? ? ? super.onSizeChanged(w, h, oldw, oldh) ? ? ? ? mViewWidth = measuredWidth - paddingLeft - paddingRight ? ? ? ? mViewHeight = measuredHeight - paddingTop - paddingBottom ? ? ? ? mRadius = mViewWidth / 2 - mCircleWidth ? ? ? ? mCirclePath.addCircle(0f, 0f, mRadius, Path.Direction.CW) ? ? ? ? mPathMeasure = PathMeasure(mCirclePath, false) ? ? ? ? val minutesShapePath = Path() ? ? ? ? val quarterShapePath = Path() ? ? ? ? minutesShapePath.addRect(0f, 0f, mRadius * 0.01f, mRadius * 0.06f, Path.Direction.CW) ? ? ? ? quarterShapePath.addRect(0f, 0f, mRadius * 0.02f, mRadius * 0.06f, Path.Direction.CW) ? ? ? ? val minutesDashPathEffect = PathDashPathEffect( ? ? ? ? ? ? minutesShapePath, ? ? ? ? ? ? mPathMeasure.length / 60, ? ? ? ? ? ? 0f, ? ? ? ? ? ? PathDashPathEffect.Style.ROTATE ? ? ? ? ) ? ? ? ? val quarterDashPathEffect = PathDashPathEffect( ? ? ? ? ? ? quarterShapePath, ? ? ? ? ? ? mPathMeasure.length / 12, ? ? ? ? ? ? 0f, ? ? ? ? ? ? PathDashPathEffect.Style.ROTATE ? ? ? ? ) ? ? ? ? mSumPathEffect = SumPathEffect(minutesDashPathEffect, quarterDashPathEffect) ? ? ? ? val hourPointerHeight = mRadius * 0.5f ? ? ? ? val hourPointerWidth = mRadius * 0.07f ? ? ? ? val hourRect = RectF( ? ? ? ? ? ? -hourPointerWidth / 2, ? ? ? ? ? ? -hourPointerHeight * 0.7f, ? ? ? ? ? ? hourPointerWidth / 2, ? ? ? ? ? ? hourPointerHeight * 0.3f ? ? ? ? ) ? ? ? ? mHourPath.addRoundRect(hourRect, mRectRadius, mRectRadius, Path.Direction.CW) ? ? ? ? val minutePointerHeight = mRadius * 0.7f ? ? ? ? val minutePointerWidth = mRadius * 0.05f ? ? ? ? val minuteRect = RectF( ? ? ? ? ? ? -minutePointerWidth / 2, ? ? ? ? ? ? -minutePointerHeight * 0.8f, ? ? ? ? ? ? minutePointerWidth / 2, ? ? ? ? ? ? minutePointerHeight * 0.2f ? ? ? ? ) ? ? ? ? mMinutePath.addRoundRect(minuteRect, mRectRadius, mRectRadius, Path.Direction.CW) ? ? ? ? val secondPointerHeight = mRadius * 0.9f ? ? ? ? val secondPointerWidth = mRadius * 0.03f ? ? ? ? val secondRect = RectF( ? ? ? ? ? ? -secondPointerWidth / 2, ? ? ? ? ? ? -secondPointerHeight * 0.8f, ? ? ? ? ? ? secondPointerWidth / 2, ? ? ? ? ? ? secondPointerHeight * 0.2f ? ? ? ? ) ? ? ? ? mSecondPath.addRoundRect(secondRect, mRectRadius, mRectRadius, Path.Direction.CW) ? ? ? ? startAnimator() ? ? } ? ? override fun onDraw(canvas: Canvas?) { ? ? ? ? super.onDraw(canvas) ? ? ? ? if (canvas == null) { ? ? ? ? ? ? return ? ? ? ? } ? ? ? ? canvas.translate((mViewWidth / 2).toFloat(), (mViewHeight / 2).toFloat()) ? ? ? ? //畫圓盤 ? ? ? ? mCirclePaint.pathEffect = null ? ? ? ? canvas.drawPath(mCirclePath, mCirclePaint) ? ? ? ? //畫刻度 ? ? ? ? mCirclePaint.pathEffect = mSumPathEffect ? ? ? ? canvas.drawPath(mCirclePath, mCirclePaint) ? ? ? ? //時(shí)分秒指針 ? ? ? ? mPointerPaint.color = Color.BLACK ? ? ? ? canvas.save() ? ? ? ? canvas.rotate(mHoursDegree) ? ? ? ? canvas.drawPath(mHourPath, mPointerPaint) ? ? ? ? canvas.restore() ? ? ? ? canvas.save() ? ? ? ? canvas.rotate(mMinutesDegree) ? ? ? ? canvas.drawPath(mMinutePath, mPointerPaint) ? ? ? ? canvas.restore() ? ? ? ? canvas.save() ? ? ? ? canvas.rotate(mSecondsDegree) ? ? ? ? canvas.drawPath(mSecondPath, mPointerPaint) ? ? ? ? canvas.restore() ? ? ? ? //畫中心點(diǎn) ? ? ? ? mPointerPaint.color = Color.WHITE ? ? ? ? canvas.drawCircle(0f, 0f, mRadius * 0.02f, mPointerPaint) ? ? } ? ? private fun startAnimator() { ? ? ? ? val cal = Calendar.getInstance() ? ? ? ? val hour = cal.get(Calendar.HOUR) ?//小時(shí) ? ? ? ? val minute = cal.get(Calendar.MINUTE) ?//分 ? ? ? ? val second = cal.get(Calendar.SECOND) ?//秒 ? ? ? ? mCurrentTimeInSecond = (hour * 60 * 60 + minute * 60 + second).toLong() ? ? ? ? if (mTimer == null) { ? ? ? ? ? ? mTimer = Timer() ? ? ? ? } else { ? ? ? ? ? ? mTimer?.cancel() ? ? ? ? ? ? mTimerTask.cancel() ? ? ? ? } ? ? ? ? mTimer?.schedule(mTimerTask, 0, 1000) ? ? } ? ? private var mTimerTask: TimerTask = object : TimerTask() { ? ? ? ? override fun run() { ? ? ? ? ? ? mCurrentTimeInSecond++ ? ? ? ? ? ? computeDegree() ? ? ? ? ? ? invalidate() ? ? ? ? } ? ? } ? ? //12小時(shí) 00:00:00 ~ 12:00:00 ? ? private fun computeDegree() { ? ? ? ? val secondsInOneRoll = 12 * 60 * 60 ? ? ? ? val currentSeconds = mCurrentTimeInSecond % secondsInOneRoll ? ? ? ? var leftSeconds = currentSeconds ? ? ? ? val hours = currentSeconds / 60 / 60 ? ? ? ? leftSeconds = currentSeconds - hours * 60 * 60 ? ? ? ? val minutes = leftSeconds / 60 ? ? ? ? leftSeconds -= minutes * 60 ? ? ? ? val seconds = leftSeconds % 60 ? ? ? ? mHoursDegree = hours * 30f ? ? ? ? mMinutesDegree = minutes * 6f ? ? ? ? mSecondsDegree = seconds * 6f ? ? } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
解決Android加殼過程中mprotect調(diào)用失敗的原因分析
本文探討的主要內(nèi)容是mprotect調(diào)用失敗的根本原因,以及在加殼實(shí)現(xiàn)中的解決方案,通過本文的闡述,一方面能夠幫助遇到同類問題的小伙伴解決心中的疑惑,另一方面能夠給大家提供可落地的實(shí)現(xiàn)方案,需要的朋友可以參考下2022-01-01淺談Android App開發(fā)中Fragment的創(chuàng)建與生命周期
這篇文章主要介紹了Android App開發(fā)中Fragment的創(chuàng)建與生命周期,文中詳細(xì)地介紹了Fragment的概念以及一些常用的生命周期控制方法,需要的朋友可以參考下2016-02-02Android獲取手機(jī)號(hào)碼和運(yùn)營(yíng)商信息的方法
這篇文章主要介紹了Android獲取手機(jī)號(hào)碼和運(yùn)營(yíng)商信息的方法,以實(shí)例形式完整講述了獲取手機(jī)號(hào)碼和運(yùn)營(yíng)商信息的技巧,代碼中包含完整的注釋說明,需要的朋友可以參考下2015-01-01Android BottomSheetDialog實(shí)現(xiàn)底部對(duì)話框的示例
這篇文章主要介紹了Android BottomSheetDialog實(shí)現(xiàn)底部對(duì)話框的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06Android實(shí)現(xiàn)短信發(fā)送功能
這篇文章主要介紹了Android實(shí)現(xiàn)短信發(fā)送功能,對(duì)Android實(shí)現(xiàn)短信發(fā)送的每一步都進(jìn)行了詳細(xì)的介紹,感興趣的小伙伴們可以參考一下2015-12-12利用Jetpack?Compose實(shí)現(xiàn)繪制五角星效果
這篇文章主要為大家介紹了Jetpack?Compose如何使用自定義操作符實(shí)現(xiàn)繪制五角星效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-04-04android中AutoCompleteTextView的簡(jiǎn)單用法(實(shí)現(xiàn)搜索歷史)
本篇文章主要介紹了android中AutoCompleteTextView的簡(jiǎn)單用法(自動(dòng)提示),有需要的可以了解一下。2016-11-11