So you want to make a game, do you? Do you want to compete with the likes of Angry Birds? Well, learning the Android Canvas is the best place to start.
Many people want to make Android games. I would assume that is why you are at my page. I hope that my tutorial will help you grasp a beginner's understanding of the SurfaceView class and the underlying Canvas class. That way, you'll be making your game or custom view in no time! So, let's start.
Step 1 - Make A Class
In order to start working with the SurfaceView and all of it's functions, you are going to have to extend from it. Create a new class file, and define it as:
Extending the SurfaceView class will allow you to create a view based on SurfaceView and place it into your Activity. Implementing the callback interface will allow you to be notified when the surface is created and destroyed, starting and stopping any current tasks or threads that you may be running when the Activity is created or ended, respectively.
Step 2 - Construct the Class
The next step is to construct the class with many of the important parts. We're going to need a constructor:
As you can see in this code, we are setting up the new SurfaceView. The call to
class DrawingPanel extends SurfaceView implements SurfaceHolder.Callback {
}
Extending the SurfaceView class will allow you to create a view based on SurfaceView and place it into your Activity. Implementing the callback interface will allow you to be notified when the surface is created and destroyed, starting and stopping any current tasks or threads that you may be running when the Activity is created or ended, respectively.
Step 2 - Construct the Class
The next step is to construct the class with many of the important parts. We're going to need a constructor:
public DrawingPanel(Context context) {
super(context);
getHolder().addCallback(this);
}
@Override
public void onDraw(Canvas canvas) {
//do drawing stuff here.
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
As you can see in this code, we are setting up the new SurfaceView. The call to
getHolder().addCallback(this)
allows us to set up the methods in step 3. The onDraw(Canvas canvas)
method will be where you construct what you are going to draw on the canvas. To draw on the canvas, you can use many methods, including:canvas.drawColor(color) //Draws a solid color
canvas.drawLine(params) //Draws a line
canvas.drawRect(params) //Draws a rectangle
canvas.drawBitmap(params) //Draws a bitmap image
- other methods available at the Android reference site
Step 3 - Create the Drawing Thread
In this step, we will create a thread that will draw whatever we put into the
Something to note is that this tutorial will use
The thread code is pretty much copy-paste-able, however, if you want to change positions of any of the drawings, you will have to modify their positions in the section above the call to
Next, we will start with implementing the
Step 4 - Implement the SurfaceView
This is the easiest step, by far. Simply create a new instance of your View in your activity, and add it. Easy! Now you're done! Go out there and make some good games!
In this step, we will create a thread that will draw whatever we put into the
onDraw()
method, as it won't call itself.
class PanelThread extends Thread {
private SurfaceHolder _surfaceHolder;
private DrawingPanel _panel;
private boolean _run = false;
public PanelThread(SurfaceHolder surfaceHolder, DrawingPanel panel) {
_surfaceHolder = surfaceHolder;
_panel = panel;
}
public void setRunning(boolean run) { //Allow us to stop the thread
_run = run;
}
@Override
public void run() {
Canvas c;
while (_run) { //When setRunning(false) occurs, _run is
c = null; //
set to false and loop ends, stopping thread try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
//Insert methods to modify positions of items in onDraw()
postInvalidate();
}
} finally {
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
Something to note is that this tutorial will use
postInvalidate()
instead of a direct call to onDraw()
. This is because, in my experience, I have found this method to be better for the system and for performance.The thread code is pretty much copy-paste-able, however, if you want to change positions of any of the drawings, you will have to modify their positions in the section above the call to
postInvalidate()
. This is also pretty straight-forward, and (usually) requires you to use class-wide variables, stated in the original DrawingPanel
class.Next, we will start with implementing the
onSurfaceCreated
and onSurfaceDestroyed
methods supplyed by the implemented callback in order to stop and start our thread:
@Override
public void surfaceCreated(SurfaceHolder holder) {
setWillNotDraw(false); //Allows us to use invalidate() to call onDraw()
_thread = new PanelThread(getHolder(), this); //Start the thread that
_thread.setRunning(true); //will make calls to
_thread.start(); //onDraw()
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
try {
_thread.setRunning(false); //Tells thread to stop
_thread.join(); //Removes thread from mem.
} catch (InterruptedException e) {}
}
Step 4 - Implement the SurfaceView
This is the easiest step, by far. Simply create a new instance of your View in your activity, and add it. Easy! Now you're done! Go out there and make some good games!
Great, thanks. A working example would be nice.
ReplyDeleteit is a working example, you gotta paste like 3 things? r u that lazy
ReplyDeleteI have started implementing this, but there are a few things that could use some clarification.
ReplyDeleteFirst, where is PanelThread defined? Is it in its own file, or embedded in the DrawingPanel object, or defined after the DrawingPanel class in the same file?
Next, how does the DrawingPanel get access to the _thread in the surfaceCreated/surfaceDestroyed methods? This may end up being obvious after the answer to the first question, but right now it isn't apparent.
The last thing I noticed was that the @Override tag was not needed by the surfaceChanged, surfaceCreated and surfaceDestroyed methods. This is because the methods in the base are declared as abstract, not virtual (or an indicator I'm inheriting from the wrong thing/version?).
Any pointers would be appreciated.
It should be in the DrawingPanel object. In doing this, it has a direct way to reference the _thread.
DeleteThe override is only there because I'm lazy and had eclipse do it for me :p
This comment has been removed by the author.
DeleteUsing threads is not a beginners example is it ?
DeletePersonally, I believe that most of the time, people will be looking for Canvas tutorials because they want to animate something, or make something move in their custom view... They can't do that without the threading. While normally I'd agree with you, the nature of the Canvas almost requires people have a general idea of how to use threads.
DeleteMan it's unbelievable how people act as spoiled little bitches. It's almost like you have to apologize for the helping.
DeleteNot sure where to copy stuff to. Could we get a download link to the full source.
ReplyDeletethis is working code---
ReplyDeletepackage com.example.surfaceviewexample;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class First extends Activity {
DrawingPanel dp;
PanelThread _thread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.first);
dp = new DrawingPanel(this);
setContentView(dp);
}// end of oncreate fns
// new class start
class DrawingPanel extends SurfaceView implements SurfaceHolder.Callback {
public DrawingPanel(Context context) {
super(context);
getHolder().addCallback(this);
}
@Override
public void onDraw(Canvas canvas) {
int x = 0;
int y = 0;
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
// make the entire canvas white
paint.setColor(Color.WHITE);
canvas.drawPaint(paint);
// another way to do this is to use:
// canvas.drawColor(Color.WHITE);
// draw a solid blue circle
paint.setColor(Color.BLUE);
canvas.drawCircle(20, 20, 15, paint);
// draw blue circle with antialiasing turned on
paint.setAntiAlias(true);
paint.setColor(Color.BLUE);
canvas.drawCircle(60, 20, 15, paint);
}
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,int arg3) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
setWillNotDraw(false); //Allows us to use invalidate() to call onDraw()
_thread = new PanelThread(getHolder(), this); //Start the thread that
_thread.setRunning(true); //will make calls to
_thread.start(); //onDraw()
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
try {
_thread.setRunning(false); //Tells thread to stop
_thread.join(); //Removes thread from mem.
} catch (InterruptedException e) {}
}
} // end of drawing panel class
// start of thread class
class PanelThread extends Thread {
private SurfaceHolder _surfaceHolder;
private DrawingPanel _panel;
private boolean _run = false;
public PanelThread(SurfaceHolder surfaceHolder, DrawingPanel panel) {
_surfaceHolder = surfaceHolder;
_panel = panel;
}
public void setRunning(boolean run) { //Allow us to stop the thread
_run = run;
}
@Override
public void run() {
Canvas c;
while (_run) { //When setRunning(false) occurs, _run is
c = null; //set to false and loop ends, stopping thread
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
//Insert methods to modify positions of items in onDraw()
postInvalidate();
}
} finally {
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
private void postInvalidate() {
}
}// end of thread class
} // end of main class