รองรับตัวควบคุมใน Android เวอร์ชันต่างๆ

ถ้าคุณสนับสนุนตัวควบคุมเกมในเกม เป็นความรับผิดชอบของคุณ เพื่อให้แน่ใจว่าเกมจะตอบสนองต่อตัวควบคุม อย่างสอดคล้องกันในทุกอุปกรณ์ ที่ทำงานใน Android เวอร์ชันต่างๆ กัน วิธีนี้จะช่วยให้เกมเข้าถึง และผู้เล่นสามารถเพลิดเพลินกับประสบการณ์การเล่นเกมที่ราบรื่น แม้ในตอนที่เปลี่ยนหรืออัปเกรดอุปกรณ์ Android ก็ตาม

บทเรียนนี้สาธิตวิธีใช้ API ที่มีอยู่ใน Android 4.1 ขึ้นไป ด้วยวิธีที่เข้ากันได้แบบย้อนหลัง ซึ่งช่วยให้เกมของคุณรองรับ คุณลักษณะบนอุปกรณ์ที่ใช้ Android 3.1 ขึ้นไป

  • เกมจะตรวจหาได้ว่ามีการเพิ่ม เปลี่ยนแปลง หรือนำตัวควบคุมเกมใหม่ออกหรือไม่
  • เกมค้นหาความสามารถของเกมคอนโทรลเลอร์ได้
  • เกมสามารถจดจำเหตุการณ์การเคลื่อนไหวที่เข้ามาจากตัวควบคุมเกม

ตัวอย่างในบทเรียนนี้อิงตามการใช้งานข้อมูลอ้างอิง มาจากตัวอย่าง ControllerSample.zip พร้อมให้ดาวน์โหลด ที่ด้านบน ตัวอย่างนี้แสดงวิธีนำ InputManagerCompat ไปใช้ เพื่อรองรับ Android เวอร์ชันต่างๆ ในการรวบรวมตัวอย่าง คุณจะต้อง ต้องใช้ Android 4.1 (API ระดับ 16) ขึ้นไป เมื่อคอมไพล์แล้ว แอปตัวอย่าง ทำงานบนอุปกรณ์ทั้งหมดที่ใช้ Android 3.1 (API ระดับ 12) ขึ้นไปเป็นบิลด์ เป้าหมาย

เตรียมพร้อมที่จะใช้ 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)

คุณสามารถใช้ Abstraction เพื่อสร้างการรองรับตัวควบคุมเกมแบบรับรู้เวอร์ชัน ใช้งานได้ในหลายแพลตฟอร์ม วิธีการดังกล่าวมีขั้นตอนต่อไปนี้

  1. กำหนดอินเทอร์เฟซ Java ตัวกลางที่แอบนำการติดตั้ง ฟีเจอร์เกมคอนโทรลเลอร์ที่เกมต้องการ
  2. สร้างการใช้พร็อกซีสำหรับอินเทอร์เฟซที่ใช้ API ใน Android 4.1 ขึ้นไป
  3. สร้างการใช้งานอินเทอร์เฟซของคุณที่กำหนดเองซึ่งใช้ API ที่พร้อมใช้งาน ระหว่าง Android 3.1 ถึง Android 4.0
  4. สร้างตรรกะสำหรับการเปลี่ยนระหว่างการใช้งานเหล่านี้ขณะรันไทม์ และเริ่มใช้อินเทอร์เฟซในเกม

สำหรับภาพรวมเกี่ยวกับวิธีใช้นามธรรมเพื่อให้มั่นใจว่าแอปพลิเคชัน สามารถทำงานในลักษณะที่เข้ากันได้แบบย้อนหลังใน 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 ของ Google แจ้งให้เกมของคุณทราบเมื่อมีการเพิ่ม เปลี่ยนแปลงตัวควบคุมเกม หรือ ลบแล้ว

ต่อไป ให้สร้างการติดตั้งใช้งานสำหรับ InputManagerCompat ที่ได้ผล ในแพลตฟอร์มเวอร์ชันต่างๆ หากเกมของคุณใช้ Android 4.1 หรือ สูงขึ้นและเรียกเมธอด InputManagerCompat ซึ่งมีการใช้พร็อกซี เรียกใช้เมธอดที่เทียบเท่าใน InputManager อย่างไรก็ตาม หากเกมของคุณใช้ Android 3.1 ถึง Android 4.0 การใช้งานที่กำหนดเอง ประมวลผลการเรียกไปยังเมธอด InputManagerCompat โดยใช้ เฉพาะ API ที่เปิดตัวไม่เกิน Android 3.1 ไม่ว่าจะอัปโหลดแบบใด การติดตั้งใช้งานเฉพาะเวอร์ชันจะถูกใช้ขณะรันไทม์ และการติดตั้งใช้งานก็จะผ่าน ผลลัพธ์การโทรกลับไปที่เกมอย่างโปร่งใส

รูปที่ 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() วิธี เมื่อระบบรายงานเหตุการณ์การเคลื่อนไหว ตรวจสอบว่าเหตุการณ์นี้มาจากรหัสอุปกรณ์ที่ติดตามอยู่แล้ว หรือจาก รหัสอุปกรณ์ใหม่ หากรหัสอุปกรณ์เป็นเครื่องใหม่ ให้แจ้ง 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;
}

การแจ้งเตือนของผู้ฟังสามารถทำได้โดยใช้ Handler ออบเจ็กต์สำหรับส่ง DeviceEvent Runnable ในคิวข้อความ DeviceEvent มีการอ้างอิงไปยัง InputManagerCompat.InputDeviceListener วันและเวลา DeviceEvent ทำงาน ซึ่งเป็นเมธอด Callback ที่เหมาะสมของผู้ฟัง จะถูกเรียกเพื่อส่งสัญญาณหากมีการเพิ่ม เปลี่ยนแปลง หรือนำตัวควบคุมเกมออก

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

ใช้การใช้งานเฉพาะเวอร์ชัน

มีการใช้ตรรกะการเปลี่ยนเฉพาะเวอร์ชันในคลาสที่ทำหน้าที่เป็น โรงงาน

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 จากเกม ตัวควบคุม เกมของคุณควรประมวลผลเหตุการณ์ตัวควบคุมเกมได้แล้ว สอดคล้องกันในอุปกรณ์ที่ใช้ 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 พร้อมให้ดาวน์โหลดด้านบน