Android自定义View——绘制一个会动的时钟

Posted David-Kuper

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义View——绘制一个会动的时钟相关的知识,希望对你有一定的参考价值。

一、效果图

二、整体思路以及流程

时钟可以分为几个独立的部分:

  • 外圆—— 时钟的外环圆
  • 刻度—— 包括刻度线和刻度值
  • 指针—— 包括时针、分针、秒针

按照绘制的流程也可以按照外圆、刻度、指针这样由外到内的顺序绘制。

Step1: 绘制外圆

这是最简单的一步。只需要指定半径、圆心坐标之后即可绘制外圆。

Step2:绘制刻度以及刻度值

一般来说,时钟都是12小时制的,因此一般总共有60条刻度线(包括整点刻度、和非整点刻度)。计算每一条刻度线的首尾坐标,从正午12点刻度开始,每5个刻度线绘制时刻度以及刻度值(即12小时制的值),其余绘制分刻度。

小技巧:绘制刻度线的时候,由于android系统采用的坐标系原点为视图的左上角,这样使得计算每个刻度线的首尾坐标变得复杂。但如果每一次绘制完一个刻度线就将坐标系旋转刻度间夹角度数的话,那么每一次绘制都等于在坐标轴上进行,那么就会使得坐标的计算及其简化,绘制过程变得简单。

Step3:绘制指针
绘制时针比较繁琐一些。首先,考虑Android坐标系原点在左上角的原因,计算时针的坐标相对麻烦,因此需要将坐标系原点平移到圆心,简化计算;其次,秒针、分针、时针的指向的角度(相对于12点)确定牵扯到进位的问题;再次,需要将相对于12点的角度转换然为相对于Android坐标系X轴的角度,然后,计算指针坐标;最后,画线。

下面列出各个步骤:
(1)将坐标原点平移到圆心。

(2)计算时针、分针、秒针偏移角(相对于12点,数学直角坐标系中的y轴)。首先,需要计算出秒针的偏移量(此处以后的偏移量是相对于正午12点来说的);其次,计算分针偏移量(考虑到秒针偏移量);最后,计算时针偏移量(考虑到分针偏移量)。

(3)将坐标系逆时针旋转90度,计算得到相对于Android坐标系X轴的偏移角(这样过后Android坐标系的X、Y轴相当于原数学直角坐标系的Y、X轴,两轴替换)。

(4)计算秒针、分针、时针的首尾坐标。

(5)绘制指针。

三、ShowYouMyCode

/**
 * Created by David on 2017/1/14.
 */

public class ClockView extends View 
    private Date time;
    private float centerX, centerY, radius;//圆心坐标以及半径
    private float width, height; //视图宽高
    private float lengthOfHourScale; // 时刻度线长度
    private float lengthOfMinuteScale; // 分刻度线长度
    private float lengthOfHourHand;  // 时针长度
    private float lengthOfMinuteHand;  // 分针长度
    private float lengthOfSecondHand;  // 秒针长度
    /**
     * 各种画笔
     */
    private Paint paintCircle, paintScaleHour, paintScaleMinute, paintHourHand, paintSecondHand, paintMinuteHand;
    private boolean is24HourSystem; // 小时制(24、12)
    private int currentHour;
    private int currentMinute;

    public ClockView(Context context) 
        super(context);
        initView(context);
    

    public ClockView(Context context, AttributeSet attrs) 
        super(context, attrs);
        initView(context);
    

    public ClockView(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
        initView(context);
    

    /**
     *初始化画笔、刻度长度、指针长度及时间
     * @param context
     */
    public void initView(Context context) 
        //外圆画笔
        paintCircle = new Paint();
        paintCircle.setStyle(Paint.Style.STROKE);
        paintCircle.setAntiAlias(true);
        paintCircle.setStrokeWidth(5f);
        paintCircle.setColor(Color.RED);

        //时刻度画笔
        paintScaleHour = new Paint();
        paintScaleHour.setStyle(Paint.Style.STROKE);
        paintScaleHour.setAntiAlias(true);
        paintScaleHour.setStrokeWidth(3.5f);
        paintScaleHour.setColor(Color.RED);

        //分刻度画笔
        paintScaleMinute = new Paint();
        paintScaleMinute.setStyle(Paint.Style.STROKE);
        paintScaleMinute.setAntiAlias(true);
        paintScaleMinute.setStrokeWidth(1.5f);
        paintScaleMinute.setTextSize(10);
        paintScaleMinute.setColor(Color.RED);

        //时针画笔
        paintHourHand = new Paint();
        paintHourHand.setStyle(Paint.Style.STROKE);
        paintHourHand.setAntiAlias(true);
        paintHourHand.setStrokeWidth(5f);
        paintHourHand.setColor(Color.RED);

        //分针画笔
        paintMinuteHand = new Paint();
        paintMinuteHand.setStyle(Paint.Style.STROKE);
        paintMinuteHand.setAntiAlias(true);
        paintMinuteHand.setStrokeWidth(3f);
        paintMinuteHand.setColor(Color.RED);

        //秒针画笔
        paintSecondHand = new Paint();
        paintSecondHand.setStyle(Paint.Style.STROKE);
        paintSecondHand.setAntiAlias(true);
        paintSecondHand.setStrokeWidth(1.5f);
        paintSecondHand.setColor(Color.RED);

        //系统时间
        time = new Date(System.currentTimeMillis());
        is24HourSystem = false; // 12小时制
        lengthOfHourHand = 200f;  //时针长度
        lengthOfMinuteHand = 300f; //分针长度
        lengthOfSecondHand = 400f; //秒针长度

        lengthOfHourScale = 60f;  //时刻度长度
        lengthOfMinuteScale = 30f; //分刻度长度

        currentHour = 0;
        currentMinute = 0;
    

    @Override
    protected void onFinishInflate() 
        Log.e(getClass().getSimpleName(), "onFinishInflate()");
        super.onFinishInflate();
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
        Log.e(getClass().getSimpleName(), "onMeasure()");
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    

    /**
     * 此处确定宽高、圆心、半径
     * @param changed
     * @param left
     * @param top
     * @param right
     * @param bottom
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) 
        Log.e(getClass().getSimpleName(), "onLayout()");
        super.onLayout(changed, left, top, right, bottom);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
        centerX = width / 2;
        centerY = height / 2;
        radius = Math.min(width / 2, height / 2);
        Log.e(getClass().getSimpleName(), "height = " + height + "    width = " + width + "    centerY = " + centerY + "    centerX = " + centerX);
    

    @Override
    protected void onAttachedToWindow() 
        Log.e(getClass().getSimpleName(), "onAttachedToWindow()");
        super.onAttachedToWindow();
    

    @Override
    protected void onDraw(Canvas canvas) 
        super.onDraw(canvas);
        int hours = is24HourSystem == true ? 24 : 12;
        int divisions = is24HourSystem == true ? 120 : 60; // 分割线数量

        //Step 1: 画外圆
        drawCircle(centerX, centerY, radius, canvas);
        //Step 2: 画刻度线及其刻度值
        drawScaleValue(centerX, centerY, radius, divisions, canvas);
        //Setp 3:画时针分针
        drawHand(centerX, centerY, radius, divisions, canvas);
        invalidate();
    

    /**
     * 描绘时钟外圆
     *
     * @param x
     * @param y
     * @param radius
     */
    protected void drawCircle(float x, float y, float radius, Canvas canvas) 
        canvas.drawCircle(x, y, radius, paintCircle);
    

    /**
     * 描绘刻度值
     *
     * @param x
     * @param y
     * @param radius
     */
    protected void drawScaleValue(float x, float y, float radius, int divisions, Canvas canvas) 
        int divisionPercent = 360 / divisions;
        for (int i = 0; i < divisions; i++) 
            if (i % 5 == 0)  //时刻度
                int hourValue = i / 5 == 0 ? 12 : i / 5;
                String degreeStr = String.valueOf(hourValue);
                canvas.drawLine(centerX, centerY - radius, centerX, centerY - radius + lengthOfHourScale, paintHourHand);
                canvas.drawText(degreeStr, centerX - paintScaleHour.measureText(degreeStr) / 2, centerY - radius + lengthOfHourScale + paintScaleHour.measureText(degreeStr) / 2 + 10, paintScaleHour);
             else  //分刻度
                canvas.drawLine(centerX, centerY - radius, centerX, centerY - radius + lengthOfMinuteScale, paintScaleMinute);
            
            //通过旋转画布简化坐标运算
            canvas.rotate(divisionPercent, centerX, centerY);
        
    


    /**
     * 描绘时针分针
     *
     * @param x
     * @param y
     * @param radius
     */
    protected void drawHand(float x, float y, float radius, int divisions, Canvas canvas) 
//        Log.e(getClass().getSimpleName(),"drawHand() begin");
        time = new Date(System.currentTimeMillis());
        canvas.save(); //保存之前的画布
        // 将坐标原点平移到圆心
        canvas.translate(x, y);

        //Step 1: 计算时针、分针、秒针偏移弧度(相对于12点)
        float divisionPercent = 360.0f / divisions;
        float secondPercent = divisionPercent * (time.getSeconds() % 60.0f);  //秒指针偏移量(相对于12点的偏移量)
        float minutePercentRelativeToHour = (secondPercent / 360.0f) * divisionPercent + divisionPercent * time.getMinutes(); //分指针偏移量(相对于时针的偏移量)
        float hourPercent = (time.getMinutes() / 60.0f) * divisionPercent * 5 + divisionPercent * (divisions / 12) * (time.getHours() % 12.0f); // 时针偏移量(相对于12点的偏移量)
        float minutePercent = minutePercentRelativeToHour + divisionPercent * (divisions / 12) * (time.getHours() % 12.0f);
        Log.e(getClass().getSimpleName(),"drawHand()   hourPercent = " + hourPercent + "    minutePercent = " + minutePercent + "    secondPercent = " + secondPercent + "    minutePercentRelativeToHour = " + minutePercentRelativeToHour);

        //Step 2: 将时钟角度转换至Android系统绘制坐标系的角度(顺时针旋转90度,X、Y坐标互换)
        //        并转换成弧度
        float AndroidHourAngel = hourPercent - 90;
        float AndroidMinuteAngel = minutePercent - 90;
        float AndroidSecondAngel = secondPercent - 90;
        //将角度转换成弧度
        double HourRadians = Math.toRadians(AndroidHourAngel);       //小时弧度
        double MinuteRadians = Math.toRadians(AndroidMinuteAngel);   //分钟弧度
        double SecondRadians = Math.toRadians(AndroidSecondAngel);   //秒钟弧度
        Log.e(getClass().getSimpleName(),"drawHand()   AndroidHourAngel = " + AndroidHourAngel + "    AndroidMinuteAngel = " + AndroidMinuteAngel + "    AndroidSecondAngel = " + AndroidSecondAngel);

        //Step 3: 计算坐标
        float stopHourX = (float) (lengthOfHourHand * Math.cos(HourRadians));
        float stopHourY = (float) (lengthOfHourHand * Math.sin(HourRadians));
        float stopMinuteX = (float) (lengthOfMinuteHand * Math.cos(MinuteRadians));
        float stopMinuteY = (float) (lengthOfMinuteHand * Math.sin(MinuteRadians));
        float stopSecondX = (float) (lengthOfSecondHand * Math.cos(SecondRadians));
        float stopSecondY = (float) (lengthOfSecondHand * Math.sin(SecondRadians));
        Log.e(getClass().getSimpleName(),"drawHand()  stopHourX = " + stopHourX + "   stopHourY = " + stopHourY + "   stopMinuteX = " + stopMinuteX + "   stopMinuteY = " + stopMinuteY + "   stopSecondX = " + stopSecondX + "   stopSeconY = " + stopSecondY);

        //Step 4: 画线
        canvas.drawLine(0, 0, stopHourX, stopHourY, paintHourHand); // 画时针
        canvas.drawLine(0, 0, stopMinuteX, stopMinuteY, paintMinuteHand); // 画分针
        canvas.drawLine(0, 0, stopSecondX, stopSecondY, paintSecondHand); // 画秒钟

        canvas.restore();
    

    /**
     * 重置时间
     *
     * @param time
     */
    protected void reset(Date time) 

    

    protected void verifyTime() 

    

四、总结

绘制时钟的时候将时钟拆分为外圆、刻度、指针三个组成部分,使得绘制的流程变得清晰,便于管理和修改;在绘制图形的时候进行坐标系的变换以简化计算复杂度。

以上是关于Android自定义View——绘制一个会动的时钟的主要内容,如果未能解决你的问题,请参考以下文章

Android 自定义时钟控件 时针分针秒针的绘制这一篇就够了

利用canvas来绘制一个会动的图画

我的Android进阶之旅Android自定义View实现一个游戏摇杆,来实现监听遥动的方向或者监听摇动的角度的功能

Android自定义View初步

android绘制时钟,canvas学习

android绘制时钟,canvas学习