赞
踩
在安卓开发中,当系统自带的View已经无法满足项目需求时,就要自定义View。在Android中是没有与钟表有关的View,因此我们制作一个简单的钟表View,这样就可以在其他项目中进行使用。
自定义钟表具有表盘,表盘上有12个刻度,有时针、分针、秒针,和家里面的石英表样式相同,用于显示时间会比数码表更加有内涵。
只要是21年之后从Android Studio官网
下载的AS,都可以运行该App。因为高版本IDE向下兼容,只需要修改Java环境。
onMeasure
方法被重写用于决定自定义View的最终大小。这个过程考虑了父布局传递过来的宽度和高度的具体规格(spec)。MeasureSpec
类提供了一种方式来理解这些规格,包括它们的模式和大小。
模式有三种:
UNSPECIFIED
:父布局没有限制子View的大小,子View可以选择任何大小。EXACTLY
:父布局指定了一个确切的大小,子View应该尽可能地匹配这个大小。AT_MOST
:父Layout设定了一个最大值,子View的大小不能超过这个值。在代码中,首先检查了宽度和高度的规格模式。
EXACTLY
,则取两者中的较小值作为View的大小。EXACTLY
,则取高度的值作为View的大小。EXACTLY
,则取宽度的值作为View的大小。EXACTLY
,则取一个默认的值400作为View的大小。最后,调用setMeasuredDimension(int, int)
方法来设置View的大小。这个方法接受两个参数:第一个是View的宽度,第二个是View的高度。由于在本例中,View是一个圆形,所以不管宽度还是高度,最终的大小都会被设置为相同的值,从而保证View是完美圆形的。
这种方法确保了View在不同设备和屏幕方向上具有一致的外观和大小,前提是父布局至少为View指定了一个方向上的确切大小。如果宽度和高度都没有具体的规格,那么View将会有一个默认的400px大小。这可能会导致View在布局中超出预期的范围,因此在实际应用中,可能需要对这种情况进行额外的处理。
//显示的尺寸,和使用时传入的宽高相关,因为整体为圆形 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //获取传入宽高的模式 int wmode = MeasureSpec.getMode(widthMeasureSpec); int hmode = MeasureSpec.getMode(heightMeasureSpec); int wsize = MeasureSpec.getSize(widthMeasureSpec); int hsize = MeasureSpec.getSize(heightMeasureSpec); //判断模式,获取最终显示的尺寸 int size = 400; if (wmode == MeasureSpec.EXACTLY) { if (hmode == MeasureSpec.EXACTLY) { size = Math.min(wsize, hsize); } else { size = wsize; } } else { if (hmode == MeasureSpec.EXACTLY) { size = hsize; } else { size = 400; } } //将测量好的值设置给宽高 setMeasuredDimension(size, size); }
重写onDraw
方法来绘制时钟的表盘和指针。在绘制之前,先创建一个Paint
对象并根据需要设置其样式、颜色、宽度等属性。设置Paint对象的抗锯齿模式为true,这样可以让时钟的数字和指针看起来更加平滑。
Paint paint = new Paint();
//设置抗锯齿
paint.setAntiAlias(true);
//获取在布局当中设置的自定义属性,设置给view
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColokView);
int color = typedArray.getColor(R.styleable.ColokView_clockColor, Color.BLACK);
//设置画笔的颜色
paint.setColor(color);
时钟想要显示当前时间,所以必须获取系统的准确时间,并将其分解为小时、分钟和秒。定义一个getTime
方法获取系统时间。首先,创建了一个 Calendar
类的实例,通过调用静态方法 getInstance()
来初始化该实例,这样可以确保 calendar
对象包含了调用该方法时设备上的当前日期和时间。
接下来,使用 calendar
对象来获取当前的时间:
hours = calendar.get(Calendar.HOUR);
这行代码获取了当前的小时数,但是请注意,这是基于12小时制的,所以它返回的小时数范围是0(午夜12点)到11(中午12点)。minutes = calendar.get(Calendar.MINUTE);
这行代码获取了当前的分钟数,范围是0到59。seconds = calendar.get(Calendar.SECOND);
这行代码获取了当前的秒数,范围也是0到59。 //获取当前时间的方法
public void getTime() {
Calendar calendar = Calendar.getInstance();
hours = calendar.get(Calendar.HOUR);
minutes = calendar.get(Calendar.MINUTE);
seconds = calendar.get(Calendar.SECOND);
}
下面讲解onDraw
方法的具体实现,它负责在View上绘制表盘和指针。
onDraw
方法,这个方法是View类的一部分,用于在View上进行绘制。super.onDraw(canvas);
确保父类的绘制逻辑得到执行。STROKE
),这样绘制的图形只有边缘有颜色。FILL
,以View中心为圆心,以10像素为半径绘制一个圆。Canvas
状态。canvas.rotate()
方法根据角度绘制刻度,这里有一个问题,因为每次旋转后都应该绘制新的刻度,但代码中却重复绘制了相同的刻度,这可能是一个错误。handler.sendEmptyMessageDelayed(1, 1000);
),这部分代码的意图可能是让时钟每秒移动一次,但它被放置在了绘制刻度的循环中,这也是一个逻辑错误。Canvas
状态。Canvas
。Canvas
状态。Canvas
状态。Canvas
。Canvas
状态。//显示的内容就在onDraw方法中进行绘制 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //设置空心 paint.setStyle(Paint.Style.STROKE); //设置内边距 setPadding(20, 20, 20, 20); //绘制外层大圈 paint.setStrokeWidth(8);//设置线条宽度 canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - 20, paint); //绘制内层大圆 paint.setStrokeWidth(4); canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2 - 30, paint); //绘制表中间轴心 paint.setStyle(Paint.Style.FILL); canvas.drawCircle(getWidth() / 2, getHeight() / 2, 10, paint); //绘制表的刻度12个,通过旋转画布实现 for (int i = 1; i <= 12; i++) { //保存画布的状态 canvas.save(); //旋转到指定的角度 canvas.rotate(30 * i, getWidth() / 2, getHeight() / 2); canvas.drawLine(getWidth() / 2, 40, getWidth() / 2, 60, paint); //恢复旋转之前的状态 canvas.restore(); handler.sendEmptyMessageDelayed(1, 1000); } //绘制时针,1h=30°,1m=0.5° paint.setStrokeWidth(8); canvas.save(); //旋转画布,旋转的度数由当前时间决定 canvas.rotate(30 * hours + 0.5f * minutes, getWidth() / 2, getHeight() / 2); canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - getHeight() / 5, paint); canvas.restore(); //绘制分针,1min=6° paint.setStrokeWidth(5); canvas.save(); canvas.rotate(6 * minutes, getWidth() / 2, getHeight() / 2); canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - getHeight() / 4, paint); canvas.restore(); //绘制秒针,1s=6° paint.setStrokeWidth(3); canvas.save(); canvas.rotate(6 * seconds, getWidth() / 2, getHeight() / 2); canvas.drawLine(getWidth() / 2, getHeight() / 2, getWidth() / 2, getHeight() / 2 - getHeight() / 3, paint); canvas.restore(); }
为了让指针走起来,我们定义了一个Handler
的匿名子类,并覆写了其handleMessage
方法。Handler
通常用于处理线程间通信,特别是在Android开发中,它常用来处理UI线程(主线程)和后台线程之间的消息传递。
在handleMessage
方法中,首先检查传入的Message
对象的what
字段是否等于1。如果是,表示该消息需要被处理:
调用getTime()
方法来获取当前的时间。
调用invalidate()
方法强制重新绘制View。invalidate()
方法会告诉系统该View的部分或全部区域已经变得不再有效,需要重绘。调用此方法后,系统将安排onDraw
方法在适当的时候被再次调用。
使用handler.sendEmptyMessageDelayed(1, 1000);
来设定一个定时器。这行代码的意思是,它会让当前的消息处理器(handler
)在1000毫秒(即1秒)后,再次向自己发送一个what
值为1的空Message
对象。这样就创建了一个每秒重复执行一次的循环,用于不断更新时钟时间并刷新界面。
通过Handler
在Android的主线程上每秒更新并重绘时钟界面,从而模拟了一个动态的时钟效果。
Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
//重新获取时间
getTime();
//重新绘制界面
invalidate();
handler.sendEmptyMessageDelayed(1, 1000);
}
}
};
总的来说,本次自定义钟表项目可以学习到编程技巧,并且加深对Canvas API的理解。大家可以考虑添加一些定制选项,如不同的表盘样式、颜色或字体,以便用户可以根据自己的喜好来个性化他们的时钟;也可以考虑添加一些高级功能,如秒表、闹钟或世界时钟,以扩展应用程序的实用性。简单的项目掌握透了就不简单,期待大家在此基础上制作更多的创新钟表!
♻️下面两种方式都可以获取源代码 |
---|
1️⃣ 点击直接下载 Android Studio 自定义钟表 |
2️⃣关注公众号《 萌新加油站 》,后台回复: 钟表 |
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/586509 推荐阅读 相关标签 Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。 |
---|