Respect the original, welcome to reprint, reproduced please indicate: the FROM GA_studio blog.csdn.net/tianjian459…

As mentioned above, Canvas can be roughly divided into three categories:

1. Methods related to layer saving and rollback, such as save and restore;

2. Scale, rotate, clipXXX and other methods to operate the canvas;

3. DrawXXX and a series of drawing related methods;

The drawBitmap method was mainly discussed in the previous section, and an example of a chestnut floating planet, in that example, the planet has big and small, need to move, sometimes may need to rotate or miscut, with these requirements, We need to use the methods related to Canvas translate, scale, rotate and skew. Translation, scaling, rotation and miscut sound so familiar that we often deal with these words when doing some basic animations. Now let’s take a look at what happens when you combine these guys with a Canvas;

Of course, there are two basic concepts that need to be clarified before you look at it:

1. The top left corner of the Canvas is (0,0);

2. Based on the upper left corner, X is positive, Y is positive, and vice versa;

Canvas. Translate () – Canvas translation:

First let’s draw a 400 X 400 red rectangle on the canvas

canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);Copy the code

Now a red rectangle appears in the upper left corner of the canvas (shaded blue for clarity) with a size of 400 X 400 and looks like this:



Let’s play with Canvas. Translate ()

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLUE);
        canvas.translate(100, 100);
        canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
    }Copy the code



Take a look at the effect:

You can see that even though you are drawing the same rectangle, it has moved 100px to the right and 100px to the bottom of the canvas.

In this case, what happens if we translate (100,100) on our canvas and draw the same rectangle? Does it overlap the previous rectangle? Let’s wait and see:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLUE);
        canvas.translate(100, 100);
        canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        canvas.translate(100, 100);
        canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
    }Copy the code



In effect, the two translate translate pieces are superimposed, with the canvas offset (200,200) by the time the second rectangle is drawn;

Canvas. Translate (), canvas. Translate (), canvas.

Let’s first find a scale picture from the Internet for reference:

From the diagram, the elements of the scale are: outer frame, scale lines (different numerical scale lines vary in length), numbers

So all we need to do is draw the above elements separately in onDraw:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制外框
        drawOuter(canvas);
        // 绘制刻度线
        drawLines(canvas);
        // 绘制数字
        drawNumbers(canvas);
    }Copy the code

Let’s first simple analysis, the scale has an outer frame, the outer frame distance left and right have a certain margin, the first and last scale line distance from the border also have a certain margin, the distance between the rest of the scale line is the same, some other special scale line length is different;

With the above analysis, one by one, we first draw frame, frame is a rectangular, only need to determine the size and location of the border, and then use the canvas. The drawRect () can be drawn:

Let’s first define a few required data. For screen adaptation, all data are DP:

Private static final int DIVIDING_RULE_HEIGHT = 70; Private static final int DIVIDING_RULE_MARGIN_LEFT_RIGHT = 10; Private static final int FIRST_LINE_MARGIN = 5; private static final int FIRST_LINE_MARGIN = 5; Private static final int DEFAULT_COUNT = 9;Copy the code

Then convert the above data into the corresponding pixel value:

    private void initData() {
        mDividRuleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                DIVIDING_RULE_HEIGHT, mResources.getDisplayMetrics());
        mHalfRuleHeight = mDividRuleHeight / 2;

        mDividRuleLeftMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                DIVIDING_RULE_MARGIN_LEFT_RIGHT, mResources.getDisplayMetrics());
        mFirstLineMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                FIRST_LINE_MARGIN, mResources.getDisplayMetrics());

    }
Copy the code

With the above data, it can be determined that the Rect of the outer border is:

mOutRect = new Rect(mDividRuleLeftMargin, top, mTotalWidth - mDividRuleLeftMargin,
                mRuleBottom);Copy the code

Next look at the scale line drawing, according to the centimeter can be calculated in the middle of the number of cells, according to the centimeter occupied screen width and the number of cells can be calculated for each cell screen width:

mLineInterval = (mTotalWidth - 2 * mDividRuleLeftMargin - 2 * mFirstLineMargin)
                / (DEFAULT_COUNT * 10 - 1);
Copy the code

Given the width of each grid, we just need to keep moving the canvas to the right as we draw the scale:

@param canvas */ private void drawLines(canvas) {canvas.save(); canvas.translate(mLineStartX, 0); int top = mMaxLineTop; for (int i = 0; i <= 5="=" 10="=" default_count="" *="" 10; ="" i++)="" {="" if="" (i="" %="" 0)="" top="mMaxLineTop;" }="" else="" canvas.drawline(0,="" mrulebottom,="" 0,="" top,="" mlinepaint); ="" canvas.translate(mlineinterval,="" 0); ="" canvas.restore(); <="" "<="" "> Because there are three kinds of scales on the scale, we also make corresponding processing. The scale line with integer multiples of 10 is the longest, the scale line with integer multiples of 5 is of medium length, and the rest are shorter.

At this point, the scale effect drawn is:



At this time the basic appearance of the scale will come out, the corresponding text we are interested in can add their own;

All roads lead to Rome. Can we use canvas. Translate? For example, we can calculate the position of each scale line according to the I value in the for loop, and then draw directly. Well, canvas. Translate () that's all;

Canvas. Scale () - Canvas scale:

For Scale, Android provides the following two interfaces:

    /**
     * Preconcat the current matrix with the specified scale.
     *
     * @param sx The amount to scale in X
     * @param sy The amount to scale in Y
     */
    public native void scale(float sx, float sy);

    /**
     * Preconcat the current matrix with the specified scale.
     *
     * @param sx The amount to scale in X
     * @param sy The amount to scale in Y
     * @param px The x-coord for the pivot point (unchanged by the scale)
     * @param py The y-coord for the pivot point (unchanged by the scale)
     */
    public final void scale(float sx, float sy, float px, float py) {
        translate(px, py);
        scale(sx, sy);
        translate(-px, -py);
    }Copy the code

Scale (float sx, float sy); scale(float sx, float sy);

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.BLUE); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); Canvas. Scale (0.5 0.5 f, f); mPaint.setColor(Color.YELLOW); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); }Copy the code

We scaled the canvas 0.5 times in both x and y directions, using the default reference point (origin 0,0). The effect is as follows:

Scale (float sx,float sy, float px,float py): scale(sx,float sy, float px,float py)

Scale (float sx, float sy); scale(float sx, float sy);

translate(px, py);
scale(sx, sy);
translate(-px, -py);Copy the code

So you move the canvas px,py, scale, scale and then you move the canvas back to the original reference point;

Let's draw the same rectangle on the basis of the previous one, with x and y scaled by 0.5 times and the scaling center being the center of the rectangle:

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.BLUE); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); // Save the canvas state canvas.save(); Canvas. Scale (0.5 0.5 f, f); mPaint.setColor(Color.YELLOW); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); // Canvas state rolls back canvas.restore(); Canvas. Scale (0.5 f, 0.5 f, 200, 200); mPaint.setColor(Color.BLACK); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); }Copy the code

Take a look at the effect:

The effect is to put a nail in the center of the rectangle and scale it;

Based on the Android implementation above, we can actually use the following code to achieve the same effect:

// Move the canvas to the center of the rectangle. Translate (200, 200); // Scale canvas. Scale (0.5f, 0.5f); Canvas. Translate (-200, -200); mPaint.setColor(Color.BLACK); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);Copy the code

Now that we know how to scale the canvas, let's do a small example based on canvas.scale() :



The above is a static picture of visual error found on the Internet, and we simulated the above effect;

The idea is very simple:

1. Draw a square as wide as the screen.

2. Scale the canvas from the center of the square as a reference point;

3. Draw the original square during scaling;

Note: Each drawing must use canvas.save() and canvas.restore() to lock and roll back the canvas to avoid subsequent drawing (a separate story later).

Initialize the brush first, note that the brush should be set to hollow:

/** * private void initPaint() {mPaint = new Paint(paint.anti_alias_flag); // Set the brush to hollow mPaint. SetStyle (style.stroke); // Set the paint Color to mPaint. SetColor (color.black); // Set the brush width mPaint. SetStrokeWidth (mLineWidth); }Copy the code

Then I scale the canvas in a loop while drawing the original square:

Private void drawSquare(canvas) {for (int I = 0; i < TOTAL_SQUARE_COUNT; I++) {// save the canvas canvas.save(); float fraction = (float) i / TOTAL_SQUARE_COUNT; Canvas. Scale (fraction, fraction, mHalfWidth, mHalfHeight); canvas.drawRect(mSquareRect, mPaint); // Rollback canvas. Restore (); }}Copy the code

Let's take a look at the effect of drawing:

In fact, the final effect is a little bit different from the one found online. Due to the scale of the canvas, the smaller the brush width is, the smaller the brush width is. In the original image, everything is the same width, but it seems that the effect is better after the brush width is scaled. .

Canvas.rotate () - Canvas rotation:

Canvas.rotate () and canvas.scale() can be compared. If you understand canvas.scale(), canvas.rotate() is very simple and useful.

Canvas.rotate () rotates the canvas. Similar to canvas.scale(), canvas.rotate() has two methods:

/** * Preconcat the current matrix with the specified rotation. * * @param degrees The amount to rotate, in degrees */ public native void rotate(float degrees); /** * Preconcat the current matrix with the specified rotation. * * @param degrees The amount to rotate, in degrees * @param px The x-coord for the pivot point (unchanged by the rotation) * @param py The y-coord for the pivot  point (unchanged by the rotation) */ public final void rotate(float degrees, float px, float py) { translate(px, py); rotate(degrees); translate(-px, -py); }Copy the code

The rotate method is rotate based on the base point, and the rotate method is rotate based on the base point. The rotate method is rotate based on the base point, and the rotate method is rotate based on the base point.

Let's rotate the rectangle in the upper left corner by how many degrees? Let's play 90 degrees first;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLUE);
        canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        mPaint.setColor(Color.YELLOW);
        canvas.rotate(90);
        canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
    }
Copy the code

So what we're expecting is a rotating rectangle on the screen, which is a nice yellow rectangle, so let's take a look;

What about the yellow rectangle?

Since the reference point is the origin, we have rotated the rectangle directly by 90 degrees, so we have rotated the rectangle out of the screen, but of course we can't see it, so we have reduced the Angle to 45 degrees:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLUE);
        canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
        mPaint.setColor(Color.YELLOW);
        canvas.rotate(45);
        canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);
    }
Copy the code

At this point we can clearly see that the yellow rectangle is the result of the red rectangle being rotated 45 degrees about the origin (0,0);



Let's change the rotation reference point to the center of the rectangle:

Canvas. Rotate (45200200);Copy the code

You can see that the yellow rectangle is now the result of the red rectangle being rotated around the center:



Rotate (float degrees,float px,float py) and canvas. Rotate (float degrees,float px,float py).

translate(px, py);
rotate(degrees);
translate(-px, -py);Copy the code

Rotate () with canvas. Rotate () we have a small example of an alarm clock dial:

Clock dial is actually similar to the scale, but one is drawn on a straight line, one is drawn on a circle, in the final analysis is to determine a position to draw the scale line;

Since it is a circle, the simplest way is to draw the line at 12 o 'clock of the alarm clock and draw it to the corresponding circle by rotation of canvas. Let's do it together:

The whole circle is 360 degrees, and there is an integral time scale every 30 degrees. There are four short scales between the integral scale and the scale, dividing into five small segments, each of which is 6 degrees. With these analyses, we can use the following code to draw:

/** @canvas */ private void drawLines(canvas) {for (int I = 0; i <= 6="=" 30="=" 360; ="" i++)="" {="" if="" (i="" %="" 0)="" mlinebottom="mLineTop" +="" mlonglineheight; ="" mlinepaint.setstrokewidth(mlinewidth); ="" }="" else="" mshortlineheight; ="" mlinepaint.setstrokewidth(mhalflinewidth); ="" canvas.save(); ="" canvas.rotate(i,="" mhalfwidth,="" mhalfheight); ="" canvas.drawline(mlineleft,="" mlinetop,="" mlineleft,="" mlinebottom,="" mlinepaint); ="" canvas.restore(); ="" <="" pre="">

The effect is as follows:



The overall code is as follows:

/** * @author AJian */ public class RotateClockView extends View {private static final int LONG_LINE_HEIGHT = 35. private static final int SHORT_LINE_HEIGHT = 25; private Paint mCirclePaint, mLinePaint; private DrawFilter mDrawFilter; private int mHalfWidth, mHalfHeight; Private int mCircleLineWidth, mHalfCircleLineWidth; Private int mLineWidth, mHalfLineWidth; Private int mLongLineHeight; private int mLongLineHeight; // private int mShortLineHeight; Private int mLineLeft, mLineTop; Private int mLineBottom; private int mLineBottom; Private int mFixLineHeight; public RotateClockView(Context context) { super(context); mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); mCircleLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()); mHalfCircleLineWidth = mCircleLineWidth; mLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics()); mHalfLineWidth = mLineWidth / 2; mFixLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics()); mLongLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, LONG_LINE_HEIGHT, getResources().getDisplayMetrics()); mShortLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SHORT_LINE_HEIGHT, getResources().getDisplayMetrics()); initPaint(); } private void initPaint() { mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mCirclePaint.setColor(Color.RED); // Set the brush to hollow McIrclepaint.setstyle (style.stroke); / / set the brush width mCirclePaint setStrokeWidth (mCircleLineWidth); mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mLinePaint.setColor(Color.RED); mLinePaint.setStyle(Style.FILL_AND_STROKE); / / set the brush width mLinePaint setStrokeWidth (mLineWidth); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { canvas.setDrawFilter(mDrawFilter); super.onDraw(canvas); // Draw the dial drawCircle(canvas); // Draw scale drawLines(canvas); } /** * @canvas */ private void drawLines(canvas) {for (int I = 0; i <= 6="=" 30="=" 360; ="" i++)="" {="" if="" (i="" %="" 0)="" mlinebottom="mLineTop" +="" mlonglineheight; ="" mlinepaint.setstrokewidth(mlinewidth); ="" }="" else="" mshortlineheight; ="" mlinepaint.setstrokewidth(mhalflinewidth); ="" canvas.save(); ="" canvas.rotate(i,="" mhalfwidth,="" mhalfheight); ="" canvas.drawline(mlineleft,="" mlinetop,="" mlineleft,="" mlinebottom,="" mlinepaint); ="" canvas.restore(); = = "" * *" "* = =" "" "painted dial @ param = "canvas" = "" private =" void "=" "methods like drawcircle (canvas =" canvas ") = "" canvas.drawcircle(mhalfwidth,="" mhalfheight,="" mhalfwidth="" -="" mhalfcirclelinewidth,="" mcirclepaint); ="" @override="" protected="" onsizechanged(int="" w,="" int="" h,="" oldw,="" oldh)="" super.onsizechanged(w,="" oldh); 2 = ""; ="" mhalfheight="h" mlineleft="mHalfWidth" mhalflinewidth; ="" mlinetop="mHalfHeight" mfixlineheight; ="" }<="" pre="">

Similarly, students who are interested can fill in the text themselves;

Canvas. Skew () - Wrong cut of canvas:

/** * Preconcat the current matrix with the specified skew. * * @param sx The amount to skew in X * @param sy The amount  to skew in Y */ public native void skew(float sx, float sy);Copy the code

This method only needs to understand two parameters:

Float sx: Tilts the canvas in the x direction by the corresponding Angle, sx is the tan value of the tilting Angle;

Float sy: Tilt the canvas in the y direction by the corresponding Angle, sy is the tan value of the tilt Angle;

Notice, this is all about the tan of the inclination, so if we're going to tilt it 45 degrees in the X direction, the tan of 45 is 1;

Let's see if we tilt it 45 degrees on the X-axis:

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.BLUE); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); // Canvas. Skew (1, 0); mPaint.setColor(0x8800ff00); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); }Copy the code

The effect is as follows:



Tilt it 45 degrees on the y axis:

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.BLUE); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); // Canvas. Skew (0, 1); mPaint.setColor(0x8800ff00); canvas.drawRect(new Rect(0, 0, 400, 400), mPaint); }Copy the code

The effect is as follows:






So much for Canvas translate, scale, rotate and skew. These methods are not complex, and flexible use can often solve many seemingly complex problems in drawing, so it is important to understand. And when we see the relevant effects, we can make timely and appropriate associations.

Of course, Matrix(which will be discussed separately later) is often used to operate Canvas to achieve the same effect. If you want to see an example, you can refer to a gorgeous dynamic analysis and implementation of loading!

Source download link


Copy the codeCopy the codeCopy the code