หากคุณรองรับตัวควบคุมเกมในเกมของคุณ คุณมีหน้าที่ต้อง ตรวจสอบว่าเกมตอบสนองต่อตัวควบคุมอย่างสม่ำเสมอในอุปกรณ์ ที่ใช้ 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 เวอร์ชันต่างๆ
| ข้อมูลผู้ควบคุมข้อมูล | Controller 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 คุณสามารถใช้ออบเจ็กต์ต่อไปนี้
SparseArrayของรหัสอุปกรณ์เพื่อ ติดตามอุปกรณ์ควบคุมเกมที่เชื่อมต่อกับอุปกรณ์Handlerเพื่อประมวลผลเหตุการณ์ในอุปกรณ์ เมื่อแอปเริ่มทำงานหรือกลับมาทำงานอีกครั้งHandlerจะได้รับข้อความให้เริ่ม การสำรวจการเชื่อมต่อของเกมคอนโทรลเลอร์ โดยHandlerจะเริ่มวนรอบเพื่อตรวจสอบ เกมคอนโทรลเลอร์ที่เชื่อมต่อที่รู้จักแต่ละตัวและดูว่ามีการส่งคืนรหัสอุปกรณ์หรือไม่nullค่าที่ส่งคืนจะระบุว่าตัวควบคุมเกมถูกยกเลิกการเชื่อมต่อHandlerจะหยุดการสำรวจเมื่อแอป หยุดชั่วคราว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() เมื่อระบบรายงานเหตุการณ์การเคลื่อนไหว
ให้ตรวจสอบว่าเหตุการณ์นี้มาจากรหัสอุปกรณ์ที่ติดตามอยู่แล้วหรือมาจากรหัสอุปกรณ์ใหม่
หากรหัสอุปกรณ์เป็นรหัสใหม่ ให้แจ้งผู้ฟังที่ลงทะเบียน
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 ทำงาน ระบบจะเรียกใช้เมธอดการเรียกกลับที่เหมาะสมของ 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() ในมุมมองหลักตามที่อธิบายไว้ในจัดการ MotionEvent จากเกม
คอนโทรลเลอร์ (API ระดับ 12 ขึ้นไป) ตอนนี้เกมของคุณ
ควรประมวลผลเหตุการณ์ของเกมคอนโทรลเลอร์ได้อย่างสม่ำเสมอในอุปกรณ์
ที่ใช้ Android 3.1 (API ระดับ)
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 ซึ่งพร้อมให้
ดาวน์โหลด