Android Example of a Zoomable Game Board

In this article, I briefly describe how I built a simple game board in Android. The sample app shows a grid of squares that represents a game board. The squares on the game board come from an array of image tiles. The grid can be zoomed in or out and moved. Touch events are triggered when squares are touched. Regular and long press are handled. With this sample as a starting point, it should be easy to build a game that uses a board of squares.

The video below shows the demo app in action.

Source Code

Full source code for this game board demo is available at the download page.

Classes in the Demo Application

There are four classes that do most of the work in this sample application.

This view supports both zooming and panning and other touch gestures and actions. What gets shown on the screen depends on the onDraw method provided by its subclasses. In this demo app, the subclass is GameBoardView.

This view displays a grid of rectangles that can be zoomed in and out and panned. The default grid is 13 x 13, with 8 squares showing. The zoom point is the center of the view.

Defines the listener interface for touch actions on a GameBoardView. It defines onTouchDown, onTouchUp, and onLongTouchUp. For onTouchUp and onLongTouchUp, the listener receives index numbers of the squares of the grid. No coordinates are included in the call to onTouchDown.

This is the class that brings together all the other classes into a working demo. It generates a random grid of of squares. The grid represents a simple game board. Each of the squares shows a white, red, or blue tile. This activity connects a GameBoardView to the game board grid so it can be displayed on the screen. This activity also defines actions for touch actions. When a square is touched, it is highlighted to show that it is selected. When a long touch is done, the square changes to the next color in the sequence (white, red, blue).

How It Works

GameBoardActivity has a simple layout. A real game would have much more, but all I wanted to show was how easily a GameBoardView could be used to fill up the screen with a grid of squares, even if there were more squares than fit on the screen. The layout says to fill the screen width with a game board and to fill whatever is left of the height after the simple text instructions are displayed.

 android:orientation="vertical" >


 android:text="@string/instructions" />


The connection to the GameBoardView is made in onCreate. The activity provides the initial grid values to the view and attaches itself as the listener for touch events.

@Override public void onCreate(Bundle savedInstanceState) {

 setupMyGrid (NumSquaresOnGridSide);
 GameBoardView gv = (GameBoardView) findViewById (;
 if (gv != null) {
  setGridView (gv);

  gv.setNumSquaresAlongCanvas (NumSquaresOnGridSide);
  gv.setNumSquaresAlongSide (NumSquaresOnViewSide);
  gv.updateGrid (getGrid ());
  gv.setTouchListener (this);

The grid is generated randomly with values between 0 and 2. Those values are used by GameBoardView to look up images from its array of three images.

The touch listener methods in GameBoardActivity are there to illustrate how to select squares and take actions on the game board. The onTouchUp method shows how a square is selected. It does not update the grid value because selection state can be maintained in the GameBoardView. In a real game, other actions, such as button presses, might want to know which square is selected.

public void onTouchUp (int downX, int downY, int upX, int upY) {
 GameBoardView gv = getGridView ();
 if (gv == null) return;

 boolean isSelected = gv.isSelected (upX, upY);
 gv.clearSelections ();
 if (!isSelected) gv.toggleSelection (upX, upY);
 gv.invalidate ();


The long press handling is a bit different. It results in the value on the grid being changed. After a change to the board, the view is informed that it needs to redraw itself. This happens in this demo in response to a long press, but in a real game, it’s more likely going to be that some computations have modified the grid contents and it’s time to update the display.

public void onLongTouchUp (int downX, int downY, int upX, int upY) {
 GameBoardView gv = getGridView ();
 if (gv == null) return;

 int oldValue = gv.gridValue (upX, upY);
 int newValue = oldValue + 1;
 if (newValue >= NumRedBlueTypes) newValue = 0;
 gv.setGridValue (upX, upY, newValue);
 gv.invalidate ();


Next we will examine how a GameBoardView works. Its two main responsibilities are: (1) drawing the images squares onto the screen as an x-y grid; (2) handling touch events and converting the view coordinates of the touch points into the index values between 1 and the number of squares along the side of the grid.

The most complicated thing the view has to do is be able to draw all the squares of the grid on the screen, even if they do not all fit in the height and width allocated for the view. It writes onto a canvas that is just large enough to hold all the squares on the grid. The figure below depicts roughly what is going on.


The size of the canvas object is determined by the size of a square. The square size is determined by how many squares fit along the shortest edge of the view. In this demo program, the activity requests for there to be 13 squares along the edge of the grid and for there to be 8 squares visible in the view. So the canvas size is roughly 1.6 times the view size. (13 / 8 = 1.625).

The view supports panning and zooming, but most of the work for that is done in class PanZoomView. Think of it this way. Panning means the green canvas box moves around, left and right, and up and down. The user sees what is in the gray zone. Zooming means that the green canvas box gets larger and smaller, revealing different parts of the canvas in the gray box.

The images that are drawn on the screen are defined in an array in GameBoardView. There is one set of images for the regular images and another set for when a square is selected. As you read through the code, you will see the place in getBitmapsArray where the bitmap objects are set up from the resource ids. Doing this once speeds up the drawing operation.

static private final int [] mUnselectedImageIds
 = {R.drawable.no_marker_default,
static private final int [] mSelectedImageIds
 = {R.drawable.no_marker_highlighted,
private Bitmap [] mBitmaps;
private Bitmap [] mSelectedBitmaps;

Drawing is done in the onDrawPz method and drawOnCanvas methods. The onDrawPz method is what the superclass (PanZoomView) put in place for subclasses to draw their contents.  There is a lot going on in that method, but basically all it is doing is (1) figuring out the square size from the view size; (2) figuring out how large the canvas is and where its origin point; (3) looping through the grid values and displaying the right image in each square. Parts 1-2 are done in onDrawPz. It looks a bit like this:

@Override public void onDrawPz(Canvas canvas) {;

 ... setting a lot of variables ...
 // The canvas is translated by the amount we have
 // scrolled and the standard amount to move the origin
 // of the canvas up and left so the region is centered
 // in the view. (Note: mPosX/mPosY are defined in PanZoomView.)
 float x = 0, y = 0;
 mPosX0 = mOriginOffsetX;
 mPosY0 = mOriginOffsetY;
 x = mPosX - mPosX0;
 y = mPosY - mPosY0;
 canvas.translate (x, y);

 ... more set up ...

 // The focus point for zooming is the center of the
 // displayable region. That point is defined by half
 // the canvas width and height.
 mFocusX = mHalfMaxCanvasWidth;
 mFocusY = mHalfMaxCanvasHeight;
 canvas.scale (mScaleFactor, mScaleFactor, mFocusX, mFocusY);

 ... more ...
 // Do the drawing operation for the view.
 drawOnCanvas (canvas);

It is a bit more involved than that because it has to account for the fact that code in its superclass is tracking touches and supporting pan and zoom gestures. So that’s why you see calls like “canvas.translate” and “canvas.scale”.

With all of that set up there in onDrawPz, the drawOnCanvas method is actually pretty easy to understand. It is going to draw the 13 x 13 grid on the canvas. It looks up values from the grid object, which specifies the index number of what image to put on the screen. It then has two loops where each of the images is drawn onto the canvas.

public void drawOnCanvas (Canvas canvas) {
 int x, y;
 float fx, fy;
 Paint paint = new Paint();
 Bitmap [] bitmaps = getBitmapsArray ();
 Bitmap [] selectedBitmaps = mSelectedBitmaps;
 int [] [] grid = getGridArray ();
 // Set width and height for the rectangle to be drawn.
 RectF dest1 = mDestRectF;
 // Draw squares to fill the grid.
 float dx = 0, dy = 0;
 for (int j = 0; j < pNumSquaresAlongCanvas; j++) {
 dx = 0;
 for (int i = 0; i < pNumSquaresAlongCanvas; i++) {
   int bindex = grid [j][i];
   boolean isSelected = (mGridSelect [j][i] > 0);

   Bitmap b1 = isSelected ? selectedBitmaps [bindex]
                          : bitmaps [bindex];
   dest1.offsetTo (dx, dy);
   canvas.drawBitmap (b1, null, dest1, paint);
   dx = dx + mSquareWidth;
 dy = dy + mSquareHeight;

Touches are tracked in PanZoomView. All GameBoardView has to do is convert touches in the View coordinate system that PanZoomView supports into the x-y grid coordinates that GameBoardView listener objects expect. Here is an excerpt from the onTouchUp method.

public void onTouchUp (float downX, float downY, 
                       float upX, float upY) {

 long nDownTime = mDownTime;
 mDownTime = 0l;

 // Figure out if this was a long press.
 long nUpTime = System.nanoTime ();
 boolean isLongPress = (nUpTime - nDownTime) > mLongPressTimeOut;

 // Convert view coordinates to canvas coordinates
 // and, eventually to index values for the grid cells.

 // We want integer index values to call the listener.
 int sUpX = (int) Math.floor (fx2) + 1;
 int sUpY = (int) Math.floor (fy2) + 1;
 int sDownX = (int) Math.floor (dfx2) + 1;
 int sDownY = (int) Math.floor (dfy2) + 1;

 // Next check to see if there is a listener for these events.
 // If there is not, there is nothing else to do.
 GameBoardTouchListener listener = getTouchListener ();
 if (listener == null) return;

 // Tell the listener about the Up event. 
 if (isLongPress)
 listener.onLongTouchUp (sDownX, sDownY, sUpX, sUpY); 
 else listener.onTouchUp (sDownX, sDownY, sUpX, sUpY); 

A very important class in this sample app is PanZoomView. It handles all the low level things related to touch, pan, and zoom gestures. I won’t go into the details here, but you should spend some time reading through it so you understand its onTouchEvent method and its ScaleGestureDetector object. You will also see how it translates all of the touches into method calls (as with onTouchUp and onTouchDown) or changes in instance variables for panning and zooming.

PanZoomView is a class that you might be able to reuse in different contexts. Just override its assorted “supportsXXX” methods (e.g. supportsPan, supportsZoom) and implement your own onDrawPz method for new subclasses.

Game Board In Action

This code is a good starting point for a game. I have used it in my Gomoku game: Gomoku League on Google Play. That game can be played on an 11×11 board or a 19×19 board. For the larger board, the zooming and panning features are very useful.

Another game app that uses the PanZoomView and has its own variation of a GameBoardView is my Double Star game. It is in beta test. Go the Double Star page on Google Play and click “Become a Tester”.


I have experimented with panning and zooming and displaying grids in several different demo applications.

  • Android Rotate and Scale Bitmaps  (February, 2014) – This tutorial app covers scaling of bitmaps, rotation of bitmaps, and translation from one origin to another.

About Bill Lahti

Bill Lahti is a software engineer building mobile applications and knowledge management solutions. Two of his interests are writing for this blog and building Android apps, with strategy games being an area of particular interest.
This entry was posted in Android, Game Design and tagged , , , , , , . Bookmark the permalink.

One Response to Android Example of a Zoomable Game Board

  1. Pingback: Multitouch Panning and Zooming Examples for Android | More Is Not Always Better

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s