ShareIntroduction
If you didn't see the first part, go
here.
Graphics in Android - Part II
The second part of this series will show what you have to change to switch from using the View class to SurfaceView class.
Just to remember, our last code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
package com.droidnova.android.tutorial2d; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.view.Window; public class Tutorial2D extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(new Panel(this)); } class Panel extends View { public Panel(Context context) { super(context); } @Override public void onDraw(Canvas canvas) { Bitmap _scratch = BitmapFactory.decodeResource(getResources(), R.drawable.icon); canvas.drawColor(Color.BLACK); canvas.drawBitmap(_scratch, 10, 10, null); } } }
|
Now lets simply change the super class of our Panel from View to SurfaceView.
1 2 3
|
// code snipped class Panel extends SurfaceView { // code snipped
|
Lets compile and start and you will see…. nothing.
The reason therefor is, that we need the SurfaceHolder, which provide us with the canvas we can draw on.
So one more change: use SurfaceHolder.Callback interface. The interface requires 3 additional methods we need to implement: surfaceCreated(), surfaceDestroyed(), surfaceChanged()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
// code snipped class Panel extends SurfaceView implements SurfaceHolder.Callback { @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub } @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub } }
|
Additional we have to add the Panel to the SurfaceHolder for a callback. We will do this in the Panel constructor.
1 2 3 4
|
public Panel(Context context) { super(context); getHolder().addCallback(this); }
|
The next big thing: We need a Thread which will control the drawings. Lets create the inner class TutorialThread which extends from Thread class. We need a constructor with the Panel and the SurfaceHolder as parameters, a setter to set the variable which keeps the thread running and we need to override the method run().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
class TutorialThread extends Thread { private SurfaceHolder _surfaceHolder; private Panel _panel; private boolean _run = false; public TutorialThread(SurfaceHolder surfaceHolder, Panel panel) { _surfaceHolder = surfaceHolder; _panel = panel; } public void setRunning(boolean run) { _run = run; } @Override public void run() { } }
|
Now lets use the TutorialThread in our Panel:
1 2 3 4 5 6 7 8 9
|
class Panel extends SurfaceView implements SurfaceHolder.Callback { private TutorialThread _thread; public Panel(Context context) { super(context); getHolder().addCallback(this); _thread = new TutorialThread(getHolder(), this); } // code snipped
|
We can also implement the methods surfaceCreated() and surfaceDestroyed(). The method surfaceCreated() after the surface was created. So we can set our running variable to true and start the thread.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
@Override public void surfaceCreated(SurfaceHolder holder) { _thread.setRunning(true); _thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { // simply copied from sample application LunarLander: // we have to tell thread to shut down & wait for it to finish, or else // it might touch the Surface after we return and explode boolean retry = true; _thread.setRunning(false); while (retry) { try { _thread.join(); retry = false; } catch (InterruptedException e) { // we will try it again and again... } } }
|
Now we are near the finish line because we just have to implement the run() method.
First of all we need a Canvas object (line 3). Then a while loop with our _run variable (line 4)
Inside the loop we set our Canvas object to null and open a try block (line 5-6).
Inside this try block, we have to lock the canvas, draw on it, unlock and post it.
We are inside of a thread, so we have to synchronize at least our drawing with the SurfaceHolder (line 8-10).
We unlock and post it in the finally block to make sure, we don’t leave the Surface in an inconsistent state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
@Override public void run() { Canvas c; while (_run) { c = null; try { c = _surfaceHolder.lockCanvas(null); synchronized (_surfaceHolder) { _panel.onDraw(c); } } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { _surfaceHolder.unlockCanvasAndPost(c); } } } }
|
Finally we should have this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
|
package com.droidnova.android.tutorial2d; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.os.Bundle; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.Window; public class Tutorial2D extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(new Panel(this)); } class Panel extends SurfaceView implements SurfaceHolder.Callback { private TutorialThread _thread; public Panel(Context context) { super(context); getHolder().addCallback(this); _thread = new TutorialThread(getHolder(), this); } @Override public void onDraw(Canvas canvas) { Bitmap _scratch = BitmapFactory.decodeResource(getResources(), R.drawable.icon); canvas.drawColor(Color.BLACK); canvas.drawBitmap(_scratch, 10, 10, null); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceCreated(SurfaceHolder holder) { _thread.setRunning(true); _thread.start(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { // simply copied from sample application LunarLander: // we have to tell thread to shut down & wait for it to finish, or else // it might touch the Surface after we return and explode boolean retry = true; _thread.setRunning(false); while (retry) { try { _thread.join(); retry = false; } catch (InterruptedException e) { // we will try it again and again... } } } } class TutorialThread extends Thread { private SurfaceHolder _surfaceHolder; private Panel _panel; private boolean _run = false; public TutorialThread(SurfaceHolder surfaceHolder, Panel panel) { _surfaceHolder = surfaceHolder; _panel = panel; } public void setRunning(boolean run) { _run = run; } @Override public void run() { Canvas c; while (_run) { c = null; try { c = _surfaceHolder.lockCanvas(null); synchronized (_surfaceHolder) { _panel.onDraw(c); } } finally { // do this in a finally so that if an exception is thrown // during the above, we don't leave the Surface in an // inconsistent state if (c != null) { _surfaceHolder.unlockCanvasAndPost(c); } } } } } }
|
Screenshot which shows the same icon as in part I.

Goto
Part III