最近在练习自定义view, 想起之前面试的时候笔试有道题是写出自定义一个时钟的关键代码. 今天就来实现一下. 步骤依然是先分析, 再上代码. 实现效果 View分析 时钟主要分为五个部分:
最近在练习自定义view, 想起之前面试的时候笔试有道题是写出自定义一个时钟的关键代码. 今天就来实现一下. 步骤依然是先分析, 再上代码.
实现效果
View分析
时钟主要分为五个部分:
1、中心点: 圆心位置
2、圆盘: 以中心点为圆心,drawCircle画个圆
3、刻度:
paint有个aip, setPathEffect可以根据path画特效, 那么刻度就可以根据圆的path画一个矩形path的特效, 并且这个api只会画特效, 不会画出圆.
/** * shape: 特效的path, 这里传一个矩形 * advance: 两个特效path之间的间距, 即两个矩形的left间距 * phase: 特效起始位置的偏移 * style: 原始path拐弯的时候特效path的转换方式,这里用ROTATE跟着旋转即可 */ PathDashPathEffect(Path shape, float advance, float phase, Style style)
刻度又分两种, 粗一点刻度: 3、6、9、12, 和细一点的刻度. 两种特效又可以用SumPathEffect合起来画
SumPathEffect(PathEffect first, PathEffect second)
4、时分秒指针
时分秒指针都是一个圆角矩形, 先把他们的位置计算出来, 然后旋转圆心去绘制不同角度的指针.
5、动画效果
TimerTask每隔一秒计算时间, 根据时间去换算当前时分秒指针的角度, 动态变量只有三个角度.
实现源码
// // 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) //时分秒指针 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() //画中心点 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) //小时 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小时 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 } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持自由互联。