Android中物理输入设备的接入与使用
Android可以使用蓝牙接入手柄,蓝牙接键盘、OTG接键盘鼠标。本文整理了关于如何处理这些外部设备的输入信息的方法。
设备接入后,Android系统会做一次中转。,把具体的事件按照传统的Android事件做分发。
作为开发者,我们要处理以上全部的硬件事件,只需要关注View中的三个函数即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class PhysicalView extends View { ... @Override public boolean dispatchGenericMotionEvent(MotionEvent event) { return handle; } @Override public boolean dispatchKeyEvent(KeyEvent event) { return handle; } @Override public boolean dispatchCapturedPointerEvent(MotionEvent event) { return handle; } } |
使用时的注意点是,
上述的几个方法是以
返回true,即消费了事件,阻止了默认分发行为。
以下内容是我阅读文档+尝试+请教已有经验的同事整理出来的。
手柄事件
回调dispatchGenericMotionEvent、dispatchKeyEvent
如何判断是手柄设备?
1 2 3 4 5 6 7 | // isGamepad(event.getDevice()) public final boolean isGamepad(@Nullable InputDevice dev) { if (dev == null) return false; int sources = dev.getSources(); return ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) && ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK); } |
手柄输入有哪些裸数据?
手柄有这些按键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | KeyEvent.KEYCODE_BUTTON_A: KeyEvent.KEYCODE_BUTTON_B: KeyEvent.KEYCODE_BUTTON_X: KeyEvent.KEYCODE_BUTTON_Y: KeyEvent.KEYCODE_BUTTON_L1: KeyEvent.KEYCODE_BUTTON_R1: KeyEvent.KEYCODE_BUTTON_L2: KeyEvent.KEYCODE_BUTTON_R2: KeyEvent.KEYCODE_BACK: KeyEvent.KEYCODE_BUTTON_SELECT: KeyEvent.KEYCODE_BUTTON_START: KeyEvent.KEYCODE_BUTTON_THUMBL: KeyEvent.KEYCODE_BUTTON_THUMBR: KeyEvent.KEYCODE_DPAD_UP: KeyEvent.KEYCODE_DPAD_DOWN: KeyEvent.KEYCODE_DPAD_LEFT: KeyEvent.KEYCODE_DPAD_RIGHT: |
手柄一般有左右手柄球、上下左右方向键。具体可以是这样处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | //left control ball float axisX = event.getAxisValue(MotionEvent.AXIS_X); float axisY = event.getAxisValue(MotionEvent.AXIS_Y); if (Float.compare(axisX, oldAxisX) != 0 || Float.compare(axisY, oldAxisY) != 0) { // } oldAxisX = axisX; oldAxisY = axisY; // right control ball float axisZ = event.getAxisValue(MotionEvent.AXIS_Z); float axisRZ = event.getAxisValue(MotionEvent.AXIS_RZ); if (Float.compare(axisZ, oldAxisZ) != 0 || Float.compare(axisRZ, oldAxisRZ) != 0) { // } oldAxisZ = axisZ; oldAxisRZ = axisRZ; // direction left right float axisHatX = event.getAxisValue(MotionEvent.AXIS_HAT_X); if (Float.compare(axisHatX, -1.0f) == 0) {// left // } else if (Float.compare(axisHatX, 1.0f) == 0) {// right // } else if (Float.compare(axisHatX, 0f) == 0) { if (Float.compare(oldAxisHatX, -1.0f) == 0) {// release left // } else if (Float.compare(oldAxisHatX, 1.0f) == 0) {// release right // } } oldAxisHatX = axisHatX; // direction up down float axisHatY = event.getAxisValue(MotionEvent.AXIS_HAT_Y); if (Float.compare(axisHatY, -1.0f) == 0) {// up // } else if (Float.compare(axisHatY, 1.0f) == 0) {// down // } else if (Float.compare(axisHatY, 0f) == 0) { if (Float.compare(oldAxisHatY, -1.0f) == 0) {// release up // } else if (Float.compare(oldAxisHatY, 1.0f) == 0) {// release down // } } oldAxisHatY = axisHatY; |
手柄模式?
值得注意的是手柄一般可以切模式。一些资料可能会这样介绍,在Android中也有概念会简化化成,能不能拿到某个按键的的范围。
1 2 3 4 5 6 7 | public final boolean isSupportMode(InputDevice dev){ if(dev == null)return false; if(dev.getMotionRange(MotionEvent.AXIS_LTRIGGER) != null && dev.getMotionRange(MotionEvent.AXIS_RTRIGGER) != null){ return true; } return dev.getMotionRange(MotionEvent.AXIS_GAS) != null && dev.getMotionRange(MotionEvent.AXIS_BRAKE) != null; } |
我弄了几个手柄,觉得这种方式不可靠。更为健壮的做法是,直观接受系统发出来的事件,按照标准的规格处理就好。
在硬件差异的屏蔽上,系统做了很多努力,这里没必要在手动做一层拦截(拦截存在误判)。
键盘事件
回调dispatchKeyEvent
如何判断是键盘设备?
1 2 3 4 5 6 | // isKeyboard(event.getDevice()) public final boolean isKeyboard(@Nullable InputDevice dev) { if (dev == null) return false; return (dev.getSources() & InputDevice.SOURCE_KEYBOARD) != 0 && (dev.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC); } |
Android的键盘码是在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>YESHEN</title> <style type="text/css"> body { margin: 16px; } </style> </head> <body> <input id="yeshen-log" placeholder="Click here, then press down a key." size="40"> </body> <script type="text/javascript"> const input = document.getElementById('yeshen-log'); input.addEventListener('keydown', function logKey(e) { console.log(e.key, e.keyCode,e.keyCode.toString(16)); }); </script> </html> |
H5上的键盘码可以如上这样看。我在Android的API中未能找到
鼠标事件
回调dispatchGenericMotionEvent;dispatchCapturedPointerEvent(Android8.0+支持鼠标捕获)
如何判断是鼠标设备?
1 2 3 4 5 6 7 8 | // isMouse(event.getDevice()) public final boolean isMouse(@Nullable InputDevice dev) { if (dev == null) return false; return (dev.getSources() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE && (dev.getSources() & (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_JOYSTICK)) == 0; } // all message from dispatchCapturedPointerEvent |
如何做鼠标捕获
1 2 3 | # 参考 https://developer.android.com/training/gestures/movement#pointer-capture https://stackoverflow.com/a/12743948 |
1 2 3 4 5 6 7 8 9 | private boolean pointerCapture(View v) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false; if (!v.hasPointerCapture()) { v.requestPointerCapture(); return true; } return false; } // after PointerCapture,mouse event will dispatch by dispatchCapturedPointerEvent |
需要注意的是,插拔鼠标的时候,会有大量的鼠标事件产生,处理鼠标事件的代码耗时越短越好。
什么是鼠标捕获呢?
要从什么是鼠标开始说起。Android中有个累是专门处理指针的,它会获取鼠标的物理输入,然后转化成相对坐标,在屏幕上绘制出来。
在Android8+之后开放了View获取鼠标原始数据的方法。也就是说,我们可以自己处理鼠标的绘制与显示。
鼠标捕获的使用场景主要是在FPS游戏中,鼠标的方向就是枪头的方向。
鼠标有哪些原始数据?
- 左/右/中间 鼠标是否按下的事件
1 2 3 4 5 6 7 8 | if(event.getActionMasked()==MotionEvent.ACTION_BUTTON_PRESS || event.getActionMasked()==MotionEvent.ACTION_DOWN){ if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {// left mouse button } if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {//middle mouse button } if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {//right mouse button } } |
- 中间滚轮滚动的事件
1 2 3 | if(event.getActionMasked()==MotionEvent.ACTION_SCROLL){ float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) } |
- 鼠标移动的事件
1 2 | float axisX = event.getAxisValue(MotionEvent.AXIS_X); float axisY = event.getAxisValue(MotionEvent.AXIS_Y); |