Android实现简单时钟View的方法

通过Canvas的平移与旋转简化绘图逻辑是一个非常有用的技巧,下面的时钟view就是利用这个方法完成的,省去了使用三角函数计算坐标的麻烦。

package com.example.swt369.simpleclock;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.Toast;

import java.util.Calendar;

/**
 * Created by swt369 on 2017/8/20.
 */

public class ClockView extends View {
  //时钟各个部件的长度
  private float mScaleLengthLong;
  private float mScaleLengthShort;
  private float mTickLengthHour;
  private float mTickLengthMinute;
  private float mTickLengthSecond;
  //view的宽和高
  private float mWidth;
  private float mHeight;
  //时钟半径
  private float mRadius;

  //绘制各部件时用的Paint
  private Paint mPaintScaleLong;
  private Paint mPaintScaleShort;
  private Paint mPaintOutline;
  private Paint mPaintNum;
  private Paint mPaintTickHour;
  private Paint mPaintTickMinute;
  private Paint mPaintTickSecond;

  private ViewTreeObserver.OnPreDrawListener onPreDrawListener;
  public ClockView(final Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);

    onPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
      //获取view宽高并计算各个部件的长度
      @Override
      public boolean onPreDraw() {
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        mRadius = Math.min(mWidth,mHeight) / 2 * 0.95f;
        mScaleLengthLong = mRadius * 0.1f;
        mScaleLengthShort = mRadius * 0.05f;
        mTickLengthHour = mRadius * 0.3f;
        mTickLengthMinute = mRadius * 0.45f;
        mTickLengthSecond = mRadius * 0.6f;
        return true;
      }
    };
    getViewTreeObserver().addOnPreDrawListener(onPreDrawListener);

    //点击显示具体时间
    setOnClickListener(new OnClickListener() {
      @Override
      public void onClick(View v) {
        Calendar calendar = Calendar.getInstance();
        String time = String.format("当前时间:%02d:%02d:%02d",
            calendar.get(Calendar.HOUR_OF_DAY),calendar.get(Calendar.MINUTE),calendar.get(Calendar.SECOND));
        Toast.makeText(context,time, Toast.LENGTH_SHORT).show();
      }
    });

    //初始化所有Paint对象
    initializePaints();
  }

  @Override
  protected void onDraw(Canvas canvas) {
    if(mWidth == 0 || mHeight == 0){
      return;
    }
    if(onPreDrawListener != null){
      getViewTreeObserver().removeOnPreDrawListener(onPreDrawListener);
      onPreDrawListener = null;
    }
    //绘制时钟
    drawClock(canvas);
    //一秒后重绘
    postInvalidateDelayed(1000);
  }

  private void drawClock(Canvas canvas) {
    //保存原始状态
    canvas.save();

    //将坐标系原点移到中心,并逆时针旋转90度。完成后x轴朝上。
    canvas.translate(mWidth / 2,mHeight / 2);
    canvas.rotate(-90);

    //画外围轮廓
    canvas.drawCircle(0,0,mRadius, mPaintOutline);

    //画刻度
    for(int i = 0 ; i < 12 ; i++){
      String num = String.valueOf(i == 0 ? 12 : i);
      if(i % 3 == 0){
        canvas.drawLine(mRadius,0,mRadius - mScaleLengthLong,0, mPaintScaleLong);
      }else {
        canvas.drawLine(mRadius,0,mRadius - mScaleLengthShort,0, mPaintScaleShort);
      }
      canvas.drawText(num,mRadius - mScaleLengthLong - mPaintNum.measureText(num) * 2,0, mPaintNum);

      //顺时针旋转30度
      canvas.rotate(30);
    }

    Calendar calendar = Calendar.getInstance();

    //画时针
    int hour = calendar.get(Calendar.HOUR);
    canvas.save();
    canvas.rotate(hour * 30);
    canvas.drawLine(0,0,mTickLengthHour,0,mPaintTickHour);
    canvas.restore();

    //画分针
    int minute = calendar.get(Calendar.MINUTE);
    canvas.save();
    canvas.rotate(minute * 6);
    canvas.drawLine(0,0,mTickLengthMinute,0,mPaintTickMinute);
    canvas.restore();

    //画秒针
    int second = calendar.get(Calendar.SECOND);
    canvas.save();
    canvas.rotate(second * 6);
    canvas.drawLine(0,0,mTickLengthSecond,0,mPaintTickSecond);
    canvas.restore();

    //恢复原始状态
    canvas.restore();
  }

  private void initializePaints(){
    mPaintScaleLong = new Paint();
    mPaintScaleLong.setAntiAlias(true);
    mPaintScaleLong.setStrokeWidth(5);

    mPaintScaleShort = new Paint();
    mPaintScaleShort.setAntiAlias(true);
    mPaintScaleShort.setStrokeWidth(3);

    mPaintOutline = new Paint();
    mPaintOutline.setStyle(Paint.Style.STROKE);
    mPaintOutline.setAntiAlias(true);
    mPaintOutline.setStrokeWidth(5);

    mPaintNum = new Paint();
    mPaintNum.setTextSize(30);

    mPaintTickHour = new Paint();
    mPaintTickHour.setAntiAlias(true);
    mPaintTickHour.setStrokeWidth(6);

    mPaintTickMinute = new Paint();
    mPaintTickMinute.setAntiAlias(true);
    mPaintTickMinute.setStrokeWidth(4);

    mPaintTickSecond = new Paint();
    mPaintTickSecond.setAntiAlias(true);
    mPaintTickSecond.setStrokeWidth(2);
  }
}

代码注释已经比较详细了,下面把比较重要的部分再说明一下:

//保存原始状态
canvas.save();

//省略中间部分...

//恢复原始状态
canvas.restore();

在绘图开始时调用canvas.save(),可以保存下未经任何平移、旋转操作的原始画布状态。在所有绘图工作完成后调用canvas.restore(),可以恢复到上一次保存的状态(类似进出栈的感觉)。

//将坐标系原点移到中心,并逆时针旋转90度。完成后x轴朝上。
canvas.translate(mWidth / 2,mHeight / 2);
canvas.rotate(-90);

画布的平移与旋转可能比较抽象,可以想象成坐标系的平移与旋转。调用canvas.translate(dx,dy)相当于将坐标原点向x,y方向移动了dx,dy的距离,调用canvas.rotate(degree)相当于坐标系顺时针旋转了degree°。

//画刻度
for(int i = 0 ; i < 12 ; i++){
  String num = String.valueOf(i == 0 ? 12 : i);
  if(i % 3 == 0){
    canvas.drawLine(mRadius,0,mRadius - mScaleLengthLong,0, mPaintScaleLong);
  }else {
    canvas.drawLine(mRadius,0,mRadius - mScaleLengthShort,0, mPaintScaleShort);
  }
  canvas.drawText(num,mRadius - mScaleLengthLong - mPaintNum.measureText(num) * 2,0, mPaintNum);

  //顺时针旋转30度
  canvas.rotate(30);
}

根据i值决定是画大刻度(90°的倍数)还是小刻度。每次画完一条之后将坐标系顺时针旋转30°,这样保证每次画的刻度线的坐标不变。

//一秒后重绘
postInvalidateDelayed(1000);

作用是在1000ms(1s)后再次执行View的draw流程,产生指针运动的效果。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • android高仿小米时钟(使用Camera和Matrix实现3D效果)

    继续练习自定义View..毕竟熟才能生巧.一直觉得小米的时钟很精美,那这次就搞它~这次除了练习自定义View,还涉及到使用Camera和Matrix实现3D效果. 一个这样的效果,在绘制的时候最好选择一个方向一步一步的绘制,这里我选择由外到内.由深到浅的方向来绘制,代码步骤如下: 1.首先老一套~新建attrs.xml文件,编写自定义属性如时钟背景色.亮色(用于分针.秒针.渐变终止色).暗色(圆弧.刻度线.时针.渐变起始色),新建MiClockView继承View,重写构造方法,获取自定义属性值

  • Android ActionBar制作时钟实例解析

    本文实例为大家分享了Android ActionBar制作时钟的具体代码,供大家参考,具体内容如下 1. MainActivity.java   package com.example.days19actionbar07custom; import com.example.days19actionbar07custom.R; import android.app.Activity; import android.os.Bundle; import android.view.Menu; impor

  • Android画个时钟玩玩

    先看下最终的效果 开始实现 新建一个ClockView集成View public class ClockView extends View { } 先重写onMeasure方法,这里要先说一下View的测量模式,一共有三种: 1.EXACTLY 即精确值模式,当我们将控件的layout_width属性或layout_height属性指定为具体数值时,比如android:layout_width="100dp",或者指定为math_parent属性时(占据父View的大小),系统使用的是

  • android实现widget时钟示例分享

    一.在 AndroidManifest.xml文件中配置Widgets: 复制代码 代码如下: <manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.example.widget"    android:versionCode="1"    android:versionName="1.0" >   

  • Android多功能时钟开发案例(实战篇)

    上一篇为大家介绍的是Android多功能时钟开发基础内容,大家可以回顾一下,Android多功能时钟开发案例(基础篇) 接下来进入实战,快点来学习吧. 一.时钟 在布局文件中我们看到,界面上只有一个TextView,这个TextView的作用就是显示一个系统的当前时间,同时这个时间还是一秒一秒跳的,要实现一秒一秒的跳就需要我们每隔一秒就要刷新一下,同时我们这里还考虑了切换到另一个Tab的时候,这个时间就不跳动了,这样就会减少这个对系统的占用,考虑到了这点我们在这里用到了Handler,通过han

  • Android获取设备CPU核数、时钟频率以及内存大小的方法

    本文实例讲述了Android获取设备CPU核数.时钟频率以及内存大小的方法.分享给大家供大家参考,具体如下: 因项目需要,分析了一下 Facebook 的开源项目 - Device Year Class. Device Year Class 的主要功能是根据 CPU核数.时钟频率 以及 内存大小 对设备进行分级.代码很简单,只包含两个类: DeviceInfo -> 获取设备参数, YearClass -> 根据参数进行分级. 下表是 Facebook 公司提供的分级标准,其中 Year 栏表

  • Android仿小米时钟效果

    我在一个[博客] android高仿小米时钟(使用Camera和Matrix实现3D效果)上面看到了小米时钟实现.特别感兴趣.就认真的看了一遍.并自己敲了一遍.下面说下我自己的理解和我的一些改进的地方效果真的特别棒就发布了自己的时钟应用. 先上图(电脑没有gif截图软件.大家凑合看.哪个软件好也可以给我推荐下) 话不多说,首先自定义控件XimiClockView继承view  并做一些初始化的操作 看到的漂亮时钟图片我自己画的效果图(以后妈妈再也不用担心我迟到了) public XimiCloc

  • Android 仿日历翻页、仿htc时钟翻页、数字翻页切换效果

    废话不多说,效果图: 自定义控件找自网络,使用相对简单,具体还没有来得及深入研究,只是先用笨方法大概实现了想要的效果,后续有空会仔细研究再更新文章, 本demo切换方法是用的笨方法,也就是由新数字和旧数字相比较来切换数字变换的,大致使用方法如下: //获取输入框中的数字 int newNumber = Integer.parseInt(etInput.getText().toString()); //获取个.十.百位数字 int nbai = newNumber / 100; int nshi

  • Android通过Path实现搜索按钮和时钟复杂效果

    在Android中复杂的图形的绘制绝大多数是通过path来实现,比如绘制一条曲线,然后让一个物体随着这个曲线运动,比如搜索按钮,比如一个简单时钟的实现: 那么什么是path呢! 定义:path  就是路径,就是图形的路径的集合,它里边包含了路径里边的坐标点,等等的属性.我们可以获取到任意点的坐标,正切值. 那么要获取Path上边所有点的坐标还需要用到一个类,PathMeasure; PathMesure: PathMeasure是一个用来测量Path的类,主要有以下方法: 构造方法 公共方法 可

  • Android多功能时钟开发案例(基础篇)

    本文我们进入Android多功能时钟开发实战学习,具体的效果可以参考手机上的时钟,内容如下 首先我们来看一看布局文件layout_main.xml 整个布局: <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/container" android:layout_width="match_parent" androi

随机推荐