หากคุณรองรับเกมคอนโทรลเลอร์ในเกม คุณมีหน้าที่รับผิดชอบในการตรวจสอบว่าเกมตอบสนองต่อคอนโทรลเลอร์อย่างสม่ำเสมอในอุปกรณ์ที่ใช้ Android เวอร์ชันต่างๆ ซึ่งจะช่วยให้เกมเข้าถึงผู้ชมได้กว้างขึ้น และผู้เล่นจะเพลิดเพลินกับประสบการณ์การเล่นเกมที่ราบรื่นด้วยคอนโทรลเลอร์ได้แม้ว่าจะเปลี่ยนหรืออัปเกรดอุปกรณ์ Android
บทเรียนนี้จะแสดงวิธีใช้ API ที่มีใน Android 4.1 ขึ้นไปในลักษณะที่เข้ากันได้แบบย้อนหลัง ซึ่งจะช่วยให้เกมรองรับฟีเจอร์ต่อไปนี้ในอุปกรณ์ที่ใช้ Android 3.1 ขึ้นไป
- เกมสามารถตรวจหาได้ว่ามีการเพิ่ม เปลี่ยนแปลง หรือนำเกมคอนโทรลเลอร์ใหม่ออก
- เกมสามารถค้นหาความสามารถของเกมคอนโทรลเลอร์ได้
- เกมสามารถจดจำเหตุการณ์การเคลื่อนไหวที่เข้ามาจากเกมคอนโทรลเลอร์ได้
เตรียมพร้อมที่จะแยก API ออกเพื่อรองรับเกมคอนโทรลเลอร์
สมมติว่าคุณต้องการตรวจสอบว่าสถานะการเชื่อมต่อของเกมคอนโทรลเลอร์มีการเปลี่ยนแปลงในอุปกรณ์ที่ใช้ Android 3.1 (ระดับ API 12) หรือไม่ อย่างไรก็ตาม API จะใช้ได้ใน Android 4.1 (API ระดับ 16) ขึ้นไปเท่านั้น ดังนั้นคุณจึงต้องสร้างการใช้งานที่รองรับ Android 4.1 ขึ้นไป พร้อมกับกลไกการทำงานสำรองที่รองรับ Android 3.1 ถึง Android 4.0
ตารางที่ 1 จะแสดงความแตกต่างในการรองรับเกมคอนโทรลเลอร์ระหว่าง Android 3.1 (API ระดับ 12) กับ 4.1 (API ระดับ 16) เพื่อช่วยคุณพิจารณาว่าฟีเจอร์ใดต้องใช้กลไกการทำงานสำรองสำหรับเวอร์ชันที่ต่ำกว่า
ตารางที่ 1 API สำหรับการรองรับเกมคอนโทรลเลอร์ใน Android เวอร์ชันต่างๆ
| ข้อมูลคอนโทรลเลอร์ | API คอนโทรลเลอร์ | ระดับ API 12 | ระดับ API 16 |
|---|---|---|---|
| การระบุอุปกรณ์ | getInputDeviceIds() |
• | |
getInputDevice() |
• | ||
getVibrator() |
• | ||
SOURCE_JOYSTICK |
• | • | |
SOURCE_GAMEPAD |
• | • | |
| สถานะการเชื่อมต่อ | onInputDeviceAdded() |
• | |
onInputDeviceChanged() |
• | ||
onInputDeviceRemoved() |
• | ||
| การระบุเหตุการณ์การป้อนข้อมูล | การกด D-pad (KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_CENTER) |
• | • |
การกดปุ่มเกมคอนโทรลเลอร์ (BUTTON_A, BUTTON_B, BUTTON_THUMBL, BUTTON_THUMBR, BUTTON_SELECT, BUTTON_START, BUTTON_R1, BUTTON_L1, BUTTON_R2, BUTTON_L2) |
• | • | |
การเคลื่อนไหวของจอยสติ๊กและสวิตช์หมวก (AXIS_X, AXIS_Y, AXIS_Z, AXIS_RZ, AXIS_HAT_X, AXIS_HAT_Y) |
• | • | |
การกดทริกเกอร์แอนะล็อก (AXIS_LTRIGGER, AXIS_RTRIGGER) |
• | • |
คุณสามารถใช้แอบสแตรกชันเพื่อสร้างการรองรับเกมคอนโทรลเลอร์ที่คำนึงถึงเวอร์ชันและทำงานได้ในทุกแพลตฟอร์ม แนวทางนี้เกี่ยวข้องกับขั้นตอนต่อไปนี้
- กำหนดอินเทอร์เฟซ Java ตัวกลางที่แยกการใช้งานฟีเจอร์เกมคอนโทรลเลอร์ที่เกมของคุณต้องใช้
- สร้างการใช้งานพร็อกซีของอินเทอร์เฟซที่ใช้ API ใน Android 4.1 ขึ้นไป
- สร้างการใช้งานอินเทอร์เฟซที่กำหนดเองซึ่งใช้ API ที่มีระหว่าง Android 3.1 ถึง Android 4.0
- สร้างตรรกะสำหรับการสลับระหว่างการใช้งานเหล่านี้ในรันไทม์ และเริ่มใช้อินเทอร์เฟซในเกม
ดูภาพรวมของวิธีใช้การแยกออกเพื่อยืนยันว่าแอปพลิเคชันสามารถ ทำงานในลักษณะที่เข้ากันได้แบบย้อนหลังใน Android เวอร์ชันต่างๆ ได้ที่ หัวข้อการสร้าง UI ที่เข้ากันได้แบบย้อนหลัง
เพิ่มอินเทอร์เฟซเพื่อความเข้ากันได้แบบย้อนหลัง
หากต้องการให้มีความเข้ากันได้แบบย้อนหลัง คุณสามารถสร้างอินเทอร์เฟซที่กำหนดเอง แล้วเพิ่มการใช้งานเฉพาะเวอร์ชัน ข้อดีอย่างหนึ่งของแนวทางนี้คือช่วยให้คุณจำลองอินเทอร์เฟซสาธารณะใน Android 4.1 (ระดับ API 16) ที่รองรับเกมคอนโทรลเลอร์ได้
Kotlin
// The InputManagerCompat interface is a reference example.
// The full code is provided in the ControllerSample.zip sample.
interface InputManagerCompat {
val inputDeviceIds: IntArray
fun getInputDevice(id: Int): InputDevice
fun registerInputDeviceListener(
listener: InputManager.InputDeviceListener,
handler: Handler?
)
fun unregisterInputDeviceListener(listener:InputManager.InputDeviceListener)
fun onGenericMotionEvent(event: MotionEvent)
fun onPause()
fun onResume()
interface InputDeviceListener {
fun onInputDeviceAdded(deviceId: Int)
fun onInputDeviceChanged(deviceId: Int)
fun onInputDeviceRemoved(deviceId: Int)
}
}
Java
// The InputManagerCompat interface is a reference example.
// The full code is provided in the ControllerSample.zip sample.
public interface InputManagerCompat {
...
public InputDevice getInputDevice(int id);
public int[] getInputDeviceIds();
public void registerInputDeviceListener(
InputManagerCompat.InputDeviceListener listener,
Handler handler);
public void unregisterInputDeviceListener(
InputManagerCompat.InputDeviceListener listener);
public void onGenericMotionEvent(MotionEvent event);
public void onPause();
public void onResume();
public interface InputDeviceListener {
void onInputDeviceAdded(int deviceId);
void onInputDeviceChanged(int deviceId);
void onInputDeviceRemoved(int deviceId);
}
...
}
อินเทอร์เฟซ InputManagerCompat มีเมธอดต่อไปนี้
getInputDevice()- จำลอง
getInputDevice()รับออบเจ็กต์InputDeviceที่แสดงความสามารถของเกมคอนโทรลเลอร์ getInputDeviceIds()- จำลอง
getInputDeviceIds()แสดงผลอาร์เรย์ของจำนวนเต็ม ซึ่งแต่ละจำนวนเต็มคือรหัสของอุปกรณ์ป้อนข้อมูลที่แตกต่างกัน วิธีนี้มีประโยชน์หากคุณกำลังสร้างเกมที่รองรับผู้เล่นหลายคนและต้องการตรวจหาว่ามีการเชื่อมต่อคอนโทรลเลอร์กี่ตัว registerInputDeviceListener()- จำลอง
registerInputDeviceListener()ช่วยให้คุณลงทะเบียนเพื่อรับข้อมูลเมื่อมีการเพิ่ม เปลี่ยนแปลง หรือนำอุปกรณ์ใหม่ออก unregisterInputDeviceListener()- จำลอง
unregisterInputDeviceListener()ยกเลิกการลงทะเบียน Listener ของอุปกรณ์ป้อนข้อมูล onGenericMotionEvent()- จำลอง
onGenericMotionEvent()ช่วยให้เกมของคุณสกัดกั้นและจัดการออบเจ็กต์MotionEventและค่าแกนที่แสดงเหตุการณ์ต่างๆ เช่น การเคลื่อนไหวของจอยสติ๊กและการกดทริกเกอร์แอนะล็อก onPause()- หยุดการโพลเหตุการณ์เกมคอนโทรลเลอร์เมื่อกิจกรรมหลักหยุดชั่วคราว หรือเมื่อเกมไม่มีโฟกัสอีกต่อไป
onResume()- เริ่มการโพลเหตุการณ์เกมคอนโทรลเลอร์เมื่อกิจกรรมหลักกลับมาทำงานต่อ หรือเมื่อเกมเริ่มต้นและทำงานในเบื้องหน้า
InputDeviceListener- จำลองอินเทอร์เฟซ
InputManager.InputDeviceListenerแจ้งให้เกมของคุณทราบเมื่อมีการเพิ่ม เปลี่ยนแปลง หรือนำเกมคอนโทรลเลอร์ออก
จากนั้นสร้างการใช้งานสำหรับ InputManagerCompat ที่ทำงานได้ในแพลตฟอร์มเวอร์ชันต่างๆ หากเกมของคุณทำงานบน Android 4.1 หรือ
สูงกว่าและเรียกใช้เมธอด InputManagerCompat การใช้งานพร็อกซี
จะเรียกใช้เมธอดที่เทียบเท่าใน InputManager
อย่างไรก็ตาม หากเกมของคุณทำงานบน Android 3.1 ถึง Android 4.0 การใช้งานที่กำหนดเองจะประมวลผลการเรียกใช้เมธอด InputManagerCompat โดยใช้เฉพาะ API ที่เปิดตัวไม่เกิน Android 3.1 ไม่ว่าการใช้งานเฉพาะเวอร์ชันใดจะใช้ในรันไทม์ การใช้งานจะส่งผลการเรียกกลับไปยังเกมอย่างโปร่งใส
ใช้อินเทอร์เฟซใน Android 4.1 ขึ้นไป
InputManagerCompatV16 เป็นการใช้งานอินเทอร์เฟซ InputManagerCompat
ที่ส่งต่อการเรียกใช้เมธอดไปยัง
InputManager และ
InputManager.InputDeviceListener จริง
โดยรับ InputManager จาก Context ของระบบ
Kotlin
// The InputManagerCompatV16 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV16(
context: Context,
private val inputManager: InputManager =
context.getSystemService(Context.INPUT_SERVICE) as InputManager,
private val listeners:
MutableMap<InputManager.InputDeviceListener, V16InputDeviceListener> = mutableMapOf()
) : InputManagerCompat {
override val inputDeviceIds: IntArray = inputManager.inputDeviceIds
override fun getInputDevice(id: Int): InputDevice = inputManager.getInputDevice(id)
override fun registerInputDeviceListener(
listener: InputManager.InputDeviceListener,
handler: Handler?
) {
V16InputDeviceListener(listener).also { v16listener ->
inputManager.registerInputDeviceListener(v16listener, handler)
listeners += listener to v16listener
}
}
// Do the same for unregistering an input device listener
...
override fun onGenericMotionEvent(event: MotionEvent) {
// unused in V16
}
override fun onPause() {
// unused in V16
}
override fun onResume() {
// unused in V16
}
}
class V16InputDeviceListener(
private val idl: InputManager.InputDeviceListener
) : InputManager.InputDeviceListener {
override fun onInputDeviceAdded(deviceId: Int) {
idl.onInputDeviceAdded(deviceId)
}
// Do the same for device change and removal
...
}
Java
// The InputManagerCompatV16 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV16 implements InputManagerCompat {
private final InputManager inputManager;
private final Map<InputManagerCompat.InputDeviceListener,
V16InputDeviceListener> listeners;
public InputManagerV16(Context context) {
inputManager = (InputManager)
context.getSystemService(Context.INPUT_SERVICE);
listeners = new HashMap<InputManagerCompat.InputDeviceListener,
V16InputDeviceListener>();
}
@Override
public InputDevice getInputDevice(int id) {
return inputManager.getInputDevice(id);
}
@Override
public int[] getInputDeviceIds() {
return inputManager.getInputDeviceIds();
}
static class V16InputDeviceListener implements
InputManager.InputDeviceListener {
final InputManagerCompat.InputDeviceListener mIDL;
public V16InputDeviceListener(InputDeviceListener idl) {
mIDL = idl;
}
@Override
public void onInputDeviceAdded(int deviceId) {
mIDL.onInputDeviceAdded(deviceId);
}
// Do the same for device change and removal
...
}
@Override
public void registerInputDeviceListener(InputDeviceListener listener,
Handler handler) {
V16InputDeviceListener v16Listener = new
V16InputDeviceListener(listener);
inputManager.registerInputDeviceListener(v16Listener, handler);
listeners.put(listener, v16Listener);
}
// Do the same for unregistering an input device listener
...
@Override
public void onGenericMotionEvent(MotionEvent event) {
// unused in V16
}
@Override
public void onPause() {
// unused in V16
}
@Override
public void onResume() {
// unused in V16
}
}
ใช้อินเทอร์เฟซใน Android 3.1 ถึง Android 4.0
หากต้องการสร้างการใช้งาน InputManagerCompat ที่รองรับ Android 3.1 ถึง Android 4.0 คุณสามารถใช้ออบเจ็กต์ต่อไปนี้
- A
SparseArrayของรหัสอุปกรณ์เพื่อ ติดตามเกมคอนโทรลเลอร์ที่เชื่อมต่อกับอุปกรณ์ - A
Handlerเพื่อประมวลผลเหตุการณ์ของอุปกรณ์ เมื่อแอปเริ่มต้นหรือกลับมาทำงานต่อHandlerจะได้รับข้อความเพื่อเริ่ม โพลการตัดการเชื่อมต่อของเกมคอนโทรลเลอร์Handlerจะเริ่มลูปเพื่อตรวจสอบ เกมคอนโทรลเลอร์ที่เชื่อมต่อที่รู้จักแต่ละตัวและดูว่ามีการแสดงผลรหัสอุปกรณ์หรือไม่ ค่าแสดงผลnullหมายความว่าเกมคอนโทรลเลอร์ถูกตัดการเชื่อมต่อ TheHandlerจะหยุดการโพลเมื่อแอป หยุดชั่วคราว A
MapของInputManagerCompat.InputDeviceListenerออบเจ็กต์ คุณจะใช้ Listener เพื่ออัปเดตสถานะการเชื่อมต่อของเกมคอนโทรลเลอร์ที่ติดตาม
Kotlin
// The InputManagerCompatV9 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
class InputManagerV9(
val devices: SparseArray<Array<Long>> = SparseArray(),
private val listeners:
MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
) : InputManagerCompat {
private val defaultHandler: Handler = PollingMessageHandler(this)
…
}
Java
// The InputManagerCompatV9 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV9 implements InputManagerCompat {
private final SparseArray<long[]> devices;
private final Map<InputDeviceListener, Handler> listeners;
private final Handler defaultHandler;
…
public InputManagerV9() {
devices = new SparseArray<long[]>();
listeners = new HashMap<InputDeviceListener, Handler>();
defaultHandler = new PollingMessageHandler(this);
}
}
ใช้ออบเจ็กต์ PollingMessageHandler ที่ขยาย
Handler และลบล้าง
handleMessage()
เมธอด เมธอดนี้จะตรวจสอบว่าเกมคอนโทรลเลอร์ที่แนบอยู่ถูกตัดการเชื่อมต่อหรือไม่ และแจ้ง Listener ที่ลงทะเบียน
Kotlin
private class PollingMessageHandler(
inputManager: InputManagerV9,
private val mInputManager: WeakReference<InputManagerV9> = WeakReference(inputManager)
) : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
MESSAGE_TEST_FOR_DISCONNECT -> {
mInputManager.get()?.also { imv ->
val time = SystemClock.elapsedRealtime()
val size = imv.devices.size()
for (i in 0 until size) {
imv.devices.valueAt(i)?.also { lastContact ->
if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
// check to see if the device has been
// disconnected
val id = imv.devices.keyAt(i)
if (null == InputDevice.getDevice(id)) {
// Notify the registered listeners
// that the game controller is disconnected
imv.devices.remove(id)
} else {
lastContact[0] = time
}
}
}
}
sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, CHECK_ELAPSED_TIME)
}
}
}
}
}
Java
private static class PollingMessageHandler extends Handler {
private final WeakReference<InputManagerV9> inputManager;
PollingMessageHandler(InputManagerV9 im) {
inputManager = new WeakReference<InputManagerV9>(im);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MESSAGE_TEST_FOR_DISCONNECT:
InputManagerV9 imv = inputManager.get();
if (null != imv) {
long time = SystemClock.elapsedRealtime();
int size = imv.devices.size();
for (int i = 0; i < size; i++) {
long[] lastContact = imv.devices.valueAt(i);
if (null != lastContact) {
if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
// check to see if the device has been
// disconnected
int id = imv.devices.keyAt(i);
if (null == InputDevice.getDevice(id)) {
// Notify the registered listeners
// that the game controller is disconnected
imv.devices.remove(id);
} else {
lastContact[0] = time;
}
}
}
}
sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
CHECK_ELAPSED_TIME);
}
break;
}
}
}
หากต้องการเริ่มและหยุดการโพลการตัดการเชื่อมต่อของเกมคอนโทรลเลอร์ ให้ลบล้างเมธอดต่อไปนี้
Kotlin
private const val MESSAGE_TEST_FOR_DISCONNECT = 101
private const val CHECK_ELAPSED_TIME = 3000L
class InputManagerV9(
val devices: SparseArray<Array<Long>> = SparseArray(),
private val listeners:
MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
) : InputManagerCompat {
...
override fun onPause() {
defaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT)
}
override fun onResume() {
defaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, CHECK_ELAPSED_TIME)
}
...
}
Java
private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
private static final long CHECK_ELAPSED_TIME = 3000L;
@Override
public void onPause() {
defaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
}
@Override
public void onResume() {
defaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
CHECK_ELAPSED_TIME);
}
หากต้องการตรวจหาว่ามีการเพิ่มอุปกรณ์ป้อนข้อมูล ให้ลบล้างเมธอด onGenericMotionEvent() เมื่อระบบรายงานเหตุการณ์การเคลื่อนไหว ให้ตรวจสอบว่าเหตุการณ์นี้มาจากรหัสอุปกรณ์ที่ติดตามอยู่แล้วหรือมาจากรหัสอุปกรณ์ใหม่ หากรหัสอุปกรณ์เป็นรหัสใหม่ ให้แจ้ง Listener ที่ลงทะเบียน
Kotlin
override fun onGenericMotionEvent(event: MotionEvent) {
// detect new devices
val id = event.deviceId
val timeArray: Array<Long> = mDevices.get(id) ?: run {
// Notify the registered listeners that a game controller is added
...
arrayOf<Long>().also {
mDevices.put(id, it)
}
}
timeArray[0] = SystemClock.elapsedRealtime()
}
Java
@Override
public void onGenericMotionEvent(MotionEvent event) {
// detect new devices
int id = event.getDeviceId();
long[] timeArray = mDevices.get(id);
if (null == timeArray) {
// Notify the registered listeners that a game controller is added
...
timeArray = new long[1];
mDevices.put(id, timeArray);
}
long time = SystemClock.elapsedRealtime();
timeArray[0] = time;
}
การแจ้งเตือน Listener จะดำเนินการโดยใช้ออบเจ็กต์
Handler เพื่อส่งออบเจ็กต์ DeviceEvent
Runnable ไปยังคิวข้อความ
DeviceEvent มีการอ้างอิงถึง InputManagerCompat.InputDeviceListener เมื่อ DeviceEvent ทำงาน ระบบจะเรียกใช้เมธอด Callback ที่เหมาะสมของ Listener เพื่อส่งสัญญาณว่ามีการเพิ่ม เปลี่ยนแปลง หรือนำเกมคอนโทรลเลอร์ออก
Kotlin
class InputManagerV9(
val devices: SparseArray<Array<Long>> = SparseArray(),
private val listeners:
MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
) : InputManagerCompat {
...
override fun registerInputDeviceListener(
listener: InputManager.InputDeviceListener,
handler: Handler?
) {
listeners[listener] = handler ?: defaultHandler
}
override fun unregisterInputDeviceListener(listener: InputManager.InputDeviceListener) {
listeners.remove(listener)
}
private fun notifyListeners(why: Int, deviceId: Int) {
// the state of some device has changed
listeners.forEach { listener, handler ->
DeviceEvent.getDeviceEvent(why, deviceId, listener).also {
handler?.post(it)
}
}
}
...
}
private val sObjectQueue: Queue<DeviceEvent> = ArrayDeque<DeviceEvent>()
private class DeviceEvent(
private var mMessageType: Int,
private var mId: Int,
private var mListener: InputManager.InputDeviceListener
) : Runnable {
companion object {
fun getDeviceEvent(messageType: Int, id: Int, listener: InputManager.InputDeviceListener) =
sObjectQueue.poll()?.apply {
mMessageType = messageType
mId = id
mListener = listener
} ?: DeviceEvent(messageType, id, listener)
}
override fun run() {
when(mMessageType) {
ON_DEVICE_ADDED -> mListener.onInputDeviceAdded(mId)
ON_DEVICE_CHANGED -> mListener.onInputDeviceChanged(mId)
ON_DEVICE_REMOVED -> mListener.onInputDeviceChanged(mId)
else -> {
// Handle unknown message type
}
}
}
}
Java
@Override
public void registerInputDeviceListener(InputDeviceListener listener,
Handler handler) {
listeners.remove(listener);
if (handler == null) {
handler = defaultHandler;
}
listeners.put(listener, handler);
}
@Override
public void unregisterInputDeviceListener(InputDeviceListener listener) {
listeners.remove(listener);
}
private void notifyListeners(int why, int deviceId) {
// the state of some device has changed
if (!listeners.isEmpty()) {
for (InputDeviceListener listener : listeners.keySet()) {
Handler handler = listeners.get(listener);
DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId,
listener);
handler.post(odc);
}
}
}
private static class DeviceEvent implements Runnable {
private int mMessageType;
private int mId;
private InputDeviceListener mListener;
private static Queue<DeviceEvent> sObjectQueue =
new ArrayDeque<DeviceEvent>();
...
static DeviceEvent getDeviceEvent(int messageType, int id,
InputDeviceListener listener) {
DeviceEvent curChanged = sObjectQueue.poll();
if (null == curChanged) {
curChanged = new DeviceEvent();
}
curChanged.mMessageType = messageType;
curChanged.mId = id;
curChanged.mListener = listener;
return curChanged;
}
@Override
public void run() {
switch (mMessageType) {
case ON_DEVICE_ADDED:
mListener.onInputDeviceAdded(mId);
break;
case ON_DEVICE_CHANGED:
mListener.onInputDeviceChanged(mId);
break;
case ON_DEVICE_REMOVED:
mListener.onInputDeviceRemoved(mId);
break;
default:
// Handle unknown message type
...
break;
}
// Put this runnable back in the queue
sObjectQueue.offer(this);
}
}
ตอนนี้คุณมีการใช้งาน InputManagerCompat 2 แบบ ได้แก่ แบบที่ทำงานบนอุปกรณ์ที่ใช้ Android 4.1 ขึ้นไป และแบบที่ทำงานบนอุปกรณ์ที่ใช้ Android 3.1 ถึง Android 4.0
ใช้การใช้งานเฉพาะเวอร์ชัน
ตรรกะการสลับเฉพาะเวอร์ชันจะใช้งานในคลาสที่ทำหน้าที่เป็น Factory
Kotlin
object Factory {
fun getInputManager(context: Context): InputManagerCompat =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
InputManagerV16(context)
} else {
InputManagerV9()
}
}
Java
public static class Factory {
public static InputManagerCompat getInputManager(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
return new InputManagerV16(context);
} else {
return new InputManagerV9();
}
}
}
ตอนนี้คุณสามารถสร้างอินสแตนซ์ออบเจ็กต์ InputManagerCompat และลงทะเบียน
InputManagerCompat.InputDeviceListener ใน
View หลักได้แล้ว เนื่องจากตรรกะการสลับเวอร์ชันที่คุณตั้งค่าไว้ เกมจะใช้การใช้งานที่เหมาะสมกับ Android เวอร์ชันที่อุปกรณ์ใช้อยู่โดยอัตโนมัติ
Kotlin
class GameView(context: Context) : View(context), InputManager.InputDeviceListener {
private val inputManager: InputManagerCompat = Factory.getInputManager(context).apply {
registerInputDeviceListener(this@GameView, null)
...
}
...
}
Java
public class GameView extends View implements InputDeviceListener {
private InputManagerCompat inputManager;
...
public GameView(Context context, AttributeSet attrs) {
inputManager =
InputManagerCompat.Factory.getInputManager(this.getContext());
inputManager.registerInputDeviceListener(this, null);
...
}
}
จากนั้นลบล้างเมธอด onGenericMotionEvent() ใน View หลักตามที่
อธิบายไว้ในหัวข้อจัดการ MotionEvent จากเกม
คอนโทรลเลอร์และสูงกว่า ตอนนี้เกมของคุณควรจะประมวลผลเหตุการณ์เกมคอนโทรลเลอร์ได้อย่างสม่ำเสมอในอุปกรณ์ที่ใช้ Android 3.1 (ระดับ API 12) ขึ้นไป
Kotlin
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
inputManager.onGenericMotionEvent(event)
// Handle analog input from the controller as normal
...
return super.onGenericMotionEvent(event)
}
Java
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
inputManager.onGenericMotionEvent(event);
// Handle analog input from the controller as normal
...
return super.onGenericMotionEvent(event);
}
คุณดูการใช้งานโค้ดความเข้ากันได้อย่างสมบูรณ์ได้ในคลาส GameView ที่มีให้ในตัวอย่าง ControllerSample.zip ซึ่งดาวน์โหลดได้