I'm late to this question but coming across a similar problem recently, I decided to create my own custom board view library to address this problem. It adds more flexibility to what you can do and it leads to a more straightforward implementation.
Here are some snippets of code showing how it works. First, it is easy to draw the board grid on a custom view's onDraw()
method:
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i <= numRows; i++) {
//For custom grid sizes, y can't be equal the board size or the line drawn won't show
int y = Math.min(boardHeight - 1, i * tileSize);
canvas.drawLine(0, y, boardWidth, y, boardPaint);
}
for (int i = 0; i <= numCols; i++) {
//For custom grid sizes, x can't be equal the board size or the line drawn won't show
int x = Math.min(boardWidth - 1, i * tileSize);
canvas.drawLine(x, 0, x, boardHeight, boardPaint);
}
}
If you have a String[][]
array of strings, here is how you could set the letters, assuming the views for each tile have already been placed on the board:
public void setLetterBoard(String[][] letterBoard) {
if (letterBoard.length != getNumRows()
|| letterBoard[0].length != getNumCols()) {
setBoardSize(letterBoard.length, letterBoard[0].length);
}
int row, col;
for (int i=0; i < getChildCount(); i++) {
row = i / getNumCols();
col = i % getNumCols();
LetterTileView child = (LetterTileView) getChildAt(i);
child.setLetter(letterBoard[row][col]);
}
}
Then you need more complex work to actually handle the touch and dragging over the board tiles to select the words:
@Override
public boolean onTouchEvent(MotionEvent event) {
float X = event.getX();
float Y = event.getY();
int row = (int) (Y / getTileSize());
int col = (int) (X / getTileSize());
View child = getChildAt(row, col);
//Exit on invalid touches
if (event.getActionMasked() != MotionEvent.ACTION_UP
&& (row >= getNumRows()
|| col >= getNumCols()
|| child == null)) {
return true;
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
Tile currentTile = new Tile(row, col, child);
if (currentSelectedWord == null) {
currentSelectedWord = new SelectedWord(currentTile);
} else if (!currentTile.equals(currentSelectedWord.lastTile)
&& currentSelectedWord.isTileValid(currentTile)) {
if (!currentSelectedWord.isTileAllowed(currentTile)) {
//Clear the status of the old selection
updateTiles(currentSelectedWord.selectedTiles, false, false);
//If the current tile is valid but not allowed for the current word selection,
//start a new selection that matches the tile
currentSelectedWord = new SelectedWord(currentSelectedWord.getInitialTile());
}
List<Tile> tiles = getTilesBetween(currentSelectedWord.lastTile, currentTile);
if (tiles.size() > 0) {
currentSelectedWord.addTiles(tiles);
}
}
updateTiles(currentSelectedWord.selectedTiles, true, false);
break;
case MotionEvent.ACTION_UP:
if (currentSelectedWord != null) {
boolean isValidSelection = (listener != null && listener.onWordSelected(currentSelectedWord.toBoardWord()));
updateTiles(currentSelectedWord.selectedTiles, false, isValidSelection);
if (isValidSelection) {
selectedWords.add(currentSelectedWord);
}
currentSelectedWord = null;
}
break;
default:
return false;
}
return true;
}
The methods isTileValid()
and isTileAllowed()
make sure the tile the user is trying to select is in an allowed direction and has not been previously selected. Finally, the getTilesBetween()
return all the valid tiles between the first tile the user touched and the one he/she is currently touching.
Hope it helps!