Android Studio实现简单的自定义钟表

马肤
这是懒羊羊

项目目录

  • 一、项目概述
  • 二、开发环境
  • 三、详细设计
    • 3.1、尺寸设置
    • 3.2、绘制表盘和指针
    • 3.3、动态效果
    • 四、运行演示
    • 五、总结展望
    • 六、源码获取

      一、项目概述

      在安卓开发中,当系统自带的View已经无法满足项目需求时,就要自定义View。在Android中是没有与钟表有关的View,因此我们制作一个简单的钟表View,这样就可以在其他项目中进行使用。

      自定义钟表具有表盘,表盘上有12个刻度,有时针、分针、秒针,和家里面的石英表样式相同,用于显示时间会比数码表更加有内涵。

      二、开发环境

      只要是21年之后从Android Studio官网下载的AS,都可以运行该App。因为高版本IDE向下兼容,只需要修改Java环境。

      Android Studio实现简单的自定义钟表,在这里插入图片描述,词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,没有,程序,li,第1张

      三、详细设计

      3.1、尺寸设置

      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);
              }
          

          3.2、绘制表盘和指针

          重写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上绘制表盘和指针。

            1. 首先,覆盖了onDraw方法,这个方法是View类的一部分,用于在View上进行绘制。
            2. 调用super.onDraw(canvas);确保父类的绘制逻辑得到执行。
            3. 设置画笔的风格为空心(STROKE),这样绘制的图形只有边缘有颜色。
            4. 设置View的内边距为20像素。
            5. 绘制外层大圈,设置线条宽度为8像素,以View中心为圆心,以View宽度的一半减去20像素为半径绘制一个圆。
            6. 绘制内层大圆,设置线条宽度为4像素,以View中心为圆心,以View宽度的一半减去30像素为半径绘制一个圆。
            7. 绘制时钟的中心点小圆,设置填充样式为FILL,以View中心为圆心,以10像素为半径绘制一个圆。
            8. 循环12次,绘制时钟的12个刻度。每次循环中:
              • 保存当前的Canvas状态。
              • 使用canvas.rotate()方法根据角度绘制刻度,这里有一个问题,因为每次旋转后都应该绘制新的刻度,但代码中却重复绘制了相同的刻度,这可能是一个错误。
              • 发送一个空消息延迟1秒(通过handler.sendEmptyMessageDelayed(1, 1000);),这部分代码的意图可能是让时钟每秒移动一次,但它被放置在了绘制刻度的循环中,这也是一个逻辑错误。
              • 绘制时针:
                • 设置画笔宽度为8像素。
                • 保存Canvas状态。
                • 根据当前小时数和分钟数计算出的角度旋转Canvas。
                • 绘制时针,从View中心向上40像素处开始到60像素处结束。
                • 恢复Canvas状态。
                • 绘制分针:
                  • 设置画笔宽度为5像素。
                  • 保存Canvas状态。
                  • 根据当前分钟数计算出的角度旋转Canvas。
                  • 绘制分针,从View中心向上2/3的高度处开始向下2/3的高度处结束。
                  • 恢复Canvas状态。
                  • 绘制秒针与上述分针的逻辑基本相同,只是画笔宽度改为3像素,角度计算改为每秒钟6度。
            	//显示的内容就在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 
                        //保存画布的状态
                        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();
                }
            
                    @Override
                    public void handleMessage(@NonNull Message msg) {
                        super.handleMessage(msg);
                        if (msg.what == 1) {
                            //重新获取时间
                            getTime();
                            //重新绘制界面
                            invalidate();
                            handler.sendEmptyMessageDelayed(1, 1000);
                        }
                    }
                };
            

文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复:表情:
评论列表 (暂无评论,0人围观)

还没有评论,来说两句吧...

目录[+]

取消
微信二维码
微信二维码
支付宝二维码