پشتیبانی از کنترلرها در سراسر نسخه های اندروید

اگر از کنترلرهای بازی در بازی خود پشتیبانی می کنید، این مسئولیت شماست که مطمئن شوید که بازی شما به طور مداوم به کنترلرها در دستگاه هایی که روی نسخه های مختلف اندروید اجرا می شوند پاسخ می دهد. این به بازی شما اجازه می‌دهد تا مخاطبان بیشتری را جذب کند و بازیکنان شما می‌توانند از یک تجربه گیم‌پلی یکپارچه با کنترلرهای خود لذت ببرند، حتی زمانی که دستگاه‌های اندرویدی خود را تعویض یا ارتقا می‌دهند.

این درس نحوه استفاده از APIهای موجود در Android نسخه 4.1 و بالاتر را به روشی سازگار با عقب نشان می‌دهد و بازی شما را قادر می‌سازد از ویژگی‌های زیر در دستگاه‌های دارای Android نسخه 3.1 و بالاتر پشتیبانی کند:

  • این بازی می تواند تشخیص دهد که آیا یک کنترلر بازی جدید اضافه، تغییر یا حذف شده است.
  • بازی می تواند قابلیت های یک کنترلر بازی را جویا شود.
  • بازی می تواند رویدادهای حرکتی ورودی را از یک کنترلر بازی تشخیص دهد.

مثال‌های این درس بر اساس پیاده‌سازی مرجع ارائه‌شده توسط ControllerSample.zip نمونه موجود برای دانلود در بالا است. این نمونه نحوه پیاده سازی رابط InputManagerCompat برای پشتیبانی از نسخه های مختلف اندروید را نشان می دهد. برای کامپایل نمونه باید از اندروید 4.1 (سطح API 16) یا بالاتر استفاده کنید. پس از کامپایل، برنامه نمونه بر روی هر دستگاهی که اندروید 3.1 (سطح API 12) یا بالاتر به عنوان هدف ساخت دارد اجرا می شود.

برای پشتیبانی از کنترلرهای بازی، API های انتزاعی را آماده کنید

فرض کنید می‌خواهید بتوانید تعیین کنید که آیا وضعیت اتصال یک کنترل‌کننده بازی در دستگاه‌های دارای Android 3.1 (سطح API 12) تغییر کرده است یا خیر. با این حال، APIها فقط در اندروید 4.1 (سطح API 16) و بالاتر در دسترس هستند، بنابراین باید پیاده‌سازی را ارائه دهید که از اندروید 4.1 و بالاتر پشتیبانی می‌کند و در عین حال مکانیسم بازگشتی ارائه می‌دهد که از اندروید 3.1 تا اندروید 4.0 پشتیبانی می‌کند.

برای کمک به شما در تعیین اینکه کدام ویژگی‌ها برای نسخه‌های قدیمی‌تر به چنین مکانیزم بازگشتی نیاز دارند، جدول 1 تفاوت‌های پشتیبانی از کنترلر بازی را بین Android 3.1 (سطح API 12) و 4.1 (سطح API 16) فهرست می‌کند.

جدول 1. API ها برای پشتیبانی از کنترلر بازی در نسخه های مختلف اندروید.

اطلاعات کنترلر API کنترلر API سطح 12 API سطح 16
شناسایی دستگاه getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
وضعیت اتصال onInputDeviceAdded()
onInputDeviceChanged()
onInputDeviceRemoved()
شناسایی رویداد ورودی دکمه D ( 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 )

می‌توانید از انتزاع برای ایجاد پشتیبانی از کنترلر بازی آگاه از نسخه استفاده کنید که در همه پلتفرم‌ها کار می‌کند. این رویکرد شامل مراحل زیر است:

  1. یک رابط جاوا واسط تعریف کنید که اجرای ویژگی های کنترلر بازی مورد نیاز بازی شما را خلاصه می کند.
  2. یک پیاده سازی پروکسی از رابط خود ایجاد کنید که از API در Android نسخه 4.1 و بالاتر استفاده می کند.
  3. یک پیاده سازی سفارشی از رابط خود ایجاد کنید که از API های موجود بین Android 3.1 تا Android 4.0 استفاده می کند.
  4. منطقی را برای جابجایی بین این پیاده سازی ها در زمان اجرا ایجاد کنید و شروع به استفاده از رابط در بازی خود کنید.

برای یک نمای کلی از نحوه استفاده از انتزاع برای اطمینان از اینکه برنامه‌ها می‌توانند به روشی سازگار با عقب در نسخه‌های مختلف Android کار کنند، به ایجاد رابط‌های کاربری سازگار با عقب نگاه کنید.

یک رابط برای سازگاری با عقب اضافه کنید

برای ارائه سازگاری به عقب، می توانید یک رابط سفارشی ایجاد کنید و سپس پیاده سازی های خاص نسخه را اضافه کنید. یکی از مزایای این روش این است که به شما امکان می دهد رابط های عمومی را در اندروید 4.1 (سطح API 16) که از کنترلرهای بازی پشتیبانی می کنند، منعکس کنید.

کاتلین

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

جاوا

// 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()
Mirrors unregisterInputDeviceListener() . یک شنونده دستگاه ورودی را لغو ثبت می کند.
onGenericMotionEvent()
Mirrors onGenericMotionEvent() . به بازی شما اجازه می‌دهد اشیاء و مقادیر محور MotionEvent را که نشان‌دهنده رویدادهایی مانند حرکات جوی استیک و فشارهای ماشه آنالوگ هستند، رهگیری کرده و مدیریت کند.
onPause()
هنگامی که فعالیت اصلی متوقف می شود یا زمانی که بازی دیگر فوکوس ندارد، نظرسنجی را برای رویدادهای کنترلر بازی متوقف می کند.
onResume()
زمانی که فعالیت اصلی از سر گرفته می شود، یا زمانی که بازی شروع شده و در پیش زمینه اجرا می شود، نظرسنجی را برای رویدادهای کنترلر بازی شروع می کند.
InputDeviceListener
رابط InputManager.InputDeviceListener را منعکس می کند. هنگامی که یک کنترلر بازی اضافه، تغییر یا حذف شده است، به بازی شما اطلاع می دهد.

سپس، پیاده‌سازی‌هایی را برای InputManagerCompat ایجاد کنید که در نسخه‌های مختلف پلتفرم کار می‌کنند. اگر بازی شما روی اندروید 4.1 یا بالاتر اجرا می‌شود و یک روش InputManagerCompat را فراخوانی می‌کند، اجرای پروکسی روش معادل را در InputManager فراخوانی می‌کند. با این حال، اگر بازی شما روی اندروید 3.1 تا اندروید 4.0 اجرا می‌شود، پیاده‌سازی سفارشی با استفاده از APIهایی که حداکثر تا اندروید 3.1 معرفی شده‌اند، روش‌های InputManagerCompat را فراخوانی می‌کند. صرف نظر از اینکه کدام نسخه خاص از پیاده سازی در زمان اجرا استفاده می شود، پیاده سازی نتایج تماس را به طور شفاف به بازی ارسال می کند.

شکل 1. نمودار کلاس پیاده سازی های رابط و نسخه خاص.

رابط را در اندروید 4.1 و بالاتر پیاده سازی کنید

InputManagerCompatV16 پیاده سازی رابط InputManagerCompat است که روش پراکسی را به یک InputManager واقعی و InputManager.InputDeviceListener فراخوانی می کند. InputManager از Context سیستم به دست می آید.

کاتلین

// 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
    ...
}

جاوا

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

}

رابط را روی اندروید 3.1 تا اندروید 4.0 پیاده سازی کنید

برای ایجاد یک پیاده سازی از InputManagerCompat که از اندروید 3.1 تا اندروید 4.0 پشتیبانی می کند، می توانید از اشیاء زیر استفاده کنید:

  • SparseArray از شناسه‌های دستگاه برای ردیابی کنترل‌کننده‌های بازی که به دستگاه متصل هستند.
  • یک Handler برای پردازش رویدادهای دستگاه. هنگامی که یک برنامه راه اندازی یا از سر گرفته می شود، Handler پیامی برای شروع نظرسنجی برای قطع ارتباط کنترلر بازی دریافت می کند. Handler حلقه‌ای را شروع می‌کند تا هر کنترل‌کننده بازی متصل شناخته شده را بررسی کند و ببیند آیا شناسه دستگاه برگردانده شده است یا خیر. مقدار بازگشتی null نشان می دهد که کنترلر بازی قطع شده است. هنگامی که برنامه متوقف می شود، Handler نظرسنجی را متوقف می کند.
  • Map ای از اشیاء InputManagerCompat.InputDeviceListener . شما از شنوندگان برای به روز رسانی وضعیت اتصال کنترلرهای بازی ردیابی شده استفاده خواهید کرد.

کاتلین

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

جاوا

// 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() را لغو کنید. این روش بررسی می کند که آیا یک کنترلر بازی متصل شده قطع شده است یا خیر و به شنوندگان ثبت نام شده اطلاع می دهد.

کاتلین

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

جاوا

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

برای شروع و توقف نظرسنجی برای قطع ارتباط کنترلر بازی، این روش ها را نادیده بگیرید:

کاتلین

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

جاوا

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() را لغو کنید. وقتی سیستم یک رویداد حرکتی را گزارش می‌کند، بررسی کنید که آیا این رویداد از شناسه دستگاهی است که قبلاً ردیابی شده است یا از یک شناسه دستگاه جدید. اگر شناسه دستگاه جدید است، به شنوندگان ثبت نام شده اطلاع دهید.

کاتلین

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

جاوا

@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 اجرا می شود، روش پاسخ تماس مناسب شنونده فراخوانی می شود تا اگر کنترلر بازی اضافه، تغییر یا حذف شده است، علامت دهد.

کاتلین

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

}

جاوا

@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 دارید: یکی که روی دستگاه‌های دارای Android نسخه 4.1 و بالاتر کار می‌کند و دیگری که روی دستگاه‌های دارای Android 3.1 تا Android 4.0 کار می‌کند.

از پیاده سازی مخصوص نسخه استفاده کنید

منطق سوئیچینگ نسخه خاص در کلاسی اجرا می شود که به عنوان یک کارخانه عمل می کند.

کاتلین

object Factory {
    fun getInputManager(context: Context): InputManagerCompat =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                InputManagerV16(context)
            } else {
                InputManagerV9()
            }
}

جاوا

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 اصلی خود ثبت کنید. به دلیل منطق تغییر نسخه ای که تنظیم کرده اید، بازی شما به طور خودکار از پیاده سازی مناسب برای نسخه اندرویدی که دستگاه در حال اجرا است استفاده می کند.

کاتلین

class GameView(context: Context) : View(context), InputManager.InputDeviceListener {
    private val inputManager: InputManagerCompat = Factory.getInputManager(context).apply {
        registerInputDeviceListener(this@GameView, null)
        ...
    }
    ...
}

جاوا

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() در نمای اصلی خود نادیده بگیرید، همانطور که در Handle a MotionEvent from a Game Controller توضیح داده شده است. بازی شما اکنون باید بتواند رویدادهای کنترلر بازی را به طور مداوم در دستگاه‌های دارای Android نسخه 3.1 (سطح API 12) و بالاتر پردازش کند.

کاتلین

override fun onGenericMotionEvent(event: MotionEvent): Boolean {
    inputManager.onGenericMotionEvent(event)

    // Handle analog input from the controller as normal
    ...
    return super.onGenericMotionEvent(event)
}

جاوا

@Override
public boolean onGenericMotionEvent(MotionEvent event) {
    inputManager.onGenericMotionEvent(event);

    // Handle analog input from the controller as normal
    ...
    return super.onGenericMotionEvent(event);
}

می‌توانید پیاده‌سازی کامل این کد سازگاری را در کلاس GameView در نمونه ControllerSample.zip موجود برای دانلود در بالا بیابید.