接續上篇: 花栗鼠柑仔店: Android筆記: 讀書心得#6
參考資料: Amazon.com: Hello, Android: Introducing Google's Mobile Development Platform (Pragmatic Programmers) (9781934356562): Ed Burnette: Books
這段內容主要為:
第4章 2D繪圖
4.4 處理使用者輸入
4.4.1 使用者選到的方格 (定義與更新)
4.4.2 處理數字輸入
4.4.3 加入提示
4.4.4 搖晃畫面 (假如輸入錯誤的時候)
第4章: 2D繪圖(Exploring 2D Graphics)
4.4 處理使用者輸入
Android與iPhone程式設計有一比較不同的是Android電話有各種不同的形狀與尺寸, 甚至有不同的輸入方式. 有一些可能有鍵盤(Keyboard), 方向鍵(D-pad), 觸控式銀幕(touch screen), 軌跡球(trackball), 或混合上述等等. 一個好的Android程式必須要可以支援不同種類的輸入硬體, 也必須要可支援不同的銀幕解析度.
4.4.1 使用者選到的方格 (定義與更新)
第一步是實作一個使用者選到游標, 這游標當使用者輸入資料時會把數字更新上去, 一樣是修改PuzzleView中的onDraw()加上如下的程式碼:
## src/org/example/sudoku/PuzzleView.java
// Draw the selection...
Log.d(TAG, "selRect=" + selRect);
Paint selected = new Paint();
selected.setColor(getResources().getColor(R.color.puzzle_selected));
canvas.drawRect(selRect, selected);
這邊使用在onSizeChanged()中使用者選到的方格(salRect)將其畫上一個透明的顏色上去. 執行結果會像這樣:
接下來處理onKeyDown()事件, 根據使用者輸入來修正selRect的位置:
## src/org/example/sudoku/PuzzleView.java
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.d(TAG, "onKeyDown: keycode=" + keyCode + ", event="+ event);
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
select(selX, selY - 1);
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
select(selX, selY + 1);
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
select(selX - 1, selY);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
select(selX + 1, selY);
break;
default:
return super.onKeyDown(keyCode, event);
}
return true;
}
上面這段主要是擷取使用者以方向鍵(D-pad)輸入上下左右時, 以select()來修正游標的位置. 假如要處理軌跡球(trackball), 也可以覆寫onTrackballEvent()來處理. 假如不覆寫onTrackballEvent(), Android也會自動轉換對應的到方向鍵(D-pad)的事件, 我們不需太過擔心.
在select()中, 用新的(x, y)畫面格子相對位置判斷座標是否超過銀幕, 另外用getRect()計算selRect新的座標:
## src/org/example/sudoku/PuzzleView.java
private void select(int x, int y) {
invalidate(selRect);
selX = Math.min(Math.max(x, 0), 8);
selY = Math.min(Math.max(y, 0), 8);
getRect(selX, selY, selRect);
invalidate(selRect);
}
除了中間利用Math計算新的選到格子有無超過畫面外, 這邊呼叫了兩次 invalidate(), 這invalidate()主要是通知Android哪一些部分需要重繪, 第一個是通知Android舊的地方要重繪, 第二個是新的地方也需要重繪, 剩下的就交給Android處理.
這邊有一個很重要的概念: "除了在onDraw()方法外, 在其他方法不要直接呼叫任何繪圖的功能", 只需要用invalidate()來標示哪一些部分需要重繪, Android window manager會整合所有需要重繪的部分, 自動幫你呼叫onDraw()重繪, 且會最佳化只根據你設定需要重繪的部分處理. (也可以嘗試著每次更新都invalidate重繪整個畫面, 但程式的操作會變得延遲很重, 所以建議是由我們自訂選擇最恰當的部分invalidate, 畢竟人腦是最聰明的)
4.4.2 處理數字輸入
承續之前在onKeyDown()處理使用者上下左右移動, 現在加入對應的case處理輸入0~9的數字:
## src/org/example/sudoku/PuzzleView.java
case KeyEvent.KEYCODE_0:
case KeyEvent.KEYCODE_SPACE: setSelectedTile(0); break;
case KeyEvent.KEYCODE_1: setSelectedTile(1); break;
case KeyEvent.KEYCODE_2: setSelectedTile(2); break;
case KeyEvent.KEYCODE_3: setSelectedTile(3); break;
case KeyEvent.KEYCODE_4: setSelectedTile(4); break;
case KeyEvent.KEYCODE_5: setSelectedTile(5); break;
case KeyEvent.KEYCODE_6: setSelectedTile(6); break;
case KeyEvent.KEYCODE_7: setSelectedTile(7); break;
case KeyEvent.KEYCODE_8: setSelectedTile(8); break;
case KeyEvent.KEYCODE_9: setSelectedTile(9); break;
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_DPAD_CENTER:
game.showKeypadOrError(selX, selY);
break;
這邊除了0~9之外, 要是使用這按下Enter鍵或方向鍵的中鍵時, 銀幕上跳出輸入小鍵盤給使用者輸入數字. 假如要支援觸碰銀幕, 可以在PuzzleView中加入處理onTouchEvent(), 讓使用者點到某一個格, 也會跳出小鍵盤讓使用者輸入數字, 主要的方式也是用使用者觸碰到的X, Y座標去除以每格的寬與高來算出使用者按到哪一格:
## src/org/example/sudoku/PuzzleView.java
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_DOWN)
return super.onTouchEvent(event);
select((int) (event.getX() / width), (int) (event.getY() / height));
game.showKeypadOrError(selX, selY);
Log.d(TAG, "onTouchEvent: x " + selX + ", y " + selY);
return true;
}
最後, 所有的處理都導到同一個地方setSelectedTile( ), 設定使用者按下的數字:
## src/org/example/sudoku/PuzzleView.java
public void setSelectedTile(int tile) {
if (game.setTileIfValid(selX, selY, tile)) {
invalidate();// may change hints
} else {
// Number is not valid for this tile
Log.d(TAG, "setSelectedTile: invalid: " + tile);
}
}
showKeypadOrError( ) 與 setTileIfValid( ) 下一章會提到, 主要是與顯示讓玩家輸入的小鍵盤, 這邊有呼叫一個invalidate()重繪整個視窗, 主要是下一節有提到"顯示提示"有關, 這邊有必要要整個重繪畫面.
4.4.3 加入提示
這邊加入一個小功能, 不幫玩家整個解開但是給點小提示讓整個遊戲較容易解開, 這邊實作的是根據目前該格"可輸入的數字"的數目來提示, 假如只剩下2個數字可以輸入, 就顯示puzzle_hint_2的顏色(淺綠), 假如該格只剩下1個數字可以輸入, 就顯示puzzle_hint_1的顏色(深綠), 假如該格沒有剩下數字可以輸入(也就是有問題啦, 應該有那邊玩家算錯了), 就顯示puzzle_hint_0的顏色(紅色), 這樣可以提醒玩家哪些格子已經快要解開了, 可以節省找的時間, 這段Code加在onDraw()中:
## src/org/example/sudoku/PuzzleView.java
// Draw the hints...
// Pick a hint color based on #moves left
Paint hint = new Paint();
int c[] = { getResources().getColor(R.color.puzzle_hint_0),
getResources().getColor(R.color.puzzle_hint_1),
getResources().getColor(R.color.puzzle_hint_2), };
Rect r = new Rect();
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
int movesleft = 9 - game.getUsedTiles(i, j).length;
if (movesleft < c.length) {
getRect(i, j, r);
hint.setColor(c[movesleft]);
canvas.drawRect(r, hint);
}
}
}
執行的結果會類似如下:
4.4.4 搖晃畫面 (假如輸入錯誤的時候)
假如玩家嘗試著輸入一個很明顯的錯誤數字時, 比如在3x3格子中輸入一個已經存在的數字, 我們可以搖晃一下畫面提醒使用者(好玩), 首先在setSelectedTile()中加入一個假如輸入錯誤數字的處理:
## src/org/example/sudoku/PuzzleView.java
Log.d(TAG, "setSelectedTile: invalid: " + tile);
startAnimation(AnimationUtils.loadAnimation(game, R.anim.shake));
上面有用到一個resource R.anim.shake, 可定義在res/anim/shake.xml, 搖晃畫面時間 1,000 milliseconds (1秒) 搖晃的程度(X偏移)是 10pixels:
## res/anim/shake.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:toXDelta="10"
android:duration="1000"
android:interpolator="@anim/cycle_7" />
另外一個修飾(interpolator)可以修飾動畫的重複的次數, 或速度與加速之類的修飾, 這邊我們希望搖晃可以持續7次, 所以定義cycle_7.xml搭配上面的shake.xml一起使用:
## res/anim/cycle_7.xml
<?xml version="1.0" encoding="utf-8"?>
<cycleInterpolator
xmlns:android="http://schemas.android.com/apk/res/android"
android:cycles="7" />
至此, 使用者介面已經完成到一個階段, 下一節說明上面沒有完整說明的程式運作細節, 比如判定某格玩家輸入錯誤等等.
(第4章 2D繪圖 未完待續)
沒有留言:
張貼留言