About this article: this article was originally published in my CSDN blog (by the image watermark can be found), sorting out the past blog process, found that the summary was very careful, so it will be migrated here, I hope to help you in the custom View, 💗

The introduction

Android custom View application is very extensive, recently visited Github is accidentally found a Demo feeling very well written, COMBINED with the content of this project, I tell you how to draw the clock dial, also can deepen their understanding of the custom View, involves a lot of content, we slowly absorb.


Final effect:

Before we start, let me show you the final result


now

So let’s build this View first

  1. First, we define a custom View called ClockView that inherits from the View class.
  2. Then, in the /res/values directory, create the attrs file and define some attributes as follows
<?xml version="1.0" encoding="utf-8"? >
<resources>
    <declare-styleable name="ClockView">
        <attr name="clock_backgroundColor" format="color" />
        <attr name="clock_lightColor" format="color" />
        <attr name="clock_darkColor" format="color" />
        <attr name="clock_textSize" format="dimension" />
    </declare-styleable>

</resources>
Copy the code

Preparation for drawing the outer hour ring

The hour circle consists of an outer circle and a four-hour number, so what we need is clear.

  • We first need a Paint object to draw text,
  • You also need another Paint object to draw the circle.

Override constructor:

    /* Dark color, arc, scale line, hour, gradient start color */
    private int mDarkColor;
    /* Hour text font size */
    private float mTextSize;
    private Paint mTextPaint;
    private Paint mCirclePaint;

    public ClockView(Context context) {
        super(context);
    }

    public ClockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClockView, 0.0);
        mDarkColor = ta.getColor(R.styleable.ClockView_clock_darkColor, Color.parseColor("#80ffffff"));
        mTextSize = ta.getDimension(R.styleable.ClockView_clock_textSize, DensityUtils.sp2px(context, 14));
        ta.recycle();
        // ANTI_ALIAS_FLAG draws smoothly without stumbling
        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setColor(mDarkColor);
        // Center the text
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setTextSize(mTextSize);

        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePaint.setColor(mDarkColor);
        // Official: Geometry and text drawn with this style will be stroked, respecting the fields related to strokes on the drawing.
        // In plain English, don't paint the whole sector, just stroke the outer edge
        mCirclePaint.setStyle(Paint.Style.STROKE);
        mCirclePaint.setStrokeWidth(mCircleStrokeWidth);// Stroke width

    }
Copy the code

Don’t forget to rewrite the onMeasure method to measure the size of a MeasureSpec. Refer to the Custom View article for details on how to measure the size of a MeasureSpec.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getMeasureResult(widthMeasureSpec), getMeasureResult(heightMeasureSpec));
    }

    private int getMeasureResult(int measureSpec){
        int defaultSize = 800;
        int size = MeasureSpec.getSize(measureSpec);
        int mode = MeasureSpec.getMode(measureSpec);
        switch (mode){
            case MeasureSpec.UNSPECIFIED:
                return defaultSize;
            case MeasureSpec.AT_MOST:
                return Math.max(defaultSize, size);
            case MeasureSpec.EXACTLY:
                return size;
            default:
                returndefaultSize; }}Copy the code

Start drawing the outer ring

As we know, for drawing circles and ellipses, we often need to set a boundary rectangle with RectF before drawing. Rect if you’re drawing text.

So to draw the outer ring, first define a RectF variable to draw the ring, and then define a Rect variable to draw the text.

Note that the mCanvas drawing class is an argument in onDraw, and we save it in onDraw

   // Measure text size
    private Rect mTextRect = new Rect();
    private RectF mCircleRectF = new RectF();
    /* Hour circle line width */
    private float mCircleStrokeWidth = 4;

    /** * Draw the outer circle of time 12, 3, 6, 9 text and 4 arcs */
    private void drawOutSideArc(a) {
        String[] timeList = new String[]{"12"."3"."6"."9"};
        // Calculate the height of the number
        mTextPaint.getTextBounds(timeList[0].0, timeList[0].length(), mTextRect);// Return a rectangle to mTextRect.
        mCircleRectF.set(mTextRect.width() / 2 + mCircleStrokeWidth / 2.// Draw a small outer rectangle and draw a circle inside the rectangle
                mTextRect.height() / 2 + mCircleStrokeWidth / 2,
                getWidth() - mTextRect.width() / 2 - mCircleStrokeWidth / 2,
                getHeight() - mTextRect.height() / 2 - mCircleStrokeWidth / 2);
        mCanvas.drawText(timeList[0], getWidth() / 2, mCircleRectF.top + mTextRect.height() / 2, mTextPaint);// Get the boundary values by RectF, because the vertices are written in the upper right, so shift down
        mCanvas.drawText(timeList[1], mCircleRectF.right, getHeight() / 2 + mTextRect.height() / 2, mTextPaint);
        mCanvas.drawText(timeList[2], getWidth() / 2, mCircleRectF.bottom + mTextRect.height() / 2, mTextPaint);
        mCanvas.drawText(timeList[3], mCircleRectF.left, getHeight() / 2 + mTextRect.height() / 2, mTextPaint);
        // Draw 4 arcs connecting numbers
        for (int i = 0; i < 4; i++) {
            // Draw four arcs
            mCanvas.drawArc(mCircleRectF, 5 + 90 * i, 80.false, mCirclePaint); }}Copy the code

Next, we override the onDraw() method and, within the onDraw() method, call the above method to draw the circle

    private Canvas mCanvas;
    @Override
    protected void onDraw(Canvas canvas) {
        mCanvas = canvas;
        drawOutSideArc();
    }
Copy the code

Let’s run it and see what it looks like

In the drawing process, the McIrectf object that controls our circle is bounded by the entire control size, so the reason is clear. So we just set the mCircleRectF object to be a square.

Overrides the onSizeChanged() method to make sure it draws a circle

Package positive drawing is circular if:

  1. Make sure RectF cuts a square
  2. So to ensure that RextF is a square, we need to know the distance between the four sides of the square and the control boundary
  3. That is we need to calculate four integer variables: 1. MPaddingLeft | 2. MPaddingTop | 3. MPaddingRight | 4. MPaddingBottom
    private float mRadius;
    /* Add a default padding value to prevent camera clock rotation from exceeding the view size */
    private float mDefaultPadding;
    private float mPaddingLeft;
    private float mPaddingTop;
    private float mPaddingRight;
    private float mPaddingBottom;// The above 4 values are measured in onSizechanged()
    
    @Override
    protected void onSizeChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        mRadius = Math.min(l - getPaddingLeft() - getPaddingRight(),
                t - getPaddingTop() - getPaddingBottom()) / 2;// The length of each pointer
        mDefaultPadding = 0.12 f * mRadius;
        mPaddingLeft = mDefaultPadding + l / 2 - mRadius + getPaddingLeft();// How far is the clock from the left boundary
        mPaddingRight = mDefaultPadding + l / 2 - mRadius + getPaddingRight();// How far is the clock from the right boundary
        mPaddingTop = mDefaultPadding + t / 2 - mRadius + getPaddingTop();// How far is the clock from the upper boundary
        mPaddingBottom = mDefaultPadding + t / 2 - mRadius + getPaddingBottom();// How far is the clock from the bottom boundary
    }
Copy the code

For the radius of the circle, mRadius, we’re going to take half of the length and width of the control, and then there’s another case where if the control has padding then if the knowledge has padding, whatever the padding is, The radius of the control is always half of the width of the control. This makes the padding out of the control and makes it look less human, so the real radius should be the width of the control, minus two padding values, as follows:

mRadius = Math.min(w – getPaddingLeft() – getPaddingRight(), h – getPaddingTop() – getPaddingBottom()) / 2;

So what does mDefaultPadding do? How about we put it in the mountains to see the effect:


Preparation for drawing scale lines

Before we start drawing, we need to prepare a few tools,

  1. First, a Paint object is necessary,
  2. And then for the user’s convenience, we define a color, expose the Settings,
  3. Finally we need an int to set the length of the scale
    /* The scale line length */
    private float mScaleLength;
    /* Scale brush */
    private Paint mScaleLinePaint;
    /* Background color */
    private int mBackgroundColor;

    public ClockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClockView, 0.0);
        mBackgroundColor = ta.getColor(R.styleable.ClockView_clock_backgroundColor, Color.parseColor("#237EAD"));
        mDarkColor = ta.getColor(R.styleable.ClockView_clock_darkColor, Color.parseColor("#80ffffff"));
        mTextSize = ta.getDimension(R.styleable.ClockView_clock_textSize, DensityUtils.sp2px(context, 14));
        ta.recycle();
        .
        .
        .
        mScaleLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mScaleLinePaint.setStyle(Paint.Style.STROKE);
        mScaleLinePaint.setColor(mBackgroundColor);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        .
        .
        .
        mScaleLength = 0.12 f * mRadius;// Determine the scale length according to the scale
        mScaleLinePaint.setStrokeWidth(0.012 f * mRadius);// The width of the scale circle
    }
Copy the code

Start drawing scale lines

On the contrary, it is very simple to draw the country morning. For us, it is best to divide it into 360 parts in 60 minutes and 60 minutes. However, due to the small screen of the mobile phone, it will be too dense at first and become round:

So here, we divide 360 degrees into 200 pieces,

  1. 360/200 = 1.8 f
  2. When drawing, we rotated the Canvas Angle by 1.8F without drawing an edge
  3. Starting point: Each time we start at the top of the artboard, move a Padding down and add the height of the mTextRect, which is the height of the oclock text, and then add the length of the scale line to separate the scale lines from the arc so they don’t stick together
  4. End point: one more scale line is required at the starting point of the pen
    /** * Draw a gradient rendered light and dark gradient arc, rotate as you redraw, and cover it with a scale line of background color */
    private void drawScaleLine(a) {
        mCanvas.save();
        // Draw the background color scale line
        for (int i = 0; i < 100; i++) {
            mCanvas.drawLine(getWidth() / 2, mPaddingTop + mScaleLength + mTextRect.height() / 2,
                    getWidth() / 2, mPaddingTop + 2 * mScaleLength + mTextRect.height() / 2, mScaleLinePaint);
            mCanvas.rotate(1.8 f, getWidth() / 2, getHeight() / 2);
        }
        mCanvas.restore();
    }
Copy the code

You’re done

Project Demo address: github.com/FishInWater…

If you have any mistakes, please point them out in the comments section. Thank you very much

Have fun programming!