I am trying to use SurfaceView, but the lockCanvas(null)
is confusing, and the app freezes when I exit the activity. Also, nothing is displayed, even though the
The solution is probably that setWillNotDraw(false);
was not called.
Therefore, a referential implementation that works properly would be the following:
public class GameSurfaceView extends SurfaceView implements SurfaceHolder.Callback
{
private SurfaceHolder holder;
private MyThread myThread;
private GameController gameController;
private Paint paint;
private int width;
private int height;
public GameSurfaceView(Context context, GameController gameController, int width, int height)
{
super(context);
holder = getHolder();
holder.addCallback(this);
this.gameController = gameController;
this.width = width;
this.height = height;
paint = new Paint();
//initialize paint object parameters
setWillNotDraw(false); //this line is very important!
}
@Override
public void surfaceCreated(SurfaceHolder holder)
{
}
@Override
// This is always called at least once, after surfaceCreated
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
if (myThread == null)
{
myThread = new MyThread(holder, gameController);
myThread.setRunning(true);
myThread.start();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
myThread.setRunning(false);
while (retry)
{
try
{
myThread.join();
retry = false;
}
catch (InterruptedException e)
{
Log.d(getClass().getSimpleName(), "Interrupted Exception", e);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
System.out.println(event.getX() + " " + event.getY());
gameController.onTouchEvent(event); //handle user interaction
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawText("Hello world!", width/20, 20, paint);
gameController.draw(canvas);
}
public Thread getThread()
{
return thread;
}
public class MyThread extends Thread
{
private SurfaceHolder holder;
private boolean running = false;
private GameController gameController;
public MyThread(SurfaceHolder holder, GameController gameController)
{
this.holder = holder;
this.gameController = gameController;
}
@Override
public void run()
{
Canvas canvas = null;
while (running)
{
gameController.update(); //update the time between last update() call and now
try
{
canvas = holder.lockCanvas(null);
synchronized (holder)
{
postInvalidate();
}
}
finally
{
if (canvas != null)
{
holder.unlockCanvasAndPost(canvas);
}
}
}
}
public void setRunning(boolean b)
{
running = b;
}
}
}
The surface itself doesn't really want to answer what its width and height is, therefore it's easier if we get its measurements from the outside, and provide it for our custom View from the outside:
//Fragment onCreateView()
Display display = getActivity().getWindowManager().getDefaultDisplay();
RelativeLayout rl = (RelativeLayout)view.findViewById(R.id.gameplay_container);
rl.addView(new GameSurfaceView(this.getActivity().getApplicationContext(), gameController, display.getWidth(), display.getHeight()));
return view;
}
Now we are able to draw onto the SurfaceView from our own Thread, and we are also able to seperate all game-related logic in a GameController. The GameController is responsible for input handling and game event updates, as per the following:
public void update()
{
long delta = getTimeManager().getDeltaTime(System.currentTimeMillis());
long timeChunk;
if (isGameOver == false)
{
for (long i = 0; i < delta; i += 20)
{
long iNext = i + 20;
if (iNext > delta)
{
timeChunk = delta - i;
}
else
{
timeChunk = iNext - i;
}
// ...update game entities based on the miliseconds provided in timeChunk
}
And to get the delta amount of time (the time that passed by since the previous update) is the following:
long temp = currentTimeMillis - oldTime;
this.oldTime = currentTimeMillis;
return temp;
I hope that will be helpful to you later :)