I am developing a word game similar to the most famous one. The board has 15 x 15 tiles. There are 5 possible colors for the tiles.
The board can be zoomed in. I.e. you can either have a full view of the board, or zoom in 2X.
I have implemented 2 ways to draw the board in my Activity. Both have been tested OK on my Galaxy S and on various emulators. Both seem to work fine on a vast majority of devices. BUT, they both cause issues for a minority of users (which is what I am trying to solve here).
Here's for the description of the 2 designs:
Option 1: 'big image'
Description:
This is the simplest one. The board is a unique ImageView
(subclassed to handle double-tap zoom and dragging).
The image PNG resource is high res: approx. twice the size of the biggest screen size to avoid upscaling when zoomed in.
Issues: OutOfMemory issues that seem to occur sometimes on big screens, especially on the 10" Asus TF101 tablet.
Comments: Tests on the TF101 show that RAM usage is 3X higher than on the Galaxy S. It seems logical considering the huge scaled bitmap that has to be drawn.
Option 2: repeat 5 small images
Description: I tried to reduce the RAM used by bitmaps and to take advantage of the fact the board is just a repetition of small squares of 5 kinds. In order to make the resizing of the board view easy, I chose to make it a custom LinearLayout, containing 15 nested LinearLayout, each containing 15 images (which is certainly not the best option performance wise).
Here's what my XML layout would look like for a 4 x 4 board:
<MyCustomLinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal" >
<ImageView
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" >
</ImageView>
<ImageView
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" >
</ImageView>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal" >
<ImageView
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" >
</ImageView>
<ImageView
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" >
</ImageView>
</LinearLayout>
</MyCustomLinearLayout>
Issues: Some crashes or visual glitches for somes users. (I still don't have much info on this one)
Comments: Pros: of the option is that the memory footprint is optimized: only one instance of each of the 5 small bitmaps is kept in memory. Resizing the board is straightforward: Android does all the resizing of bitmaps and nested LinearLayouts when I resize MyCustomLinearLayout.
Cons: Too many views, which dimensions must be recomputed by the system on each zoom.
How can I improve Option 2 to keep a low RAM footprint, while respecting the other performance good practices (like not having hundreds of views) ?
EDIT: Final (?) implementation
Instead of a SurfaceView
, I went for a simple custom View
.
I was afraid that doing a lot of bitmap drawing in the onDraw()
method would cause the dragging of the board to be laggy (because onDraw()
is constantly called when the board is moved).
Turns out it is pretty smooth (maybe even a bit smoother than Option 2). As for the RAM usage, it is just a bit heavier than option 2 (comparison done using Eclipse Memory Analyzer), so it is satisfactory. I still have a few reports of OutOfMemory errors ("bitmap exceeded max VM heap size"), but I think it is inevitable when one uses bitmaps.
Here is the most significant code, where I draw the 15x15 bitmaps (initialized from resources in the constructor of the view) to build the game board:
public void drawTileAtCoordsOnCanvas(Bitmap bm, Canvas canvas, int x, int y) {
_rect.set(x * _sizeOfTile, y * _sizeOfTile, (x + 1) * _sizeOfTile, (y + 1) * _sizeOfTile);
canvas.drawBitmap(bm, null, _rect, _paint);
}
public void onDraw(Canvas canvas) {
for (int i = 0; i < _rules.BOARD_SIZE; i++) {
for (int j = 0; j < _rules.BOARD_SIZE; j++) {
switch (_rules._boardOfMultiplicators.get(i).get(j)) {
case NL:
drawTileAtCoordsOnCanvas(_bitmapNormalTile, canvas, i, j);
break;
case DL:
drawTileAtCoordsOnCanvas(_bitmapDlTile, canvas, i, j);
break;
case TL:
drawTileAtCoordsOnCanvas(_bitmapTlTile, canvas, i, j);
break;
case DW:
drawTileAtCoordsOnCanvas(_bitmapDwTile, canvas, i, j);
break;
case TW:
drawTileAtCoordsOnCanvas(_bitmapTwTile, canvas, i, j);
break;
}
}
}
drawTileAtCoordsOnCanvas(_bitmapStarTile, canvas, 7, 7);
}