在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Android/ 處理控制器輸入動作
檢測常用的手勢
優(yōu)化layout的層級
用戶輸入
管理應(yīng)用的內(nèi)存
聯(lián)系人信息
開發(fā)輔助程序
Android多媒體
添加語音功能
顯示位置地址
提供向下與橫向?qū)Ш?/span>
支持游戲控制器
訪問可穿戴數(shù)據(jù)層
處理多點觸控手勢
全屏沉浸式應(yīng)用
為多線程創(chuàng)建管理器
數(shù)據(jù)保存
Intent的發(fā)送
更新Notification
優(yōu)化下載以高效地訪問網(wǎng)絡(luò)
打印
打包可穿戴應(yīng)用
接收從其他App傳送來的數(shù)據(jù)
發(fā)送與接收消息
建立靈活動態(tài)的UI
處理鍵盤輸入
Building a Work Policy Controller
建立測試環(huán)境
創(chuàng)建表盤
分享文件
顯示Notification進度
實現(xiàn)自適應(yīng)UI流(Flows)
使用設(shè)備管理策略增強安全性
使用能感知版本的組件
執(zhí)行網(wǎng)絡(luò)操作
建立文件分享
添加移動
更新你的Security Provider來對抗SSL漏洞利用
支持鍵盤導航
創(chuàng)建和監(jiān)視地理圍欄
發(fā)送并同步數(shù)據(jù)
使用BigView樣式
無線連接設(shè)備
提供向上導航與歷史導航
最小化定期更新造成的影響
實現(xiàn)向下的導航
支持不同的屏幕大小
Android 可穿戴應(yīng)用
添加動畫
顯示聯(lián)系人頭像
使用OpenGL ES顯示圖像
處理輸入法可見性
分享文件
保持設(shè)備喚醒
淡化系統(tǒng)Bar
使用NFC分享文件
保存到Preference
Android聯(lián)系人信息與位置信息
創(chuàng)建標準的網(wǎng)絡(luò)請求
使用Drawables
管理Bitmap的內(nèi)存使用
管理Activity的生命周期
按需加載視圖
傳輸資源
為可穿戴設(shè)備創(chuàng)建自定義UI
在一個線程中執(zhí)行一段特定的代碼
性能優(yōu)化
隱藏導航欄
創(chuàng)建目錄瀏覽器
為多種大小的屏幕進行規(guī)劃
View間漸變
使用觸摸手勢
高效加載大圖
使用CursorLoader在后臺加載數(shù)據(jù)
創(chuàng)建抽屜式導航(navigation drawer)
管理音頻焦點
創(chuàng)建后臺服務(wù)
創(chuàng)建功能測試
創(chuàng)建使用Material Design的應(yīng)用
停止與重啟Activity
添加一個簡便的分享功能
啟動Activity時保留導航
TV應(yīng)用清單
創(chuàng)建向后兼容的UI
?# 優(yōu)化自定義View
創(chuàng)建單元測試
在UI上顯示Bitmap
建立OpenGL ES的環(huán)境
構(gòu)建表盤服務(wù)
JNI Tips
建立搜索界面
實現(xiàn)自定義View的繪制
使用HTTPS與SSL
按需操控BroadcastReceiver
分享簡單的數(shù)據(jù)
繪制形狀
Android位置信息
創(chuàng)建并運行可穿戴應(yīng)用
執(zhí)行 Sync Adpater
獲取最后可知位置
創(chuàng)建 Android 項目
實現(xiàn)高效的導航
退出全屏的Activity
創(chuàng)建Card
兼容音頻輸出設(shè)備
同步數(shù)據(jù)單元
傳輸數(shù)據(jù)時避免消耗大量電量
保存到文件
緩存Bitmap
提供配置 Activity
調(diào)度重復(fù)的鬧鐘
實現(xiàn)輔助功能
重復(fù)的下載是冗余的
隱藏狀態(tài)欄
實現(xiàn)自定義的網(wǎng)絡(luò)請求
規(guī)劃界面和他們之間的關(guān)系
使用Sync Adapter傳輸數(shù)據(jù)
TV應(yīng)用內(nèi)搜索
響應(yīng)觸摸事件
使用Google Cloud Messaging(已廢棄)
控制相機
Android網(wǎng)絡(luò)連接與云服務(wù)
請求分享一個文件
處理TV硬件
響應(yīng)UI可見性的變化
使用網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)
指定輸入法類型
優(yōu)化電池壽命
創(chuàng)建TV應(yīng)用
獲取聯(lián)系人列表
拖拽與縮放
啟動與停止線程池中的線程
創(chuàng)建 Sync Adpater
使用 WiFi P2P 服務(wù)發(fā)現(xiàn)
開始使用Material Design
代理至新的APIs
使用include標簽重用layouts
使得View可交互
高效顯示Bitmap
創(chuàng)建企業(yè)級應(yīng)用
Fragments之間的交互
創(chuàng)建與執(zhí)行測試用例
綜合:設(shè)計我們的樣例 App
繪制表盤
建立簡單的用戶界面
自定義動畫
開發(fā)輔助服務(wù)
避免出現(xiàn)程序無響應(yīng)ANR(Keeping Your App Responsive)
使用ViewPager實現(xiàn)屏幕滑動
設(shè)計高效的導航
Android分享操作(Building Apps with Content Sharing)
提供向后的導航
保持向下兼容
創(chuàng)建TV播放應(yīng)用
縮放View
使用 WiFi 建立 P2P 連接
Android后臺任務(wù)
連接到網(wǎng)絡(luò)
為 Notification 添加頁面
使TV應(yīng)用是可被搜索的
添加Action Bar
使用Material的主題
啟動另一個Activity
顯示正在播放卡片
適配不同的系統(tǒng)版本
輕松錄制視頻
創(chuàng)建可穿戴的應(yīng)用
創(chuàng)建自定義的布局
重新創(chuàng)建Activity
使用CursorLoader執(zhí)行查詢?nèi)蝿?wù)
使用舊的APIs實現(xiàn)新API的效果
使用備份API
安全要點
Android入門基礎(chǔ):從這里開始
保存并搜索數(shù)據(jù)
根據(jù)網(wǎng)絡(luò)連接類型來調(diào)整下載模式
使用Tabs創(chuàng)建Swipe視圖
SMP(Symmetric Multi-Processor) Primer for Android
解析 XML 數(shù)據(jù)
使用 Volley 傳輸網(wǎng)絡(luò)數(shù)據(jù)
建立ActionBar
Android交互設(shè)計
使用Intent修改聯(lián)系人信息
增加搜索功能
輕松拍攝照片
定義形狀
測試你的Activity
在 Notifcation 中接收語音輸入
與其他應(yīng)用的交互
管理系統(tǒng)UI
追蹤手勢移動
Android界面設(shè)計
執(zhí)行 Android 程序
顯示確認界面
創(chuàng)建Lists與Cards
打印HTML文檔
創(chuàng)建TV應(yīng)用
為多屏幕設(shè)計
定義Shadows與Clipping視圖
使用Fragment建立動態(tài)UI
接收Activity返回的結(jié)果
布局變更動畫
定位常見的問題
自定義ActionBar的風格
定義Layouts
發(fā)送簡單的網(wǎng)絡(luò)請求
啟動與銷毀Activity
與UI線程通信
非UI線程處理Bitmap
創(chuàng)建TV布局
提升Layout的性能
報告任務(wù)執(zhí)行狀態(tài)
判斷并監(jiān)測網(wǎng)絡(luò)連接狀態(tài)
兼容不同的設(shè)備
處理按鍵動作
優(yōu)化性能和電池使用時間
給其他App發(fā)送簡單的數(shù)據(jù)
Implementing App Restrictions
向后臺服務(wù)發(fā)送任務(wù)請求
展示Card翻轉(zhuǎn)動畫
管理ViewGroup中的觸摸事件
兼容不同的屏幕密度
通過藍牙進行調(diào)試
為可穿戴設(shè)備創(chuàng)建Notification
控制音量與音頻播放
獲取聯(lián)系人詳情
在表盤上顯示信息
提供向上的導航
滾動手勢動畫
幫助用戶在TV上找到內(nèi)容
創(chuàng)建TV導航
為索引指定App內(nèi)容
ActionBar的覆蓋疊加
Android Wear 上的位置檢測
保護安全與隱私的最佳策略
Ensuring Compatibility with Managed Profiles
解決云同步的保存沖突
獲取位置更新
創(chuàng)建List
測試程序
管理網(wǎng)絡(luò)的使用情況
為App內(nèi)容開啟深度鏈接
推薦TV內(nèi)容
建立一個Notification
管理音頻播放
設(shè)計表盤
拍照
處理控制器輸入動作
判斷并監(jiān)測設(shè)備的底座狀態(tài)與類型
處理查詢的結(jié)果
保存到數(shù)據(jù)庫
支持多個游戲控制器
創(chuàng)建 Stub Content Provider
使得ListView滑動順暢
處理數(shù)據(jù)層的事件
創(chuàng)建TV應(yīng)用的第一步
使得你的App內(nèi)容可被Google搜索
將 Notification 放成一疊
創(chuàng)建 Stub 授權(quán)器
暫停與恢復(fù)Activity
管理設(shè)備的喚醒狀態(tài)
Android圖像與動畫
打印照片
云同步
創(chuàng)建TV直播應(yīng)用
為Notification賦加可穿戴特性
提供一個Card視圖
建立請求隊列(RequestQueue)
適配不同的語言
創(chuàng)建詳情頁
測試UI組件
接收其他設(shè)備的文件
創(chuàng)建自定義View
建立第一個App
創(chuàng)建2D Picker
監(jiān)測電池的電量與充電狀態(tài)
打印自定義文檔
抽象出新的APIs
通知提示用戶
獲取文件信息
運用投影與相機視角
在IntentService中執(zhí)行后臺任務(wù)
多線程操作
創(chuàng)建一個Fragment
添加Action按鈕
在不同的 Android 系統(tǒng)版本支持控制器
維護兼容性
發(fā)送文件給其他設(shè)備
創(chuàng)建TV游戲應(yīng)用
創(chuàng)建自定義的View類
代碼性能優(yōu)化建議
Intent過濾
適配不同的屏幕

處理控制器輸入動作

編寫:heray1990 - 原文:http://developer.android.com/training/game-controllers/controller-input.html

在系統(tǒng)層面上,Android 會以 Android 按鍵碼值和坐標值的形式來報告來自游戲控制器的輸入事件。在我們的游戲應(yīng)用里,我們可以接收這些碼值和坐標值,并將它們轉(zhuǎn)化成特定的游戲行為。

當玩家將一個游戲控制器通過有線連接或者無線配對到 Android 設(shè)備時,系統(tǒng)會自動檢測控制器,將它設(shè)置成輸入設(shè)備并且開始報告它的輸入事件。我們的游戲應(yīng)用可以通過在活動的 Activity 或者被選中的 View 里調(diào)用下面這些回調(diào)方法,來接收上述輸入事件(要么在 Activity,要么在 View 中實現(xiàn)實現(xiàn)這些回調(diào)方法,不要兩個地方都實現(xiàn)回調(diào))。

建議的方法是從與用戶交互的 View 對象捕獲事件。請查看下面回調(diào)函數(shù)的對象,來獲取關(guān)于接收到輸入事件的類型:

KeyEvent:描述方向按鍵(D-pad)和游戲按鍵事件的對象。按鍵事件伴隨著一個表示特定按鍵觸發(fā)的按鍵碼值(key code),如 DPAD_DOWN 或者 BUTTON_A。我們可以通過調(diào)用 getKeyCode() 或者從按鍵事件回調(diào)方法(如 onKeyDown())來獲得按鍵碼值。

MotionEvent:描述搖桿和肩鍵運動的輸入。動作事件伴隨著一個動作碼(action code)和一系列坐標值axis values)。動作碼表示出現(xiàn)變化的狀態(tài),例如搖動一個搖桿。坐標值描述了特定物理操控的位置和其它運動屬性,例如 AXIS_X 或者 AXIS_RTRIGGER。我們可以通過調(diào)用 getAction() 來獲得動作碼,通過調(diào)用 getAxisValue() 來獲得坐標值。

這節(jié)課主要介紹如何通過實現(xiàn)上述的 View 回調(diào)方法與處理 KeyEventMotionEvent 對象,來處理常用控制器(游戲鍵盤按鍵、方向按鍵和搖桿)的輸入。

<a name="input=>

驗證游戲控制器是否已連接

在報告輸入事件的時候,Android 不會區(qū)分游戲控制器事件與非游戲控制器事件。例如,一個觸屏動作會產(chǎn)生一個表示觸摸表面上 X 坐標的 AXIS_X,但是一個搖桿動作產(chǎn)生的 AXIS_X 則表示搖桿水平移動的位置。如果我們的游戲關(guān)注游戲控制器的輸入,那么我們應(yīng)該首先檢測相應(yīng)的事件來源類型。

通過調(diào)用 getSources() 來獲得設(shè)備上支持的輸入類型的位字段,來判斷一個已連接的輸入設(shè)備是不是一個游戲控制器。我們可以通過測試以查看下面的字段是否被設(shè)置:

  • SOURCE_GAMEPAD 源類型表示輸入設(shè)備有游戲手柄按鍵(如,BUTTON_A)。注意雖然一般的游戲手柄都會有方向控制鍵,但是這個源類型并不代表游戲控制器具有 D-pad 按鈕。
  • SOURCE_DPAD 源類型表示輸入設(shè)備有 D-pad 按鈕(如,DPAD_UP)。
  • SOURCE_JOYSTICK 源類型表示輸入設(shè)備有遙控桿(如,會通過 AXIS_XAXIS_Y 記錄動作的搖桿)。

下面的一小段代碼介紹了一個 helper 方法,它的作用是讓我們檢驗已接入的輸入設(shè)備是否是游戲控制器。如果檢測到是游戲控制器,那么這個方法會獲得游戲控制器的設(shè)備 ID。然后,我們應(yīng)該將每個設(shè)備 ID 與游戲中的玩家關(guān)聯(lián)起來,并且單獨處理每個已接入的玩家的游戲操作。想更詳細地了解關(guān)于在一臺Android設(shè)備中同時支持多個游戲控制器的方法,請見支持多個游戲控制器。

public ArrayList getGameControllerIds() {
    ArrayList gameControllerDeviceIds = new ArrayList();
    int[] deviceIds = InputDevice.getDeviceIds();
    for (int deviceId : deviceIds) {
        InputDevice dev = InputDevice.getDevice(deviceId);
        int sources = dev.getSources();

        // Verify that the device has gamepad buttons, control sticks, or both.
        if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
                || ((sources & InputDevice.SOURCE_JOYSTICK)
                == InputDevice.SOURCE_JOYSTICK)) {
            // This device is a game controller. Store its device ID.
            if (!gameControllerDeviceIds.contains(deviceId)) {
                gameControllerDeviceIds.add(deviceId);
            }
        }
    }
    return gameControllerDeviceIds;
}

另外,我們可能想去檢查已接入的單個游戲控制器的輸入性能。這種檢查在某些場合會很有用,例如,我們希望游戲只用到兼容的物理操控。

用下面這些方法檢測一個游戲控制器是否支持一個特定的按鍵碼或者坐標碼:

  • 在Android 4.4(API level 19)或者更高的系統(tǒng)中,調(diào)用 hasKeys(int) 來確定游戲控制器是否支持某個按鍵碼。
  • 在Android 3.1(API level 12)或者更高的系統(tǒng)中,首先調(diào)用 getMotionRanges(),然后在每個返回的 InputDevice.MotionRange 對象中調(diào)用 getAxis() 來獲得坐標 ID。這樣就可以得到游戲控制器支持的所有可用坐標軸。

處理游戲手柄按鍵

Figure 1介紹了 Android 如何將按鍵碼和坐標值映射到實際的游戲手柄上。

http://wiki.jikexueyuan.com/project/android-training-geek/images/game-controller-profiles.png" alt="game-controller-profiles" title="Figure 1. Profile for a generic game controller." />

Figure 1. 一個常用的游戲手柄的外形

上圖的標注對應(yīng)下面的內(nèi)容:

  1. AXIS_HAT_X, AXIS_HAT_Y, DPAD_UP, DPAD_DOWN, DPAD_LEFT, DPAD_RIGHT
  2. AXIS_X, AXIS_Y, BUTTON_THUMBL
  3. AXIS_Z, AXIS_RZ, BUTTON_THUMBR
  4. BUTTON_X
  5. BUTTON_A
  6. BUTTON_Y
  7. BUTTON_B
  8. BUTTON_R1
  9. AXIS_RTRIGGER, AXIS_THROTTLE
  10. AXIS_LTRIGGER, AXIS_BRAKE
  11. BUTTON_L1

游戲手柄產(chǎn)生的通用的按鍵碼包括 BUTTON_A、BUTTON_BBUTTON_SELECTBUTTON_START。當按下 D-pad 中間的交叉按鍵時,一些游戲控制器會觸發(fā) DPAD_CENTER 按鍵碼。我們的游戲可以通過調(diào)用 getKeyCode() 或者從按鍵事件回調(diào)(如onKeyDown())得到按鍵碼。如果一個事件與我們的游戲相關(guān),那么將其處理成一個游戲動作。Table 1列出供大多數(shù)通用游戲手柄按鈕使用的推薦游戲動作。

Table 1. 供游戲手柄使用的推薦游戲動作

游戲動作 按鍵碼
在主菜單中啟動游戲,或者在游戲過程中暫停/取消暫停 BUTTON_START
顯示菜單 BUTTON_SELECTKEYCODE_MENU
跟Android導航設(shè)計指導中的Back導航行為一樣 KEYCODE_BACK
返回到菜單中上一項 BUTTON_B
確認選擇,或者執(zhí)行主要的游戲動作 BUTTON_ADPAD_CENTER

* 我們的游戲不應(yīng)該依賴于Start、Select或者Menu按鍵的存在。

Tip: 可以考慮在游戲中提供一個配置界面,使得用戶可以個性化游戲控制器與游戲動作的映射。

下面的代碼介紹了如何重寫 onKeyDown() 來將 BUTTON_ADPAD_CENTER 按鈕結(jié)合到一個游戲動作。

public class GameView extends View {
    ...

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean handled = false;
        if ((event.getSource() & InputDevice.SOURCE_GAMEPAD)
                == InputDevice.SOURCE_GAMEPAD) {
            if (event.getRepeatCount() == 0) {
                switch (keyCode) {
                    // Handle gamepad and D-pad button presses to
                    // navigate the ship
                    ...

                    default:
                         if (isFireKey(keyCode)) {
                             // Update the ship object to fire lasers
                             ...
                             handled = true;
                         }
                     break;
                }
            }
            if (handled) {
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

    private static boolean isFireKey(int keyCode) {
        // Here we treat Button_A and DPAD_CENTER as the primary action
        // keys for the game.
        return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                || keyCode == KeyEvent.KEYCODE_BUTTON_A;
    }
}

Note: 在 Android 4.2(API level 17)和更低版本的系統(tǒng)中,系統(tǒng)默認會把 BUTTON_A 當作 Android Back返回)鍵。如果我們的應(yīng)用支持這些 Android 版本,請確保將 BUTTON_A 轉(zhuǎn)換成主要的游戲動作。引用 Build.VERSION.SDK_INT 值來決定設(shè)備上當前的 Android SDK 版本。

處理 D-pad 輸入

四方向的方向鍵(D-pad)在很多游戲控制器中是一種很常見的物理控制。Android 將 D-pad 的上和下按鍵按壓報告成 AXIS_HAT_Y 事件(范圍從-1.0(上)到1.0(下)),將 D-pad 的左或者右按鍵按壓報告成 AXIS_HAT_X 事件(范圍從-1.0(左)到1.0(右))。

一些游戲控制器會將 D-pad 按壓報告成一個按鍵碼。如果我們的游戲有檢測 D-pad 的按壓,那么我們應(yīng)該將坐標值事件和 D-pad 按鍵碼當成一樣的輸入事件,如 table 2 介紹的一樣。

Table 2. D-pad 按鍵碼和坐標值的推薦默認游戲動作。

游戲動作 D-pad 按鍵碼 坐標值
向上 KEYCODE_DPAD_UP AXIS_HAT_Y (從 0 到 -1.0)
向下 KEYCODE_DPAD_DOWN AXIS_HAT_Y (從 0 到 1.0)
向左 KEYCODE_DPAD_LEFT AXIS_HAT_X (從 0 到 -1.0)
向右 KEYCODE_DPAD_RIGHT AXIS_HAT_X (從 0 到 1.0)

下面的代碼介紹了通過一個 helper 類,來檢查從一個輸入事件來決定 D-pad 方向的坐標值和按鍵碼。

public class Dpad {
    final static int UP       = 0;
    final static int LEFT     = 1;
    final static int RIGHT    = 2;
    final static int DOWN     = 3;
    final static int CENTER   = 4;

    int directionPressed = -1; // initialized to -1

    public int getDirectionPressed(InputEvent event) {
        if (!isDpadDevice(event)) {
           return -1;
        }

        // If the input event is a MotionEvent, check its hat axis values.
        if (event instanceof MotionEvent) {

            // Use the hat axis value to find the D-pad direction
            MotionEvent motionEvent = (MotionEvent) event;
            float xaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X);
            float yaxis = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y);

            // Check if the AXIS_HAT_X value is -1 or 1, and set the D-pad
            // LEFT and RIGHT direction accordingly.
            if (Float.compare(xaxis, -1.0f) == 0) {
                directionPressed =  Dpad.LEFT;
            } else if (Float.compare(xaxis, 1.0f) == 0) {
                directionPressed =  Dpad.RIGHT;
            }
            // Check if the AXIS_HAT_Y value is -1 or 1, and set the D-pad
            // UP and DOWN direction accordingly.
            else if (Float.compare(yaxis, -1.0f) == 0) {
                directionPressed =  Dpad.UP;
            } else if (Float.compare(yaxis, 1.0f) == 0) {
                directionPressed =  Dpad.DOWN;
            }
        }

        // If the input event is a KeyEvent, check its key code.
        else if (event instanceof KeyEvent) {

           // Use the key code to find the D-pad direction.
            KeyEvent keyEvent = (KeyEvent) event;
            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                directionPressed = Dpad.LEFT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                directionPressed = Dpad.RIGHT;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                directionPressed = Dpad.UP;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                directionPressed = Dpad.DOWN;
            } else if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
                directionPressed = Dpad.CENTER;
            }
        }
        return directionPressed;
    }

    public static boolean isDpadDevice(InputEvent event) {
        // Check that input comes from a device with directional pads.
        if ((event.getSource() & InputDevice.SOURCE_DPAD)
             != InputDevice.SOURCE_DPAD) {
             return true;
         } else {
             return false;
         }
     }
}

我們可以在任意想要處理 D-pad 輸入(例如,在 onGenericMotionEvent() 或者 onKeyDown() 回調(diào)函數(shù))的地方使用這個 helper 類。

例如:

Dpad mDpad = new Dpad();
...
@Override
public boolean onGenericMotionEvent(MotionEvent event) {

    // Check if this event if from a D-pad and process accordingly.
    if (Dpad.isDpadDevice(event)) {

       int press = mDpad.getDirectionPressed(event);
       switch (press) {
            case LEFT:
                // Do something for LEFT direction press
                ...
                return true;
            case RIGHT:
                // Do something for RIGHT direction press
                ...
                return true;
            case UP:
                // Do something for UP direction press
                ...
                return true;
            ...
        }
    }

    // Check if this event is from a joystick movement and process accordingly.
    ...
}

處理搖桿動作

當玩家移動游戲控制器上的搖桿時,Android 會報告一個包含 ACTION_MOVE 動作碼和更新?lián)u桿在坐標軸的位置的 MotionEvent。我們的游戲可以使用 MotionEvent 提供的數(shù)據(jù)來確定是否發(fā)生搖桿的動作。

注意到搖桿移動會在單個對象中批處理多個移動示例。MotionEvent 對象包含每個搖桿坐標當前的位置和每個坐標軸上的多個歷史位置。當用 ACTION_MOVE 動作碼(例如搖桿移動)來報告移動事件時,Android 會高效地批處理坐標值。由坐標值組成的不同的歷史值比當前的坐標值要舊,比之前報告的任意移動事件要新。詳情見 MotionEvent 參考文檔。

我們可以使用歷史信息,根據(jù)搖桿輸入更精確地表達游戲?qū)ο蟮幕顒?。調(diào)用 getAxisValue() 或者 getHistoricalAxisValue() 來獲取現(xiàn)在和歷史的值。我們也可以通過調(diào)用 getHistorySize() 來找到搖桿事件的歷史點號碼。

下面的代碼介紹了如何重寫 onGenericMotionEvent() 回調(diào)函數(shù)來處理搖桿輸入。我們應(yīng)該首先處理歷史坐標值,然后處理當前值。

public class GameView extends View {

    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {

        // Check that the event came from a game controller
        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
                InputDevice.SOURCE_JOYSTICK &&
                event.getAction() == MotionEvent.ACTION_MOVE) {

            // Process all historical movement samples in the batch
            final int historySize = event.getHistorySize();

            // Process the movements starting from the
            // earliest historical position in the batch
            for (int i = 0; i < historySize; i++) {
                // Process the event at historical position i
                processJoystickInput(event, i);
            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1);
            return true;
        }
        return super.onGenericMotionEvent(event);
    }
}

在使用搖桿輸入之前,我們需要確定搖桿是否居中,然后計算相應(yīng)的坐標移動距離。一般搖桿會有一個平面區(qū),即在坐標 (0, 0) 附近一個值范圍內(nèi)的坐標點都被當作是中點。如果 Android 系統(tǒng)報告坐標值掉落在平面區(qū)內(nèi),那么我們應(yīng)該認為控制器處于靜止(即沿著 x、y 兩個坐標軸都是靜止的)。

下面的代碼介紹了一個用于計算沿著每個坐標軸的移動距離的 helper 方法。我們將在后面討論的 processJoystickInput() 方法中調(diào)用這個 helper 方法。

private static float getCenteredAxis(MotionEvent event,
        InputDevice device, int axis, int historyPos) {
    final InputDevice.MotionRange range =
            device.getMotionRange(axis, event.getSource());

    // A joystick at rest does not always report an absolute position of
    // (0,0). Use the getFlat() method to determine the range of values
    // bounding the joystick axis center.
    if (range != null) {
        final float flat = range.getFlat();
        final float value =
                historyPos < 0 ? event.getAxisValue(axis):
                event.getHistoricalAxisValue(axis, historyPos);

        // Ignore axis values that are within the 'flat' region of the
        // joystick axis center.
        if (Math.abs(value) > flat) {
            return value;
        }
    }
    return 0;
}

將它們都放在一起,下面是我們?nèi)绾卧谟螒蛑刑幚頁u桿移動:

private void processJoystickInput(MotionEvent event,
        int historyPos) {

    InputDevice mInputDevice = event.getDevice();

    // Calculate the horizontal distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat axis, or the right control stick.
    float x = getCenteredAxis(event, mInputDevice,
            MotionEvent.AXIS_X, historyPos);
    if (x == 0) {
        x = getCenteredAxis(event, mInputDevice,
                MotionEvent.AXIS_HAT_X, historyPos);
    }
    if (x == 0) {
        x = getCenteredAxis(event, mInputDevice,
                MotionEvent.AXIS_Z, historyPos);
    }

    // Calculate the vertical distance to move by
    // using the input value from one of these physical controls:
    // the left control stick, hat switch, or the right control stick.
    float y = getCenteredAxis(event, mInputDevice,
            MotionEvent.AXIS_Y, historyPos);
    if (y == 0) {
        y = getCenteredAxis(event, mInputDevice,
                MotionEvent.AXIS_HAT_Y, historyPos);
    }
    if (y == 0) {
        y = getCenteredAxis(event, mInputDevice,
                MotionEvent.AXIS_RZ, historyPos);
    }

    // Update the ship object based on the new x and y values
}

為了支持除了單個搖桿之外更多復(fù)雜的功能,按照下面的做法:

  • 處理兩個控制器搖桿。很多游戲控制器左右兩邊都有搖桿。對于左搖桿,Android 會報告水平方向的移動為 AXIS_X 事件,垂直方向的移動為 AXIS_Y 事件。對于右搖桿,Android 會報告水平方向的移動為 AXIS_Z 事件,垂直方向的移動為 AXIS_RZ 事件。確保在代碼中處理兩個搖桿。

  • 處理肩鍵按壓(但需要提供另一種輸入方法)。一些控制器會有左右肩鍵。如果存在這些按鍵,那么 Android 報告左肩鍵按壓為一個 AXIS_LTRIGGER 事件,右肩鍵按壓為一個 AXIS_RTRIGGER 事件。在 Android 4.3(API level 18)中,一個產(chǎn)生了 AXIS_LTRIGGER 事件的控制器也會報告一個完全一樣的 AXIS_BRAKE 坐標值。同樣,AXIS_RTRIGGER 對應(yīng) AXIS_GAS。Android 會報告模擬按鍵按壓為從 0.0(釋放)到 1.0(按下)的標準值。并不是所有的控制器都有肩鍵,所以需要允許玩家用其它按鈕來執(zhí)行那些游戲動作。