Writing a custom layout for board games in Android — the BoardLayout!!!
Ever tried using TableLayout
when building a board game for Android — don’t, it’s horrible, from my experience!
In this tutorial, we are going to code a layout called BoardLayout
which is like GridLayout
in JavaFX — each child view can be placed in a cell, given its coordinates.
To build our custom layout, we need to inherit from ViewGroup
and override two methods — onMeasure
and onLayout
.
Implementing onMeasure()
onMeasure
takes two parameters — the width and height measure specs. Measure specs are basically specifications that tell the size a parent view wants to given to a child encoded in an int
that contains the mode and size. The mode gives the type of constraint and size gives the numerical value.
onMeasure
is also supposed to call measure
on each of its children — as BoardLayout
itself is a parent view.
By the way, there are three MeasureSpec
modes:
- MeasureSpec.UNSPECIFIED — this means that the parent view is not imposing any constraints on the child. The child view should set its “wanted” size.
- MeasureSpec.EXACTLY — this means that the parent view wants the child to be exactly the size it provided (in the measure-spec).
- MeasureSpec.AT_MOST — this means that the parent view is allowing the child to be any size up to the size it provided (in the measure-spec).
The methods MeasureSpec.getMode
and MeasureSpec.getSize
methods allow you to get the values of the mode and size. Our onMeasure
method should be able to handle all three modes.
In the code snippet above, we expect that at least one parameter — width or height — should be given exactly. If neither are given exactly, onMeasure throws a exception: which is okay, as long as you specify the width and height of the BoardLayout in its layout-params. Given one parameter, the other can be found by proportion as:
width:height::columns:rows
. Once we have found our width and height, we callmeasureExact
that finds the width and height per cell, and also callsmeasure
on all child views. In the end, we set our measured dimensions by calling a method inViewGroup
.
Implementing onLayout()
onLayout
gets five parameters — changed, left, top, right and bottom. The first one — changed — tells whether anything changed since layout was called before and we won’t be using that here. The other four give the sides of this view on the screen, in pixels. Based on that, we are supposed to call layout
on each of our children with the position we given them (via setLayoutParams
and initializing row
and col
in a BoardLayout.LayoutParams
).
// Should be LayoutParams inside the BoardLayout class
public static class BoardLayout.LayoutParams {
public int row;
public int col;
}@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int width = right - left;
final int height = bottom - top; final int cellWidth = width / mCols;// mCols should be defined
final int cellHeight = height / mRows;// mRows too
final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) {
final View child = getViewAt(i);
final BoardLayout.LayoutParams params = (BoardLayout.LayoutParams) child.getLayoutParams();
final int row = (params != null) ? params.row : 0;
final int col = (params != null) ? params.col : 0; final int childX = left + params.col * width;
final int childY = top + params.row * height; final int childWidth = child.getMeasuredWidth();
final int childHeigh = child.getMeasuredHeight();
// these should've initialized in onMeasure when we
// called measure on our children child.layout(childX, childY, childX + childWidth, childY + childHeight);// again left, top, right, bottom
}
This should be simple enough — all we do is calculated the X and Y (or left and top) positions of our children and then get the right and bottom values by adding their width and height. In the end, we just call
layout
on them and our job is done.
You have just learnt how to write a custom layout in Android — a custom View in its self. For a more flexible implementation, see my project Board.Android.UI. I’ve added support for padding, cell transformations, and specific positioning within cells (here we set the X and Y positions of each each to the top-left corner of each cell).