Android UI programming-using SurfaceViewt and Canvas for dynamic clock, androidcanvas
Overview:
Many times we want to write some controls similar to clock and compass, but we cannot find a suitable Demo. I think you may simply film it. When Android has a painter like Canvas and Paint, it is indeed embarrassing to choose to use images. Next I will use step by step to implement a custom clock to explain this problem.
Error example:
Here I have an "error" example. The error here should be enclosed in double quotation marks because it is not a real error, but sometimes it is inappropriate. Next let's take a look at this example to find out which technologies are not suitable for use in this example.
Display:
After reading the above two running diagrams, we can see two normal runtime diagrams, but not all of them. The error information is then displayed and analyzed. Here I will explain why this example is not a full error, but an inappropriate one. This is because if we want to use images that do not need to be changed, such as the display of some polygon, we do not need to refresh the interface in real time. This example has no problems and is easy to use. To address this, I think it is also necessary to attach code to demonstrate the implementation process.
Static drawing code:
Public class CustomCanvasView extends View {private static final String TAG = CustomCanvasView. class. getName (); private Paint paint; private int mRadius; private Canvas mCanvas; private int mHours; private int mMinutes; private int mSeconds; private Thread mThread; public CustomCanvasView (Context context, int radius) {super (context); paint = new Paint (); paint. setColor (Color. RED); paint. setStrokeJoin (Paint. join. ROUND); paint. setStrokeCap (Paint. cap. ROUND); paint. setStrokeWidth (5); mRadius = radius;} // here we will test the drawing method provided by canvas @ Override protected void onDraw (Canvas canvas) {mCanvas = canvas; drawCompass (mCanvas); refreshClock ();} private void refreshClock () {mThread = new Thread () {@ Override public void run () {try {while (true) {handler. sendEmptyMessage (0x123); sleep (1000) ;}} catch (InterruptedException e) {e. printStackTrace () ;}}; mThread. start ();} Handler handler = new Handler () {public void handleMessage (Message msg) {Calendar c = Calendar. getInstance (); mHours = c. getTime (). getHours (); mMinutes = c. getTime (). getMinutes (); mSeconds = c. getTime (). getSeconds (); invalidate (); c = null ;};};/***** draw a compass ** 2015-2-3 */private void drawCompass (Canvas canvas) {paint. setAntiAlias (true); paint. setStyle (Style. STROKE); canvas. translate (canvas. getWidth ()/2, mRadius + 300); // pan the compass canvas. drawCircle (0, 0, mRadius, paint); // draw a circle // draw a path text canvas using path. save (); drawLabel (canvas); canvas. restore (); drawDividing (canvas); drawMinuteHand (canvas, 0); canvas = null ;} /*** draw the label text on the inside of the compass ** 2015-2-4 */private void drawLabel (Canvas canvas) {canvas. translate (-155,-155); Path path = new Path (); path. addArc (new RectF (0, 0, mRadius + 100, mRadius + 100),-180,180); Paint citePaint = new Paint (paint); citePaint. setTextSize (30); citePaint. setStrokeWidth (1); canvas. drawTextOnPath ("http://blog.csdn.net/lemon_tree", path, 35, 0, citePaint); path = null; citePaint = null; canvas = null ;} /*** draw scale ** 2015-2-4 */private void drawDividing (Canvas canvas) {Paint divdPaint = new Paint (paint); // The divdPaint object of the small scale Paint. setStrokeWidth (1); divdPaint. setTextSize (20); float y = mRadius; int count = 60; // The total number of scales canvas. rotate (35*360/count, 0f, 0f); for (int I = 0; I <count; I ++) {if (I % 5 = 0) {canvas. drawLine (0f, y, 0, y + 20f, paint); canvas. drawText (String. valueOf (I/5 + 1),-4f, y + 55f, divdPaint);} else {canvas. drawLine (0f, y, 0f, y + 15f, divdPaint);} canvas. rotate (360/count, 0f, 0f); // rotating paper} divdPaint = null; canvas = null ;} /*** draw the sub-needle ** 2015-2-4 */private void drawMinuteHand (Canvas canvas, int second) {Paint tmpPaint = new Paint (paint); tmpPaint. setStrokeWidth (2); tmpPaint. setTextSize (30); tmpPaint. setColor (Color. GRAY); tmpPaint. setStrokeWidth (4); canvas. drawCircle (0, 0, 10, tmpPaint); tmpPaint. setStyle (Style. FILL); tmpPaint. setColor (Color. YELLOW); canvas. drawCircle (0, 0, 5, tmpPaint); canvas. rotate (mSeconds * 6, 0f, 0f); canvas. drawLine (0, 20, 0,-135, paint); tmpPaint = null; canvas = null ;}}
Error Log display and cause analysis:
To be honest, the above two figures look really good, but if you download my source code and run it, you may find that when your pointer goes for about 20 seconds, the program crashes. The following error message is displayed:
Is there a sense of a damn OOM problem? To tell the truth, that's also true. This may be because the resources are not cleared during invalidate (), and the custom control here inherits the View and does not adopt the Double Buffer technology, causing program crash. I have made some efforts to recycle resources here, but the problem persists. So I began to find another path to solve the problem-SurfaceView.
----------------------------------------- Split -------------------------------------------
Correct example: leading knowledge learning-dirty rectangle:
The so-called dirty rectangle refresh means to only refresh the rectangular area where the new part is located, and not refresh other useless parts to reduce resource waste. When obtaining the Canvas, we can assign a parameter to it to declare which part of the Canvas we need, so that we can only get control of this part. in this example, global refresh is used.
Leading knowledge learning-dual buffering:
For the concept of double buffering, refer to the instructions on Baidu Encyclopedia (click to enter ).
If I want to explain it in plain words, I think it should look like this: there is a dark room with a powerful painter who is responsible for drawing pictures. The dark room provides a small window to show the pictures drawn by the painter. There is also an artist's assistant in this dark room, he is responsible for displaying the pictures drawn by the painter at a certain speed in this small window (the speed here must be slower than the painter's painting speed ).
Example: run:
Are you surprised to see the above running? (Note: The GIF above looks a bit strange. It is because I didn't cut it down, and it will be much smoother when running the program.) When writing it, I also feel much better than using images. Next, let's take a look at the process of implementing it.
First thing to do
1. extends SurfaceView
2. implements SurfaceHolder. Callback
3. Customize a Thread
Step 2: implement logical functions
Based on the previous inappropriate version, the logical functions mentioned above are referenced here.
Draw seconds:
/*** Draw second-hand ** 2015-2-4 */private void drawSecondHand (Canvas canvas) {Paint handPaint = new Paint (mPaint); handPaint. setStrokeWidth (2); handPaint. setStyle (Style. FILL); int angle = (mSeconds + 25) * 6; // calculates the angle canvas. rotate (angle, 0f, 0f); canvas. drawLine (0, 20, 0,-135, mPaint );}
Draw the sub-needle:
/*** Draw the sub-needle ** 2015-2-4 */private void drawMinuteHand (Canvas canvas) {Paint handPaint = new Paint (mPaint); handPaint. setStrokeWidth (2); handPaint. setStyle (Style. FILL); canvas. save (); int angle = (mMinutes + 25) * 6; // calculates the angle canvas. rotate (angle, 0f, 0f); canvas. drawLine (0, 20, 0,-110, mPaint); canvas. restore ();}There are several more lines of code from the second-hand to the sub-needle, and what is the role of the several lines of code?
We can see the following sentence when drawing the sub-needle: canvas. rotate (angle, 0f, 0f); it is used to rotate the angle degree of the canvas. If we do not save the canvas as a state when drawing a split, the next time we plot the hour hand, it will be the logic that is made after rotation. To avoid unnecessary troubles, we need to save it Before restoring it.
Drawing hour hands:
/*** Drawing the hour ***/private void drawHourHand (Canvas canvas) {Paint handPaint = new Paint (mPaint); handPaint. setStyle (Style. FILL); handPaint. setStrokeWidth (8); canvas. save (); int angle = (mHours % 12) * 5 + 25) * 6) + (mMinutes * 6*5/60); // calculates the angle canvas. rotate (angle, 0f, 0f); canvas. drawLine (0, 20, 0,-90, handPaint); canvas. restore ();}The drawing of the hour hand is almost the same as that of the minute hand. The only thing to note is the calculation of the angle of the hour hand. If you only calculate by the number of hours, it always points to a large scale. It will never point between two large scales. To solve this problem, we need to add the number of minutes for calculation. That is, how many angles are offset when the hour hand is added for n minutes.
Custom Thread
SurfaceView requires a lock mechanism. That is to say, when drawing on my side, it is not allowed to be disturbed. There is an exclusive concept. You can use the following code:
Class DrawThread extends Thread {private SurfaceHolder holder; public boolean isRun; public DrawThread (SurfaceHolder holder) {this. holder = holder; isRun = true ;}@ Override public void run () {while (isRun) {Canvas canvas = null; try {synchronized (holder) {Canvas = holder. lockCanvas (null); canvas. drawColor (Color. BLACK); drawClock (canvas); holder. unlockCanvasAndPost (canvas); // unlock the canvas and submit the painted image Thread. sleep (1000) ;}} catch (InterruptedException e) {e. printStackTrace ();}}}}You can see that after I lock the canvas, there is a code operation for canvas. drawColor (Color. BLACK. I think you should understand why. Yes! Is clear screen! Without this code, the last drawing is not cleared, which makes the entire interface very messy. Let's take a look at the dynamic drawing of Canvas without a clear screen and with only one pointer.
We can see the Label on the inside of the clock. The white part is deepened at 1.1 points, which strongly demonstrates that the last figure is left behind.
Well, it's time to use SurfaceView and Canvas to learn the custom clock. If you still have something you don't quite understand, please visit my previous blog 《Basic knowledge of Android custom controls (1) -- Canvas"To learn, or to communicate with me in the form of comments.