Zero, preface,

The other day, I introduced a bunch of Android apis for Canvas, Paint, and Path, and the next step is to use them flexibly

What I have brought today is the drawing of a watch. After this study, I believe you will have a deeper understanding of the layer concept of Canvas. The beauty and ugliness of the table is not the focus of this paper, but the purpose of this paper is to clarify the meaning of save and restore of Canvas


First, preparation

1. Create a new class that inherits from View
public class TolyClockView extends View { public TolyClockView(Context context) { this(context, null); } public TolyClockView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init() {//TODO ()} @override protected void onDraw(Canvas) {super.ondraw (Canvas); / / TODO map}Copy the code
2. Analyze it

This is a common way to customize a View, but few of us have the concept of layers, since we’re all typing in code

In the image below, you start with a layer with the x and y axes at the vertex. If you do not save(), you will always be on this layer and the layer stack will always be one


3. Draw my own coordinate system on this interface :(has been encapsulated as a tool,Attached to the end)

The grid and coordinate system are auxiliary tools and can be drawn a lot, so use Picture to record and initialize in init()

Picture in onDraw is more efficient, the difference is like building a house with a load of bricks versus building a house with one brick

// Private Picture mPictureGrid; Private Point mCoo = new Point(500, 800); // Private Picture mPictureCoo; //init() mPictureGrid = helpdraw.getGrid (getContext()); mPictureCoo = HelpDraw.getCoo(getContext(), mCoo); MMainPaint = new Paint(paint.anti_alias_flag); mMainPaint.setStyle(Paint.Style.STROKE); mMainPaint.setStrokeCap(Paint.Cap.ROUND); / / in the ontouch canvas. DrawPicture (mPictureGrid); canvas.drawPicture(mPictureCoo);Copy the code

Just as the API literally means, draw two images on canvas, grid and coordinate system, as shown below:


2. Draw logic

Now that the preparations are done, it’s time to get down to business

1. The ontouch
canvas.save(); // Save the previous state (equivalent to operating on another layer) Canvas.translate (McOo.x, McOo.y); // Move the canvas to the center of the drawing frame. Canvas.restore (); // Merge into the root layerCopy the code
2. What is the meaning of these two sentences in the picture?

Once canvas.save(), you create a new layer (black dotted line),

Then Canvas.translate (McOo.x, McOo.y) moves the new layer to the right and down. Advantage of the new layer: Only the top layer can operate (for example, the root layer does not move when the canvas moves, which is exactly what we want).

3. Draw a circle with broken outer ring :drawBreakCircle(canvas)
/** * @param canvas */ private void drawBreakCircle(canvas canvas) {for (int I = 0; i < 4; i++) { canvas.save(); Canvas.rotate (90 * I); // Rotate (90 * I); mMainPaint.setStrokeWidth(8); mMainPaint.setColor(Color.parseColor("#D5D5D5")); DrawArc (-350, -350, 350, 350, 10, 70, False, mMainPaint); // In the -350, -350, 350, 350 rectangle area, sweep from 10° to 70° to draw an arc canvas. canvas.restore(); // Restore the previous state (equivalent to merging the layer with the previous layer)}}Copy the code

When I =0:

Because of the save, the previous layer is locked, which is equivalent to operating on another layer

After canvas.restore() is called,

Layer 2 gives its result to layer 1, waves its sleeves and leaves the stack without taking a cloud

When I =1:

Canvas.rotate (90 * 1) rotates the current layer by 90°, as shown in the figure below: Note: I only colored the first quadrant of the coordinate axis, the Canvas layer is an infinite face, and the width and height of the canvas is only limited to display. The key to rotation, translation and scaling lies in the transformation of the coordinate axis, and 90° rotation is equivalent to 90° rotation of the coordinate axis

After canvas.restore() is called,

Layer 2 gives its result to layer 1, waves its sleeves and leaves the stack without taking a cloud

After these two layers, you should know what layers are for.

When you’re done, merge all the layers into root


4. Draw dots

Draw 60 points (small lines) and lengthen each time 5, that is, draw a straight line and rotate the canvas 360/60=6° each time

private void drawDot(Canvas canvas) { for (int i = 0; i < 60; i++) { if (i % 5 == 0) { canvas.save(); canvas.rotate(30 * i); mMainPaint.setStrokeWidth(8); mMainPaint.setColor(ColUtils.randomRGB()); canvas.drawLine(250, 0, 300, 0, mMainPaint); mMainPaint.setStrokeWidth(10); mMainPaint.setColor(Color.BLACK); canvas.drawPoint(250, 0, mMainPaint); canvas.restore(); } else { canvas.save(); canvas.rotate(6 * i); mMainPaint.setStrokeWidth(4); mMainPaint.setColor(Color.BLUE); canvas.drawLine(280, 0, 300, 0, mMainPaint); canvas.restore(); }}}Copy the code


5. Draw the hour hand:
/** * @param canvas */ private void drawH(canvas canvas) {canvas.save(); canvas.rotate(40); mMainPaint.setColor(Color.parseColor("#8FC552")); mMainPaint.setStrokeCap(Paint.Cap.ROUND); mMainPaint.setStrokeWidth(8); canvas.drawLine(0, 0, 150, 0, mMainPaint); canvas.restore(); }Copy the code
6. Drawing a minute hand:
/** * Draw minute hand * @param canvas * @param deg */ private void drawM(canvas canvas) {canvas.save(); canvas.rotate(120); mMainPaint.setColor(Color.parseColor("#87B953")); mMainPaint.setStrokeWidth(8); canvas.drawLine(0, 0, 200, 0, mMainPaint); mMainPaint.setColor(Color.GRAY); mMainPaint.setStrokeWidth(30); canvas.drawPoint(0, 0, mMainPaint); canvas.restore(); }Copy the code
7. Draw the second hand
/** * draw second hand ** @param canvas ** @param deg */ private void drawS(Canvas, float deg) { mMainPaint.setStyle(Paint.Style.STROKE); mMainPaint.setColor(Color.parseColor("#6B6B6B")); mMainPaint.setStrokeWidth(8); mMainPaint.setStrokeCap(Paint.Cap.SQUARE); canvas.save(); canvas.rotate(deg); canvas.save(); canvas.rotate(45); Mmainpath. addArc(-25, -25, 25, 25, 0, 240); mmainPath. addArc(-25, -25, 25, 0, 240); canvas.drawPath(mMainPath, mMainPaint); canvas.restore(); mMainPaint.setStrokeCap(Paint.Cap.ROUND); canvas.drawLine(-25, 0, -50, 0, mMainPaint); mMainPaint.setStrokeWidth(2); mMainPaint.setColor(Color.BLACK); canvas.drawLine(0, 0, 320, 0, mMainPaint); mMainPaint.setStrokeWidth(15); mMainPaint.setColor(Color.parseColor("#8FC552")); canvas.drawPoint(0, 0, mMainPaint); canvas.restore(); }Copy the code


8. Add text
/** * Add text * @param canvas */ private void drawText(canvas) {mMainPaint. SetTextSize (60); mMainPaint.setStrokeWidth(5); mMainPaint.setStyle(Paint.Style.FILL); mMainPaint.setTextAlign(Paint.Align.CENTER); mMainPaint.setColor(Color.BLUE); Canvas. DrawText (" Ⅲ ", 350, 30, mMainPaint); DrawText (" ⅵ ", 0, 350 + 30, mMainPaint); Canvas. DrawText (" Ⅸ - 350, 30, mMainPaint); DrawText (" ⅻ ", 0, -350 + 30, mMainPaint); Typeface myFont = TypeFace.createFromasSet (getContext().getAssets(), "chops.ttf "); mMainPaint.setTypeface(myFont); mMainPaint.setTextSize(70); canvas.drawText("Toly", 0, -150, mMainPaint); }Copy the code

Ok, static effect, now let’s move it


Three, let the table move

1. Display the current time:

The rotation Angle of the table is drawn by each needle yes Canvas.rotate (XXX); Decision,

Then dynamically change the Angle of rotation on the line! Look at this math problem:

11:12:45 seconds, the Angle between the hands of the hour hand, minute hand and second hand and the horizontal line of the center? A: Second hand: 45/60. f * 360-90 Minute hand: 12/60. f * 360-90 + 45/60. f * 1 Hour hand: 11 / 12.f * 360 - 90 + 12 / 60.f * 30 + 45 / 3600.f * 30Copy the code
2. Dynamically update angles: three functions that draw Pointers, plus angles
Calendar calendar = Calendar.getInstance();
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int min = calendar.get(Calendar.MINUTE);
int sec = calendar.get(Calendar.SECOND);

drawS(canvas, sec / 60.f * 360 - 90);
drawM(canvas, min / 60.f * 360 - 90 + sec / 60.f);
drawH(canvas, hour / 12.f * 360 - 90 + min / 60.f * 30 + sec / 3600.f * 30);
Copy the code


3. Now every time I come in, the time will be updated, how to automatically update?

The golden partner of loops: Handler + Timer

Public class ClockActivity extends AppCompatActivity {/** * New Handler */ Handler mHandler = new Handler() {@override public void handleMessage(Message msg) { mView.invalidate(); // Handle: refresh view}}; private View mView; private Timer timer = new Timer(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_toly_clock); ButterKnife.bind(this); mView = findViewById(R.id.id_toly_clock); TimerTask timerTask = new TimerTask() { @Override public void run() { mHandler.sendEmptyMessage(0); // Send a message}}; Timer. Schedule (timerTask, 0, 1000); }}Copy the code

Ok, the end of the scatter flower.


Postscript: Jie wen standard

1. Growth record and Errata of this paper
Program source code The date of note
V0.1 – making The 2018-11-8 Draw a table together with Android native drawing
2. More about me
Pen name QQ WeChat hobby
Zhang Feng Jie te Li 1981462002 zdl1994328 language
My lot My Jane books My CSDN Personal website
3. The statement

1—- This article is originally written by Zhang Fengjie, please note if reproduced

2—- welcome the majority of programming enthusiasts to communicate with each other 3—- personal ability is limited, if there is something wrong welcome to criticize and testify, must be humble to correct 4—- see here, I thank you here for your love and support


Appendix: Grid + coordinate system drawing tool:

1.HelpDraw
/** * Author: Zhang Feng Jiete: <br/> * Time: 2018/11/50005:8:43 <br/> * Email: [email protected]<br/> * Description: Public class HelpDraw {/** * Get screen size */ public static Point getWinSize(Context Context) {Point Point = new Point(); Utils.loadWinSize(context, point); return point; } public static Picture getGrid(Context Context) {return getGrid(getWinSize(Context)); } public static Picture getCoo(Context Context, Point COO) {return getCoo(COO, getWinSize(Context)); } /** * @param winSize */ private static Picture getGrid(Point winSize) {Picture Picture = new Picture();  Canvas recording = picture.beginRecording(winSize.x, winSize.y); // Initialize the grid brush. Paint Paint = new Paint(); paint.setStrokeWidth(2); paint.setColor(Color.GRAY); paint.setStyle(Paint.Style.STROKE); New float[]{visible, invisible}, offset paint. SetPathEffect (new DashPathEffect(new float[]{10, 5}, 0)); recording.drawPath(HelpPath.gridPath(50, winSize), paint); return picture; } private static Picture getCoo(Point COO, @param winSize) Point winSize) { Picture picture = new Picture(); Canvas recording = picture.beginRecording(winSize.x, winSize.y); // Initialize the grid brush. Paint Paint = new Paint(); paint.setStrokeWidth(4); paint.setColor(Color.BLACK); paint.setStyle(Paint.Style.STROKE); New float[]{visible length, invisible length}, offset paint. SetPathEffect (null); DrawPath (helppath.coopath (COO, winSize), paint); DrawLine (winsize.x, coo.y, winsize.x-40, coo.y-20, paint); recording.drawLine(winSize.x, coo.y, winSize.x - 40, coo.y + 20, paint); X, winSize. Y, coo. X-20, winSize. Y-40, paint); recording.drawLine(coo.x, winSize.y, coo.x + 20, winSize.y - 40, paint); DrawText4Coo (Recording, COO, winSize, paint); return picture; } /** * Draw text for the frame ** @param canvas * @param COO * @param winSize * @param Paint */ Private static void DrawText4Coo (Canvas Canvas, Point COO, Point winSize, Paint Paint) {// Draw Paint. SetTextSize (50); canvas.drawText("x", winSize.x - 60, coo.y - 40, paint); canvas.drawText("y", coo.x - 40, winSize.y - 60, paint); paint.setTextSize(25); For (int I = 1; i < (winSize.x - coo.x) / 50; i++) { paint.setStrokeWidth(2); canvas.drawText(100 * i + "", coo.x - 20 + 100 * i, coo.y + 40, paint); paint.setStrokeWidth(5); canvas.drawLine(coo.x + 100 * i, coo.y, coo.x + 100 * i, coo.y - 10, paint); } for (int I = 1; i < coo.x / 50; i++) { paint.setStrokeWidth(2); canvas.drawText(-100 * i + "", coo.x - 20 - 100 * i, coo.y + 40, paint); paint.setStrokeWidth(5); canvas.drawLine(coo.x - 100 * i, coo.y, coo.x - 100 * i, coo.y - 10, paint); } for (int I = 1; i < (winSize.y - coo.y) / 50; i++) { paint.setStrokeWidth(2); canvas.drawText(100 * i + "", coo.x + 20, coo.y + 10 + 100 * i, paint); paint.setStrokeWidth(5); canvas.drawLine(coo.x, coo.y + 100 * i, coo.x + 10, coo.y + 100 * i, paint); } for (int I = 1; i < coo.y / 50; i++) { paint.setStrokeWidth(2); canvas.drawText(-100 * i + "", coo.x + 20, coo.y + 10 - 100 * i, paint); paint.setStrokeWidth(5); canvas.drawLine(coo.x, coo.y - 100 * i, coo.x + 10, coo.y - 100 * i, paint); }}}Copy the code
2.HelpPath
/** * Author: Zhang Feng Jiete Lie <br/> * Time: 2018/11/50005:8:05 <br/> * Email: [email protected]<br/> * Description: */ public class HelpPath {/** * Draw grid: note that only path can draw dotted lines ** @param step small square side length * @param winSize Screen size */ public static Path gridPath(int step, Point winSize) { Path path = new Path(); for (int i = 0; i < winSize.y / step + 1; i++) { path.moveTo(0, step * i); path.lineTo(winSize.x, step * i); } for (int i = 0; i < winSize.x / step + 1; i++) { path.moveTo(step * i, 0); path.lineTo(step * i, winSize.y); } return path; @param winSize @param winSize @param winSize @return @param winSize @param winSize @return @param winSize */ public static Path cooPath(Point COO, Point winSize) { Path path = new Path(); Var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var path.lineTo(winSize.x, coo.y); Var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var var path.lineTo(coo.x - winSize.x, coo.y); // veto (); // veto (); // Veto (); path.lineTo(coo.x, coo.y - winSize.y); // veto (); // veto (); // Veto (); path.lineTo(coo.x, winSize.y); return path; }}Copy the code
3.Utils
Public class Utils {/** * Get screen height ** @param CTX Context * @param winSize Screen size */ public static void loadWinSize(Context) ctx, Point winSize) { WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); if (wm ! = null) { wm.getDefaultDisplay().getMetrics(outMetrics); } winSize.x = outMetrics.widthPixels; winSize.y = outMetrics.heightPixels; }}Copy the code