Hỗ trợ tay điều khiển trên các phiên bản Android

Bạn có trách nhiệm hỗ trợ tay điều khiển trò chơi trong trò chơi của mình để đảm bảo trò chơi của bạn phản hồi tay điều khiển một cách nhất quán trên các thiết bị chạy trên các phiên bản Android khác nhau. Điều này cho phép trò chơi của bạn tiếp cận và người chơi có thể tận hưởng trải nghiệm chơi liền mạch với bộ điều khiển của họ ngay cả khi họ chuyển đổi hoặc nâng cấp thiết bị Android.

Bài học này trình bày cách sử dụng API có trong Android 4.1 trở lên theo cách tương thích ngược, cho phép trò chơi của bạn hỗ trợ trên các thiết bị chạy Android 3.1 trở lên:

  • Trò chơi có thể phát hiện xem một tay điều khiển trò chơi mới được thêm, thay đổi hoặc xoá hay chưa.
  • Trò chơi có thể truy vấn các chức năng của tay điều khiển trò chơi.
  • Trò chơi có thể nhận dạng các sự kiện chuyển động đến qua tay điều khiển trò chơi.

Các ví dụ trong bài học này dựa trên cách triển khai tệp tham chiếu được cung cấp bởi ControllerSample.zip mẫu có sẵn để tải xuống ở trên. Mẫu này trình bày cách triển khai InputManagerCompat để hỗ trợ các phiên bản Android khác nhau. Để biên dịch mẫu, bạn phải sử dụng Android 4.1 (API cấp 16) trở lên. Sau khi được biên dịch, ứng dụng mẫu chạy trên mọi thiết bị chạy Android 3.1 (API cấp 12) trở lên dưới dạng bản dựng .

Chuẩn bị các API trừu tượng để hỗ trợ tay điều khiển trò chơi

Giả sử bạn muốn có thể xác định xem kết nối của tay điều khiển trò chơi trạng thái đã thay đổi trên các thiết bị chạy trên Android 3.1 (API cấp 12). Tuy nhiên, API chỉ có trong Android 4.1 (API cấp 16) trở lên, vì vậy bạn cần cung cấp một phương thức triển khai hỗ trợ Android 4.1 trở lên trong khi cung cấp cơ chế dự phòng hỗ trợ từ Android 3.1 đến Android 4.0.

Để giúp bạn xác định tính năng nào yêu cầu cơ chế dự phòng như vậy cho các phiên bản cũ hơn, bảng 1 liệt kê những điểm khác biệt về khả năng hỗ trợ tay điều khiển trò chơi giữa Android 3.1 (API cấp 12) đến 4.1 (cấp độ API) 16).

Bảng 1. Các API để hỗ trợ tay điều khiển trò chơi trên phiên bản Android khác nhau.

Thông tin về tay điều khiển API Trình điều khiển API cấp 12 API cấp 16
Nhận dạng thiết bị getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
Trạng thái kết nối mạng onInputDeviceAdded()  
onInputDeviceChanged()  
onInputDeviceRemoved()  
Nhận dạng sự kiện đầu vào Nhấn D-pad ( KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_CENTER)
Nhấn nút trên tay điều khiển trò chơi ( BUTTON_A, BUTTON_B, BUTTON_THUMBL, BUTTON_THUMBR, BUTTON_SELECT, BUTTON_START, BUTTON_R1, BUTTON_L1, BUTTON_R2, BUTTON_L2)
Chuyển động của cần điều khiển và công tắc mũ ( AXIS_X, AXIS_Y, AXIS_Z, AXIS_RZ, AXIS_HAT_X, AXIS_HAT_Y)
Nhấn nút kích hoạt analog ( AXIS_LTRIGGER, AXIS_RTRIGGER)

Bạn có thể sử dụng mô hình trừu tượng để xây dựng tính năng hỗ trợ tay điều khiển trò chơi có nhận biết phiên bản hoạt động trên nhiều nền tảng. Phương pháp này bao gồm các bước sau:

  1. Xác định giao diện Java trung gian tóm tắt việc triển khai các tính năng của tay điều khiển trò chơi mà trò chơi của bạn yêu cầu.
  2. Tạo hoạt động triển khai proxy cho giao diện sử dụng API trong Android 4.1 trở lên.
  3. Tạo phương thức triển khai tuỳ chỉnh giao diện sử dụng các API có sẵn giữa Android 3.1 và Android 4.0.
  4. Tạo logic để chuyển đổi giữa các cách triển khai này trong thời gian chạy, và bắt đầu sử dụng giao diện trong trò chơi của bạn.

Để biết thông tin tổng quan về cách sử dụng mô hình trừu tượng để đảm bảo rằng các ứng dụng có thể hoạt động theo cách tương thích ngược trên các phiên bản Android khác nhau, hãy xem Sáng tạo Giao diện người dùng tương thích ngược.

Thêm giao diện có khả năng tương thích ngược

Để cung cấp khả năng tương thích ngược, sau đó bạn có thể tạo giao diện tuỳ chỉnh thêm các phương pháp triển khai theo phiên bản cụ thể. Một ưu điểm của phương pháp này là cho phép bạn phản chiếu các giao diện công khai trên Android 4.1 (API cấp 16) hỗ trợ tay điều khiển trò chơi.

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);
    }
    ...
}

Giao diện InputManagerCompat cung cấp các phương thức sau:

getInputDevice()
Gương getInputDevice(). Nhận InputDevice đối tượng đại diện cho các chức năng của tay điều khiển trò chơi.
getInputDeviceIds()
Gương getInputDeviceIds(). Trả về một mảng số nguyên, mỗi mảng trong số là mã nhận dạng cho một thiết bị đầu vào khác. Điều này sẽ hữu ích nếu bạn đang xây dựng một trò chơi hỗ trợ nhiều người chơi và bạn muốn biết có bao nhiêu người chơi tay điều khiển trò chơi đã được kết nối.
registerInputDeviceListener()
Gương registerInputDeviceListener(). Cho phép bạn đăng ký nhận thông báo khi có thiết bị được thêm vào, thay đổi hoặc bị xoá.
unregisterInputDeviceListener()
Gương unregisterInputDeviceListener(). Huỷ đăng ký trình nghe thiết bị đầu vào.
onGenericMotionEvent()
Gương onGenericMotionEvent(). Cho phép trò chơi chặn và xử lý Các đối tượng MotionEvent và giá trị trục đại diện cho sự kiện chẳng hạn như chuyển động của cần điều khiển và thao tác nhấn nút kích hoạt tương tự.
onPause()
Dừng thăm dò sự kiện tay điều khiển trò chơi khi hoạt động chính bị tạm dừng hoặc khi trò chơi không còn tiêu điểm.
onResume()
Bắt đầu thăm dò sự kiện tay điều khiển trò chơi khi hoạt động chính được tiếp tục hoặc khi trò chơi bắt đầu và chạy trong nền trước.
InputDeviceListener
Phản ánh InputManager.InputDeviceListener . Cho trò chơi của bạn biết khi nào bạn đã thêm, thay đổi hoặc thêm tay điều khiển trò chơi đã bị xóa.

Tiếp theo, hãy tạo các phương pháp triển khai hiệu quả cho InputManagerCompat trên các phiên bản nền tảng khác nhau. Nếu trò chơi của bạn đang chạy trên Android 4.1 hoặc cao hơn và gọi phương thức InputManagerCompat, phương thức triển khai proxy gọi phương thức tương đương trong InputManager. Tuy nhiên, nếu trò chơi của bạn đang chạy trên Android 3.1 đến Android 4.0, việc triển khai tuỳ chỉnh xử lý lệnh gọi đến phương thức InputManagerCompat bằng cách sử dụng chỉ các API được ra mắt sau Android 3.1. Bất kể phương thức triển khai theo phiên bản cụ thể được dùng trong thời gian chạy, còn phương thức triển khai thành công kết quả cuộc gọi trở lại một cách rõ ràng cho trò chơi.

Hình 1. Sơ đồ lớp về giao diện và theo phiên bản cụ thể thực tế.

Triển khai giao diện trên Android 4.1 trở lên

InputManagerCompatV16 là một cách triển khai của Giao diện InputManagerCompat proxy các lệnh gọi phương thức đến một InputManagerInputManager.InputDeviceListener thực tế. Chiến lược phát hành đĩa đơn InputManager được lấy từ hệ thống 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
    }

}

Triển khai giao diện trên Android 3.1 đến Android 4.0

Để tạo một phương thức triển khai InputManagerCompat hỗ trợ Android 3.1 cho đến Android 4.0, bạn có thể sử dụng các đối tượng sau:

  • Một SparseArray mã thiết bị để theo dõi tay điều khiển trò chơi được kết nối với thiết bị.
  • Handler để xử lý các sự kiện trên thiết bị. Khi khởi động ứng dụng hoặc được tiếp tục, Handler sẽ nhận được thông báo để bắt đầu thăm dò ý kiến để ngắt kết nối tay điều khiển trò chơi. Handler sẽ bắt đầu một để kiểm tra từng tay điều khiển trò chơi đã kết nối và xem mã thiết bị có phải là bị trả lại. Giá trị trả về null cho biết tay điều khiển trò chơi đã ngắt kết nối. Handler sẽ ngừng thăm dò ý kiến khi ứng dụng bị tạm dừng.
  • Một Map trong số InputManagerCompat.InputDeviceListener . Bạn sẽ dùng trình nghe để cập nhật trạng thái kết nối của các tay điều khiển trò chơi.

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);
    }
}

Triển khai đối tượng PollingMessageHandler mở rộng Handler và ghi đè handleMessage() . Phương thức này sẽ kiểm tra xem tay điều khiển trò chơi đi kèm có bị đã ngắt kết nối và thông báo cho các trình nghe đã đăng ký.

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;
        }
    }
}

Ghi đè để bắt đầu và dừng thăm dò ý kiến ngắt kết nối tay điều khiển trò chơi các phương thức sau:

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);
}

Để phát hiện xem một thiết bị đầu vào đã được thêm hay chưa, hãy ghi đè onGenericMotionEvent(). Khi hệ thống báo cáo một sự kiện chuyển động, kiểm tra xem sự kiện này đến từ một mã thiết bị đã được theo dõi hay từ một mã thiết bị mới. Nếu mã thiết bị là mới, hãy thông báo cho trình nghe đã đăng ký.

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;
}

Thông báo của trình nghe được triển khai bằng cách sử dụng Đối tượng Handler để gửi DeviceEvent Runnable vào hàng đợi tin nhắn. DeviceEvent chứa tham chiếu đến InputManagerCompat.InputDeviceListener. Thời gian DeviceEvent chạy, phương thức gọi lại thích hợp của trình nghe được gọi để báo hiệu liệu tay điều khiển trò chơi đã được thêm, thay đổi hoặc bị xoá hay chưa.

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);
    }
}

Bạn hiện có hai cách triển khai InputManagerCompat: một cách triển khai hoạt động trên các thiết bị chạy Android 4.1 trở lên và hoạt động trên các thiết bị chạy Android 3.1 tới Android 4.0.

Sử dụng phương pháp triển khai theo phiên bản cụ thể

Logic chuyển đổi dành riêng cho phiên bản được triển khai trong một lớp đóng vai trò là nhà máy.

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();
        }
    }
}

Bây giờ, bạn chỉ cần tạo thực thể cho đối tượng InputManagerCompat và hãy đăng ký một InputManagerCompat.InputDeviceListener trong View. Do logic chuyển đổi phiên bản mà bạn đặt thì trò chơi của bạn sẽ tự động sử dụng cách triển khai phù hợp với phiên bản Android mà thiết bị đang chạy.

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);
        ...
    }
}

Tiếp theo, hãy ghi đè Phương thức onGenericMotionEvent() trong khung hiển thị chính, như mô tả trong Xử lý MotionEvent từ một trò chơi Bộ điều khiển. Giờ đây, trò chơi của bạn có thể xử lý các sự kiện trên tay điều khiển trò chơi nhất quán trên các thiết bị chạy Android 3.1 (API cấp 12) trở lên.

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);
}

Bạn có thể tìm thấy cách triển khai đầy đủ mã tương thích này trong Lớp GameView được cung cấp trong ControllerSample.zip mẫu có thể tải xuống ở trên.