版权声明
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17073817.html
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
前言
自定义View实现时钟涉及到三角函数,如果你对三角函数不甚了解或者已经遗忘,请参考我的博客:圆与三角函数的公式与使用 这篇博客详细解释了三角函数公式与对应坐标关系。里面也举例了时钟的实现,只不过是用Jetpack compose的自定义View实现的时钟。而这篇博客用老的方式继承View实现。
例子1
效果图
代码
import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Point import android.util.AttributeSet import android.view.View import java.util.* /** * 时钟View */ class ClockView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private var mCalendar = Calendar.getInstance() private var mWidth = 0 private var mHeight = 0 private var mRadius = 0 private val mCentralPoint = Point() private val mHourPaint = Paint() private val mMinutePaint = Paint() private val mSecondsPaint = Paint() private val mScalePaint = Paint() private val mTextPaint = Paint() init { //时 mHourPaint.color = Color.WHITE mHourPaint.flags = Paint.ANTI_ALIAS_FLAG mHourPaint.strokeWidth = 4f mHourPaint.strokeCap = Paint.Cap.ROUND //分 mMinutePaint.color = Color.WHITE mMinutePaint.flags = Paint.ANTI_ALIAS_FLAG mMinutePaint.strokeWidth = 2f mMinutePaint.strokeCap = Paint.Cap.ROUND //秒 mSecondsPaint.color = Color.RED mSecondsPaint.flags = Paint.ANTI_ALIAS_FLAG mSecondsPaint.strokeCap = Paint.Cap.ROUND //刻度 mScalePaint.color = Color.WHITE mScalePaint.flags = Paint.ANTI_ALIAS_FLAG //文字 mTextPaint.color = Color.WHITE mTextPaint.flags = Paint.ANTI_ALIAS_FLAG mTextPaint.textAlign = Paint.Align.CENTER mTextPaint.textSize = 25f } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { mWidth = MeasureSpec.getSize(widthMeasureSpec) mHeight = MeasureSpec.getSize(heightMeasureSpec) mCentralPoint.x = mWidth / 2 mCentralPoint.y = mHeight / 2 mRadius = if (mWidth > mHeight) mHeight / 2 else mWidth / 2 super.onMeasure(widthMeasureSpec, heightMeasureSpec) } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) mCalendar = Calendar.getInstance() drawClockBackground(canvas) drawCurrentHour(canvas) drawCurrentMinute(canvas) drawCurrentSecond(canvas) postInvalidateDelayed(100) } /** * 绘制时钟背景,画文字与刻度 */ private fun drawClockBackground(canvas: Canvas) { //画表盘 for (i in 1..60) { val angle = 360 / 60 * i + 48 val o = angle * Math.PI / 180 var scaleRadius = mRadius * 0.95 //刻度长度 if (i % 5 == 2) { scaleRadius = mRadius * 0.90 } val px1 = mCentralPoint.x + scaleRadius * Math.cos(o) val py1 = mCentralPoint.y - scaleRadius * Math.sin(o) val px2 = mCentralPoint.x + mRadius * Math.cos(o) val py2 = mCentralPoint.y - mRadius * Math.sin(o) canvas.drawLine(px1.toFloat(), py1.toFloat(), px2.toFloat(), py2.toFloat(), mScalePaint) } //画数字 for (i in 1..12) { val textAngle = 360 / 12 * i + 90 var textRadius = mRadius * 0.8 //数字的位置半径 val textO = textAngle * Math.PI / 180 val textPx = mCentralPoint.x + textRadius * Math.cos(textO) //因为文字有绘制基线导致12会比6显示更高一点,这样与时钟圆盘显示起来不在同一的圆心点上,这里减少圆心Y轴坐标做到 val textPy = (mCentralPoint.y + 10) - textRadius * Math.sin(textO) val text = if (12 - i == 0) 12 else 12 - i canvas.drawText(text.toString(), textPx.toFloat(), textPy.toFloat(), mTextPaint) } } /** * 绘制小时指针 */ private fun drawCurrentHour(canvas: Canvas) { val hour = mCalendar.get(Calendar.HOUR) val minute = mCalendar.get(Calendar.MINUTE) //得出分钟角度,转成负数是希望以顺时针旋转 var angle = -(360 / 12 * hour - 90) //求当前分钟的比例 val proportion = 360f / 60f * minute / 360 //将30度乘分钟比得出当前小时需要多移动的角度 angle -= (360 / 12 * proportion).toInt() val o = angle * Math.PI / 180 val radius = mRadius * 0.6 val px = mCentralPoint.x + radius * Math.cos(o) val py = mCentralPoint.y - radius * Math.sin(o) canvas.drawLine( mCentralPoint.x.toFloat(), mCentralPoint.y.toFloat(), px.toFloat(), py.toFloat(), mHourPaint ) } /** * 绘制分钟指针 */ private fun drawCurrentMinute(canvas: Canvas) { val minute = mCalendar.get(Calendar.MINUTE) val second = mCalendar.get(Calendar.SECOND) //得出分钟角度,转成负数是希望以顺时针旋转 var angle = -(360 / 60 * minute - 90) //求当前秒钟的比例 val proportion = 360f / 60f * second / 360 //将6度乘秒钟比得出当前分钟需要多移动的角度 angle -= (360 / 60 * proportion).toInt() val o = angle * Math.PI / 180 val radius = mRadius * 0.7 val px = mCentralPoint.x + radius * Math.cos(o) val py = mCentralPoint.y - radius * Math.sin(o) canvas.drawLine( mCentralPoint.x.toFloat(), mCentralPoint.y.toFloat(), px.toFloat(), py.toFloat(), mMinutePaint ) } /** * 绘制秒指针 */ private fun drawCurrentSecond(canvas: Canvas) { val second = mCalendar.get(Calendar.SECOND) //得出秒角度,转成负数是希望以顺时针旋转 val angle = -(360 / 60 * second - 90) val o = angle * Math.PI / 180 val radius = mRadius * 0.75 val px = mCentralPoint.x + radius * Math.cos(o) val py = mCentralPoint.y - radius * Math.sin(o) canvas.drawLine( mCentralPoint.x.toFloat(), mCentralPoint.y.toFloat(), px.toFloat(), py.toFloat(), mSecondsPaint ) } }
例子2
效果图
代码
import android.content.Context import android.graphics.* import android.util.AttributeSet import android.view.View import java.util.* /** * 时钟View */ class ClockView2(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private var mCalendar = Calendar.getInstance() private var mWidth = 0 private var mHeight = 0 private var mRadius = 0 private val mCentralPoint = Point() private val mHourPaint = Paint() private val mMinutePaint = Paint() private val mSecondsPaint = Paint() private val mScalePaint = Paint() private val mOvalPaint = Paint() init { //时 mHourPaint.color = Color.WHITE mHourPaint.flags = Paint.ANTI_ALIAS_FLAG mHourPaint.strokeCap = Paint.Cap.ROUND //分 mMinutePaint.color = Color.WHITE mMinutePaint.flags = Paint.ANTI_ALIAS_FLAG mMinutePaint.strokeWidth = 2f mMinutePaint.strokeCap = Paint.Cap.ROUND //秒 mSecondsPaint.flags = Paint.ANTI_ALIAS_FLAG mSecondsPaint.strokeCap = Paint.Cap.ROUND //刻度 mScalePaint.color = Color.WHITE mScalePaint.flags = Paint.ANTI_ALIAS_FLAG mScalePaint.strokeWidth = 8f mScalePaint.strokeCap = Paint.Cap.ROUND //圆形 mOvalPaint.color = Color.WHITE mOvalPaint.flags = Paint.ANTI_ALIAS_FLAG } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { mWidth = MeasureSpec.getSize(widthMeasureSpec) mHeight = MeasureSpec.getSize(heightMeasureSpec) mCentralPoint.x = mWidth / 2 mCentralPoint.y = mHeight / 2 mRadius = if (mWidth > mHeight) mHeight / 2 else mWidth / 2 super.onMeasure(widthMeasureSpec, heightMeasureSpec) } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) mCalendar = Calendar.getInstance() drawClockBackground(canvas) drawCurrentHour(canvas) drawCurrentMinute(canvas) drawCurrentSecond(canvas) drawOval(canvas) postInvalidateDelayed(300) } /** * 绘制时钟背景,画文字与刻度 */ private fun drawClockBackground(canvas: Canvas) { //画表盘 for (i in 1..60) { val angle = 360 / 60 * i + 48 val o = angle * Math.PI / 180 var scaleRadius = mRadius * 0.95 //刻度长度 mScalePaint.color = Color.WHITE if (i % 5 == 2) { scaleRadius = mRadius * 0.75 } val px1 = mCentralPoint.x + scaleRadius * Math.cos(o) val py1 = mCentralPoint.y - scaleRadius * Math.sin(o) val px2 = mCentralPoint.x + (mRadius * 0.95) * Math.cos(o) val py2 = mCentralPoint.y - (mRadius * 0.95) * Math.sin(o) canvas.drawLine(px1.toFloat(), py1.toFloat(), px2.toFloat(), py2.toFloat(), mScalePaint) } } /** * 绘制小时指针 */ private fun drawCurrentHour(canvas: Canvas) { val hour = mCalendar.get(Calendar.HOUR) val minute = mCalendar.get(Calendar.MINUTE) //得出分钟角度,转成负数是希望以顺时针旋转 var angle = -(360 / 12 * hour - 90) //求当前分钟的比例 val proportion = 360f / 60f * minute / 360 //将30度乘分钟比得出当前小时需要多移动的角度 angle -= (360 / 12 * proportion).toInt() val o = angle * Math.PI / 180 val px1 = mCentralPoint.x + (mRadius * 0.15) * Math.cos(o) val py1 = mCentralPoint.y - (mRadius * 0.15) * Math.sin(o) val px2 = mCentralPoint.x + (mRadius * 0.6) * Math.cos(o) val py2 = mCentralPoint.y - (mRadius * 0.6) * Math.sin(o) mHourPaint.color = Color.WHITE mHourPaint.strokeWidth = 18f canvas.drawLine( px1.toFloat(), py1.toFloat(), px2.toFloat(), py2.toFloat(), mHourPaint ) mHourPaint.strokeWidth = 7f val px3 = mCentralPoint.x + (mRadius * 0.6) * Math.cos(o) val py3 = mCentralPoint.y - (mRadius * 0.6) * Math.sin(o) canvas.drawLine( mCentralPoint.x.toFloat(), mCentralPoint.y.toFloat(), px3.toFloat(), py3.toFloat(), mHourPaint ) //画整点标记 if (minute <= 1) { mHourPaint.strokeWidth = 9f mHourPaint.color = Color.parseColor("#3AC0E5") val px4 = mCentralPoint.x + (mRadius * 0.75) * Math.cos(o) val py4 = mCentralPoint.y - (mRadius * 0.75) * Math.sin(o) val px5 = mCentralPoint.x + (mRadius * 0.95) * Math.cos(o) val py5 = mCentralPoint.y - (mRadius * 0.95) * Math.sin(o) canvas.drawLine( px4.toFloat(), py4.toFloat(), px5.toFloat(), py5.toFloat(), mHourPaint ) } } /** * 绘制分钟指针 */ private fun drawCurrentMinute(canvas: Canvas) { val minute = mCalendar.get(Calendar.MINUTE) val second = mCalendar.get(Calendar.SECOND) //得出分钟角度,转成负数是希望以顺时针旋转 var angle = -(360 / 60 * minute - 90) //求当前秒钟的比例 val proportion = 360f / 60f * second / 360 //将6度乘秒钟比得出当前分钟需要多移动的角度 angle -= (360 / 60 * proportion).toInt() val o = angle * Math.PI / 180 val px1 = mCentralPoint.x + (mRadius * 0.15) * Math.cos(o) val py1 = mCentralPoint.y - (mRadius * 0.15) * Math.sin(o) val px2 = mCentralPoint.x + (mRadius * 0.75) * Math.cos(o) val py2 = mCentralPoint.y - (mRadius * 0.75) * Math.sin(o) mMinutePaint.strokeWidth = 18f canvas.drawLine( px1.toFloat(), py1.toFloat(), px2.toFloat(), py2.toFloat(), mMinutePaint ) mMinutePaint.strokeWidth = 7f val px3 = mCentralPoint.x + (mRadius * 0.6) * Math.cos(o) val py3 = mCentralPoint.y - (mRadius * 0.6) * Math.sin(o) canvas.drawLine( mCentralPoint.x.toFloat(), mCentralPoint.y.toFloat(), px3.toFloat(), py3.toFloat(), mMinutePaint ) } /** * 绘制秒指针 */ private fun drawCurrentSecond(canvas: Canvas) { //设置渐变色 val linearGradient = RadialGradient( mCentralPoint.x.toFloat(), mCentralPoint.y.toFloat(), mRadius.toFloat(), Color.parseColor("#1DBBFF"), Color.parseColor("#48F3D0"), Shader.TileMode.CLAMP ) mSecondsPaint.setShader(linearGradient) val second = mCalendar.get(Calendar.SECOND) //靠近圆心的2个坐标,这里增加了30秒是为了得到对立面的角度 val angle1 = -(360 / 60 * (second + 30) - 90) //这里增加正负3个角度的偏差,让秒钟起点的2个坐标有一定的距离,形成三角形的2个点的坐标 val o1 = (angle1 + 3f) * Math.PI / 180 val o2 = (angle1 - 3f) * Math.PI / 180 val radius1 = mRadius * 0.2 val px1 = (mCentralPoint.x) + radius1 * Math.cos(o1) val py1 = (mCentralPoint.y) - radius1 * Math.sin(o1) val px2 = (mCentralPoint.x) + radius1 * Math.cos(o2) val py2 = (mCentralPoint.y) - radius1 * Math.sin(o2) //圆周坐标 //得出秒角度,转成负数是希望以顺时针旋转 val angle = -(360 / 60 * second - 90) val o = angle * Math.PI / 180 val radius = mRadius * 0.95 val px3 = mCentralPoint.x + radius * Math.cos(o) val py3 = mCentralPoint.y - radius * Math.sin(o) //画一个非常长的三角形 val path = Path() path.moveTo(px1.toFloat(), py1.toFloat()) path.lineTo(px2.toFloat(), py2.toFloat()) path.lineTo(px3.toFloat(), py3.toFloat()) path.close() canvas.drawPath(path, mSecondsPaint) } private fun drawOval(canvas: Canvas) { mOvalPaint.color = Color.WHITE canvas.drawOval( mCentralPoint.x.toFloat() - 12f, mCentralPoint.y.toFloat() - 12f, mCentralPoint.x.toFloat() + 12f, mCentralPoint.y.toFloat() + 12f, mOvalPaint ) mOvalPaint.color = Color.parseColor("#3AC0E5") canvas.drawOval( mCentralPoint.x.toFloat() - 8f, mCentralPoint.y.toFloat() - 8f, mCentralPoint.x.toFloat() + 8f, mCentralPoint.y.toFloat() + 8f, mOvalPaint ) } }
End
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17073817.html
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。