I want to do something at home during the weekend. I remember the interview more than a year ago, the company directly sent me an interview question, which asked me to make a simple dial effect, but I didn’t make it at that time, so I didn’t feel shy to go to the interview. Today, I will realize it, and it can be divided into the following steps

Step 1: Draw a simple circle

Step 2: Draw the scale

Step 3: Draw time, minutes, table pointer

Step 4: Draw the current time text

Step 5: Time dynamic display



The first step to draw a circle is easy,

package com.example.clockview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.ImageView;
/ * * * Created by Adminis on 2016/11/6.
* /public class ClockView extends ImageView {
    private static final String TAG = "ClockView";
    private Paint mPaint;
    private int widhth = 200;// Width of the control    private int height = 200;// Height of the control    private int padding = 5;
    public ClockView(Context context) {
        this(context, null);
    }
    public ClockView(Context context.AttributeSet attrs) {
        this(context.attrs.0);
    }
    public ClockView(Context context.AttributeSet attrs, int defStyleAttr) {
        super(context.attrs.defStyleAttr);
        initPaint();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(widhth.height);
    }
    private void initPaint() {
        mPaint = new Paint();
        mPaint.setStrokeWidth(3);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.parseColor("# 666666"));
        mPaint.setAntiAlias(true);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        drawCircle(canvas);
    }
    / * ** draw round     * @param canvas
* /    private void drawCircle(Canvas canvas) {
         mPaint.setStyle(Paint.Style.STROKE);
         canvas.drawCircle(widhth/2.height/2.widhth/2-padding.mPaint);
    }}Copy the code

Effect:



Step 2: Draw the scale

/ * ** Draw scale * @param canvas
* /private void drawScale(Canvas canvas) {
    mPaint.setStyle(Paint.Style.FILL);
    canvas.drawLine(widhth / 2 - padding.padding.widhth / 2 - padding.padding + 4 + 8.mPaint);
}Copy the code

Effect:



But we’re going to draw something like this:



I have to draw 12 of these lines together, so that’s the same thing as the Angle between every two lines is 360/12 is equal to 30 degrees

Analysis as shown in figure:



Rotate the canvas 30 degrees after each thread

/ * ** Draw scale * @param canvas
 * /private void drawScale(Canvas canvas) {
    mPaint.setStyle(Paint.Style.FILL);
    for(int i=0;i<12;i++){
        if(i%3= =0) {// The corresponding line length points are 12, 3, 6, 9            canvas.drawLine(widhth / 2 - padding.padding.widhth / 2 - padding.padding + 4 + 15.mPaint);
        }else{
            canvas.drawLine(widhth / 2 - padding.padding.widhth / 2 - padding.padding + 4 + 8.mPaint);
        }
        canvas.rotate(30.widhth / 2.widhth / 2);
    }}Copy the code

Effect:



The scale is drawn using the canvas’s rotate() method, but the center of the circle is the rotation point

The third step is to draw the hands of the time table

The drawing points to the current time,

/ * ** Draw the timing table pointer * @param canvas
 * /private void drawPointer(Canvas canvas) {
    mCalendar = Calendar.getInstance(a);
    mHour = mCalendar.get(Calendar.HOUR);
    mMinuate = mCalendar.get(Calendar.MINUTE);
    mSecond = mCalendar.get(Calendar.SECOND);
    // Rotation per hour    mDegrees = mHour*30+mMinuate/2;
    mPaint.setColor(Color.BLACK);
    canvas.save();
    canvas.rotate(mDegrees.widhth / 2.widhth / 2);
    canvas.drawLine(widhth / 2.height / 2.widhth / 2.widhth / 2 - mHourLineLen.mPaint);
    canvas.restore();
    / / minute    mPaint.setColor(Color.RED);
    mDegrees = mMinuate*6+mSecond/10;
    canvas.save();
    canvas.rotate(mDegrees.widhth / 2.widhth / 2);
    canvas.drawLine(widhth / 2.height / 2.widhth / 2.widhth / 2 - mHourLineLen.mPaint);
    canvas.restore();
    // Draw the needle    mPaint.setColor(Color.BLUE);
    mDegrees = mSecond*6;
    canvas.save();
    canvas.rotate(mDegrees.widhth / 2.widhth / 2);
    canvas.drawLine(widhth / 2.height / 2.widhth / 2.widhth / 2 - mHourLineLen.mPaint);
    canvas.restore();
}Copy the code

Effect:



Let me explain the value of this mDegrees for example, it’s 21:20 now, but in a 12-hour system it’s 9:20, which is 9*30 degrees =270 degrees. But the hour hand pointing at 9 is definitely not correct, because there are still 20 minutes left. The needle and thread must be pointing between 9 and 10, so the hour is 30 degrees, let’s take 21:30, and the Angle of the hour would be 30*9+30/2=285 degrees which means 60 minutes 1 hour and 1 hour is 30 degrees, That means that every two minutes the hour hand should move, and according to this principle the calculation between minutes and watches is similar,

Another special point to note is that 2 is the hour hand after drawing, which is based on the current hour hand number, such as 9, and then rotated 30*9. However, the canvas uses animation and must be restored to the original one after drawing, otherwise there will be a problem when you draw for a minute. I have encountered this problem

Special changes have been made: class code copy to see the effect:

package com.example.clockview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.ImageView;

import java.util.Calendar;

/ * * * Created by Adminis on 2016/11/6.
* /public class ClockView extends ImageView {
    private static final String TAG = "ClockView";
    private Paint mPaint;
    private int widhth = 200;// Width of the control    private int height = 200;// Height of the control    private int padding = 5;
    private Calendar mCalendar;
    private int mHour;/ / hour    private int mMinuate;/ / minute    private int mSecond;/ / SEC.    private float mDegrees ;// Since the circle is 360 degrees we have 12 scales so it is 360/12    private int mHourLineLen;// time pointer line    private int mMinuateLine;Line / / minute    private int mSecondLine ;/ / table clock line    public ClockView(Context context) {
        this(context, null);
    }
    public ClockView(Context context.AttributeSet attrs) {
        this(context.attrs.0);
    }
    public ClockView(Context context.AttributeSet attrs, int defStyleAttr) {
        super(context.attrs.defStyleAttr);
        initPaint();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(widhth.height);
        mHourLineLen = (int) (widhth/2*0.6);
        mMinuateLine = (int) (widhth/2*0.7);
        mSecondLine = (int) (widhth/2*0.8);
    }
    private void initPaint() {
        mPaint = new Paint();
        mPaint.setStrokeWidth(3);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.parseColor("# 666666"));
        mPaint.setAntiAlias(true);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        drawCircle(canvas);
        drawScale(canvas);
        canvasCenterCircle(canvas);
        drawPointer(canvas);
    }

    / * ** Draw a point in the center of the circle     * @param canvas
     * /    private void canvasCenterCircle(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(widhth / 2.height / 2.5.mPaint);
    }


    / * ** draw round     * @param canvas
     * /    private void drawCircle(Canvas canvas) {
         mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(widhth / 2.height / 2.widhth / 2 - padding.mPaint);
    }
    / * ** Draw scale     * @param canvas
     * /    private void drawScale(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        for(int i=0;i<12;i++){
            if(i%3= =0) {// The corresponding line length points are 12, 3, 6, 9                canvas.drawLine(widhth / 2 - padding.padding.widhth / 2 - padding.padding + 4 + 15.mPaint);
            }else{
                canvas.drawLine(widhth / 2 - padding.padding.widhth / 2 - padding.padding + 4 + 8.mPaint);
            }
            canvas.rotate(30.widhth / 2.widhth / 2);
        }}/ * ** Draw the timing table pointer     * @param canvas
     * /    private void drawPointer(Canvas canvas) {
        mCalendar = Calendar.getInstance(a);
        mHour = mCalendar.get(Calendar.HOUR);
        mMinuate = mCalendar.get(Calendar.MINUTE);
        mSecond = mCalendar.get(Calendar.SECOND);
        // Rotation per hour        mDegrees = mHour*30+mMinuate/2;
        mPaint.setColor(Color.BLACK);
        canvas.save();
        canvas.rotate(mDegrees.widhth / 2.widhth / 2);
        canvas.drawLine(widhth / 2.height / 2.widhth / 2.widhth / 2 - mHourLineLen.mPaint);
        canvas.restore();
        / / minute        mPaint.setColor(Color.parseColor("# 666666"));
        mPaint.setStrokeWidth(5);
        mDegrees = mMinuate*6+mSecond/10;
        canvas.save();
        canvas.rotate(mDegrees.widhth / 2.widhth / 2);
        canvas.drawLine(widhth / 2.height / 2.widhth / 2.widhth / 2 - mMinuateLine.mPaint);
        canvas.restore();
        // Draw the needle        mPaint.setStrokeWidth(2);
        mPaint.setColor(Color.parseColor("# 666666"));
        mDegrees = mSecond*6;
        canvas.save();
        canvas.rotate(mDegrees.widhth / 2.widhth / 2);
        canvas.drawLine(widhth / 2.height / 2.widhth / 2.widhth / 2 - mSecondLine.mPaint);
        canvas.restore();
    }}Copy the code

Effect:



The fourth step is to draw the current time text below the center point:

/ * ** Draw text * @param canvas
 * /private void drawStr(Canvas canvas) {
    mPaint.setTextSize(24);
    StringBuffer sb =  new StringBuffer();
    if(mHour<10){
        sb.append("0").append(String.valueOf(mHour)).append(":");
    }else{
        sb.append(String.valueOf(mHour)).append(":");
    }
    if(mMinuate<10){
        sb.append("0").append(String.valueOf(mMinuate)).append(":");
    }else{
        sb.append(String.valueOf(mMinuate)).append(":");
    }
    if(mSecond<10){
        sb.append("0").append(String.valueOf(mSecond));
    }else{
        sb.append(String.valueOf(mSecond));
    }
    String str = sb.toString();
    int strW = (int) mPaint.measureText(str);
    canvas.drawText(str.widhth / 2 - strW / 2.widhth / 2 + 30.mPaint);
}Copy the code

Effect:



The final step is to use the Handler to refresh the interface every second to dynamically display the time:

The final complete code:

package com.example.clockview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.widget.ImageView;
import java.util.Calendar;
/ * * * Created by Adminis on 2016/11/6.
* /public class ClockView extends ImageView {
    private static final String TAG = "ClockView";
    private Paint mPaint;
    private int widhth = 200;// Width of the control    private int height = 200;// Height of the control    private int padding = 5;
    private Calendar mCalendar;
    private int mHour;/ / hour    private int mMinuate;/ / minute    private int mSecond;/ / SEC.    private float mDegrees ;// Since the circle is 360 degrees we have 12 scales so it is 360/12    private int mHourLineLen;// time pointer line    private int mMinuateLine;Line / / minute    private int mSecondLine ;/ / table clock line    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            invalidate();
        }};
    public ClockView(Context context) {
        this(context, null);
    }
    public ClockView(Context context.AttributeSet attrs) {
        this(context.attrs.0);
    }
    public ClockView(Context context.AttributeSet attrs, int defStyleAttr) {
        super(context.attrs.defStyleAttr);
        initPaint();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(widhth.height);
        mHourLineLen = (int) (widhth/2*0.6);
        mMinuateLine = (int) (widhth/2*0.7);
        mSecondLine = (int) (widhth/2*0.8);
    }
    private void initPaint() {
        mPaint = new Paint();
        mPaint.setStrokeWidth(3);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.parseColor("# 666666"));
        mPaint.setAntiAlias(true);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        drawCircle(canvas);
        drawScale(canvas);
        canvasCenterCircle(canvas);
        drawPointer(canvas);
        drawStr(canvas);
        mHandler.sendEmptyMessage(1);
    }
    / * ** Draw text     * @param canvas
     * /    private void drawStr(Canvas canvas) {
        mPaint.setTextSize(24);
        StringBuffer sb =  new StringBuffer();
        if(mHour<10){
            sb.append("0").append(String.valueOf(mHour)).append(":");
        }else{
            sb.append(String.valueOf(mHour)).append(":");
        }
        if(mMinuate<10){
            sb.append("0").append(String.valueOf(mMinuate)).append(":");
        }else{
            sb.append(String.valueOf(mMinuate)).append(":");
        }
        if(mSecond<10){
            sb.append("0").append(String.valueOf(mSecond));
        }else{
            sb.append(String.valueOf(mSecond));
        }
        String str = sb.toString();
        int strW = (int) mPaint.measureText(str);
        canvas.drawText(str.widhth / 2 - strW / 2.widhth / 2 + 30.mPaint);
    }
    / * ** Draw a point in the center of the circle     * @param canvas
     * /    private void canvasCenterCircle(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(widhth / 2.height / 2.5.mPaint);
    }


    / * ** draw round     * @param canvas
     * /    private void drawCircle(Canvas canvas) {
         mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(widhth / 2.height / 2.widhth / 2 - padding.mPaint);
    }
    / * ** Draw scale     * @param canvas
     * /    private void drawScale(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        for(int i=0;i<12;i++) {
            if (i % 3 = =0) {// The corresponding line length points are 12, 3, 6, 9                canvas.drawLine(widhth / 2 - padding.padding.widhth / 2 - padding.padding + 4 + 15.mPaint);
            }else{
                canvas.drawLine(widhth / 2 - padding.padding.widhth / 2 - padding.padding + 4 + 8.mPaint);
            }
            canvas.rotate(30.widhth / 2.widhth / 2);
        }}/ * ** Draw the timing table pointer     * @param canvas
     * /    private void drawPointer(Canvas canvas) {
        mCalendar = Calendar.getInstance(a);
        mHour = mCalendar.get(Calendar.HOUR);
        mMinuate = mCalendar.get(Calendar.MINUTE);
        mSecond = mCalendar.get(Calendar.SECOND);
        // Rotation per hour        mDegrees = mHour*30+mMinuate/2;
        mPaint.setColor(Color.BLACK);
        canvas.save();
        canvas.rotate(mDegrees.widhth / 2.widhth / 2);
        canvas.drawLine(widhth / 2.height / 2.widhth / 2.widhth / 2 - mHourLineLen.mPaint);
        canvas.restore();
        / / minute        mPaint.setColor(Color.parseColor("# 666666"));
        mPaint.setStrokeWidth(5);
        mDegrees = mMinuate*6+mSecond/10;
        canvas.save();
        canvas.rotate(mDegrees.widhth / 2.widhth / 2);
        canvas.drawLine(widhth / 2.height / 2.widhth / 2.widhth / 2 - mMinuateLine.mPaint);
        canvas.restore();
        // Draw the needle        mPaint.setStrokeWidth(2);
        mPaint.setColor(Color.parseColor("# 666666"));
        mDegrees = mSecond*6;
        canvas.save();
        canvas.rotate(mDegrees.widhth / 2.widhth / 2);
        canvas.drawLine(widhth / 2.height / 2.widhth / 2.widhth / 2 - mSecondLine.mPaint);
        canvas.restore();
    }}Copy the code

Dynamic effect:



Finally finished!



Someone in the group just said that the hour hand and the needle line of the watch at 3 o ‘clock and 15 seconds did not coincide, so I wrote a data by myself and tested it and found that there was really this bug. The reason for this bug is as follows

/ * ** Draw scale * @param canvas
 * /private void drawScale(Canvas canvas) {
    mPaint.setStyle(Paint.Style.FILL);
    for(int i=0;i<12;i++) {
        if (i % 3 = =0) {// The corresponding line length points are 12, 3, 6, 9            canvas.drawLine(widhth / 2-padding .padding.widhth / 2 -padding.padding + 4 + 15.mPaint);
        }else{
            canvas.drawLine(widhth / 2-padding .padding.widhth / 2-padding.padding + 4 + 8.mPaint);
        }
        canvas.rotate(30.widhth / 2.widhth / 2);
    }}

Copy the codeIt’s the X-axis that starts and ends in the wrong position, so I should get rid of the -padding

After the change:

/ * ** Draw scale * @param canvas
 * /private void drawScale(Canvas canvas) {
    mPaint.setStyle(Paint.Style.FILL);
    for(int i=0;i<12;i++) {
        if (i % 3 = =0) {// The corresponding line length points are 12, 3, 6, 9            canvas.drawLine(widhth / 2-padding .padding.widhth / 2 -padding.padding + 4 + 15.mPaint);
        }else{
            canvas.drawLine(widhth / 2-padding .padding.widhth / 2-padding.padding + 4 + 8.mPaint);
        }
        canvas.rotate(30.widhth / 2.widhth / 2);
    }}Copy the code

I’m going to kill the hour hand at 3 and set the seconds at 15:



Thank you for asking questions in the group