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

Nếu đang hỗ trợ tay điều khiển trò chơi trong trò chơi, bạn có trách nhiệm đảm bảo rằng trò chơi 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 nhiều phiên bản Android. Điều này cho phép trò chơi của bạn tiếp cận nhiều đối tượng hơn và người chơi có thể tận hưởng trải nghiệm chơi liền mạch bằng tay điều khiển ngay cả khi họ chuyển đổi hoặc nâng cấp thiết bị Android.

Bài học này minh hoạ 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ợ các tính năng sau trên thiết bị chạy Android 3.1 trở lên:

  • Trò chơi có thể phát hiện xem tay điều khiển trò chơi mới được thêm, thay đổi hay xoá.
  • 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 ra các sự kiện chuyển động đến từ 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 tham chiếu do ControllerSample.zip mẫu có sẵn để tải xuống ở trên. Mẫu này cho biết cách triển khai giao diện InputManagerCompat để hỗ trợ nhiều phiên bản Android. Để 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 biên dịch, ứng dụng mẫu sẽ chạy trên bất kỳ thiết bị nào chạy Android 3.1 (API cấp 12) trở lên làm mục tiêu bản dựng.

Chuẩn bị tóm tắt các API để hỗ trợ tay điều khiển trò chơi

Giả sử bạn muốn có thể xác định xem trạng thái kết nối của tay điều khiển trò chơi đã thay đổi trên các thiết bị chạy trên Android 3.1 (API cấp 12) hay chưa. Tuy nhiên, các API này 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, đồng thời 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ũ, bảng 1 sẽ liệt kê sự 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) và 4.1 (API cấp 16).

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

Thông tin về đơn vị kiểm soát API Bộ đ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 onInputDeviceAdded()  
onInputDeviceChanged()  
onInputDeviceRemoved()  
Nhận dạng sự kiện đầu vào Nhấn bằng 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 vào điều kiện kích hoạt kim (AXIS_LTRIGGER, AXIS_RTRIGGER)

Bạn có thể sử dụng tính năng trừu tượng để xây dựng tính năng hỗ trợ tay điều khiển trò chơi nhận biết được phiên bản hoạt động được trên các 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 quy trình triển khai các tính năng tay điều khiển trò chơi mà trò chơi của bạn yêu cầu.
  2. Tạo một phương thức triển khai proxy cho giao diện sử dụng API trong Android 4.1 trở lên.
  3. Tạo một phương thức triển khai tuỳ chỉnh cho giao diện sử dụng các API có từ Android 3.1 đến Android 4.0.
  4. Tạo logic để chuyển đổi giữa các phương thức 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 tổng quan về cách sử dụng tính năng trừu tượng nhằm đảm bảo các ứng dụng có thể hoạt động theo cách tương thích ngược trên nhiều phiên bản Android, hãy xem phần Tạo giao diện người dùng có khả nă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, bạn có thể tạo một giao diện tuỳ chỉnh, sau đó thêm các phương thức triển khai dành riêng cho phiên bản. Một ưu điểm của phương pháp này là cho phép bạn phản chiếu giao diện công khai trên Android 4.1 (API cấp 16) có 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(). Lấy đối tượng InputDevice đạ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 là một mã nhận dạng cho một thiết bị đầu vào khác nhau. Điều này rất 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 phát hiện có bao nhiêu tay điều khiển được kết nối.
registerInputDeviceListener()
Gương registerInputDeviceListener(). Cho phép bạn đăng ký nhận thông báo khi một thiết bị mới được thêm, 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 của bạn chặn và xử lý các đối tượng cũng như giá trị trục MotionEvent đại diện cho các sự kiệ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 analog.
onPause()
Dừng thăm dò các sự kiện của 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 được chú ý.
onResume()
Bắt đầu thăm dò các sự kiện của 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 ở nền trước.
InputDeviceListener
Phản chiếu giao diện InputManager.InputDeviceListener. Thông báo cho trò chơi của bạn khi một tay điều khiển trò chơi được thêm, thay đổi hoặc xoá.

Tiếp theo, hãy tạo các phương thức triển khai cho InputManagerCompat hoạt động trên nhiều phiên bản nền tảng. Nếu trò chơi của bạn đang chạy trên Android 4.1 trở lên và gọi một phương thức InputManagerCompat, thì quy trình triển khai proxy sẽ 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 cho đến Android 4.0, thì quy trình triển khai tuỳ chỉnh sẽ gọi phương thức InputManagerCompat bằng cách chỉ sử dụng các API được giới thiệu trước Android 3.1. Bất kể cách triển khai dành riêng cho phiên bản nào được sử dụng trong thời gian chạy, quá trình triển khai sẽ chuyển kết quả lệnh gọi trở lại trò chơi một cách minh bạch.

Hình 1. Sơ đồ lớp của cách triển khai giao diện và phiên bản cụ thể.

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

InputManagerCompatV16 là cách triển khai giao diện InputManagerCompat thực hiện các lệnh gọi phương thức proxy đến một InputManagerInputManager.InputDeviceListener thực tế. 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 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 gồm các 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 một ứng dụng được khởi động hoặc tiếp tục, Handler sẽ nhận được thông báo để bắt đầu thăm dò ý kiến về việc ngắt kết nối tay điều khiển trò chơi. Handler sẽ bắt đầu một vòng lặp để kiểm tra từng tay điều khiển trò chơi đã kết nối đã biết và xem mã thiết bị có được trả về hay không. Giá trị trả về của null cho biết tay điều khiển trò chơi đã bị ngắt kết nối. Handler sẽ ngừng thăm dò khi ứng dụng bị tạm dừng.
  • Một Map gồm các đối tượng InputManagerCompat.InputDeviceListener. Bạn sẽ sử dụng trình nghe để cập nhật trạng thái kết nối của tay điều khiển trò chơi được theo dõ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 đè phương thức handleMessage(). Phương thức này sẽ kiểm tra xem tay điều khiển trò chơi đính kèm có bị ngắt kết nối hay không 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;
        }
    }
}

Để bắt đầu và dừng cuộc thăm dò ý kiến về việc ngắt kết nối tay điều khiển trò chơi, hãy ghi đè 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 thiết bị đầu vào đã được thêm hay chưa, hãy ghi đè phương thức onGenericMotionEvent(). Khi hệ thống báo cáo một sự kiện chuyển động, hãy 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ã mới, hãy thông báo cho các 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 đối tượng DeviceEvent Runnable đến hàng đợi thông báo. DeviceEvent chứa tham chiếu đến InputManagerCompat.InputDeviceListener. Khi DeviceEvent chạy, phương thức gọi lại thích hợp của trình nghe sẽ được gọi để cho biết 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 hoạt động trên các thiết bị chạy Android 4.1 trở lên và một cách triển khai khác hoạt động trên các thiết bị chạy từ Android 3.1 đến Android 4.0.

Sử dụng cách triển khai cho từng phiên bản

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

Giờ đây, bạn chỉ cần tạo thực thể cho đối tượng InputManagerCompat và đăng ký InputManagerCompat.InputDeviceListener trong View chính. Do logic chuyển đổi phiên bản mà bạn đã thiết lập, 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 chế độ xem chính, như mô tả trong bài viết Xử lý một MotionEvent từ Tay điều khiển trò chơi. Giờ đây, trò chơi của bạn có thể xử lý các sự kiện tay điều khiển trò chơi một cách 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 đủ của mã tương thích này trong lớp GameView được cung cấp trong ControllerSample.zip mẫu có sẵn để tải xuống ở trên.