Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Last active April 4, 2025 14:03
Show Gist options
  • Save slightfoot/a519bc3627f49c44a226 to your computer and use it in GitHub Desktop.
Save slightfoot/a519bc3627f49c44a226 to your computer and use it in GitHub Desktop.
Example of using a SurfaceView to render potential 2d game content.
package com.your.package;
import java.util.Random;
import android.app.Activity;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(new BallSurfaceView(this));
}
public static class BallSurfaceView extends SurfaceView implements SurfaceHolder.Callback
{
private RenderThread mThread;
public BallSurfaceView(Context context)
{
super(context);
setKeepScreenOn(true);
getHolder().addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder)
{
mThread = new RenderThread(holder);
mThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
mThread.setSize(width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
mThread.quit();
}
private class RenderThread extends Thread
{
private SurfaceHolder mHolder;
private boolean mQuit;
private int mWidth;
private int mHeight;
private Renderable[] mRenderables = new Renderable[6];
private Paint mDebugPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final boolean mDebugEnable = true; // flip me to see clip rect
public RenderThread(SurfaceHolder holder)
{
super(RenderThread.class.getSimpleName());
mHolder = holder;
Random rand = new Random();
mRenderables[0] = new Background();
for(int i = 1; i < mRenderables.length; i++){
mRenderables[i] = new Ball(100 + rand.nextInt(1500));
}
mDebugPaint.setColor(Color.GREEN);
}
public void setSize(int width, int height)
{
mWidth = width;
mHeight = height;
for(int i = 0; i < mRenderables.length; i++){
mRenderables[i].playfield(width, height);
}
}
@Override
public void run()
{
mQuit = false;
Rect dirty = new Rect();
RectF dirtyF = new RectF();
double dt = 1 / 60.0; // upper-bound 60fps
double currentTime = SystemClock.elapsedRealtime();
while(!mQuit){
double newTime = SystemClock.elapsedRealtime();
double frameTime = (newTime - currentTime) / 1000.0f;
currentTime = newTime;
dirtyF.setEmpty();
while(frameTime > 0.0){
double deltaTime = Math.min(frameTime, dt);
integrate(dirtyF, 1.0f * deltaTime);
frameTime -= deltaTime;
}
dirty.set((int)dirtyF.left, (int)dirtyF.top,
(int)Math.round(dirtyF.right),
(int)Math.round(dirtyF.bottom));
render(dirty);
}
}
private void integrate(RectF dirty, double timeDelta)
{
for(int i = 0; i < mRenderables.length; i++){
final Renderable renderable = mRenderables[i];
renderable.unionRect(dirty);
renderable.update(dirty, timeDelta);
renderable.unionRect(dirty);
}
}
private void render(Rect dirty)
{
Canvas c = mHolder.lockCanvas(!mDebugEnable ? dirty : null);
if(c != null){
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
if(mDebugEnable){
c.drawRect(dirty, mDebugPaint);
}
for(int i = 0; i < mRenderables.length; i++){
mRenderables[i].draw(c);
}
mHolder.unlockCanvasAndPost(c);
}
}
public void quit()
{
mQuit = true;
try{
mThread.join();
}
catch(InterruptedException e){
//
}
}
private class Background extends Renderable
{
private final Paint mBg = new Paint(Paint.ANTI_ALIAS_FLAG);
public Background()
{
mBg.setShader(new BitmapShader(
BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_dialog_alert),
Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
}
@Override
public void playfield(int width, int height)
{
}
@Override
public void update(RectF dirty, double timeDelta)
{
}
@Override
public void draw(Canvas c)
{
c.drawPaint(mBg);
}
}
private class Ball extends Renderable
{
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float mXVelDir, mYVelDir; // pixels-per-second
private float mSize;
public Ball(int speed)
{
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.RED);
mXVelDir = speed;
mYVelDir = speed;
}
@Override
public void playfield(int width, int height)
{
mSize = width / 7.0f;
}
@Override
public void update(RectF dirty, double timeDelta)
{
mRect.left += (mXVelDir * timeDelta);
mRect.top += (mYVelDir * timeDelta);
mRect.right = mRect.left + mSize;
mRect.bottom = mRect.top + mSize;
if(mRect.left <= 0){
mRect.offset(-mRect.left, 0);
mXVelDir = -mXVelDir;
}
else if(mRect.right >= mWidth){
mRect.offset(mWidth - mRect.right, 0);
mXVelDir = -mXVelDir;
}
if(mRect.top <= 0){
mRect.offset(0, -mRect.top);
mYVelDir = -mYVelDir;
}
else if(mRect.bottom >= mHeight){
mRect.offset(0, mHeight - mRect.bottom);
mYVelDir = -mYVelDir;
}
}
@Override
public void draw(Canvas c)
{
c.drawCircle(mRect.centerX(), mRect.centerY(),
mRect.width() / 2.0f, mPaint);
}
}
private abstract class Renderable
{
protected final RectF mRect = new RectF();
public abstract void playfield(int width, int height);
public abstract void update(RectF dirty, double timeDelta);
public abstract void draw(Canvas c);
public final void unionRect(RectF dirty)
{
dirty.union(mRect);
}
}
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment