إتاحة استخدام وحدات التحكُّم في جميع إصدارات Android

تقع على عاتقك مسؤولية إذا كنت تدعم وحدات تحكُّم الألعاب في لعبتك. للتأكّد من أنّ لعبتك تستجيب باستمرار لوحدات التحكّم في جميع الأجهزة تعمل على إصدارات مختلفة من Android. يتيح ذلك للعبتك الوصول إلى ويمكن أن يستمتع اللاعبون بتجربة لعب سلسة وحدات التحكم حتى عند تبديل أجهزة Android أو ترقيتها.

يوضح هذا الدرس كيفية استخدام واجهات برمجة التطبيقات المتوفرة في الإصدار 4.1 من نظام التشغيل Android والإصدارات الأحدث بطريقة متوافقة مع الأنظمة القديمة، ما يتيح للعبتك توفير ما يلي الميزات المتاحة على الأجهزة التي تعمل بنظام التشغيل Android 3.1 والإصدارات الأحدث:

  • يمكن أن ترصد اللعبة ما إذا تمت إضافة ذراع تحكّم جديد في الألعاب أو تغييره أو إزالته.
  • يمكن أن تستعلم اللعبة من إمكانات وحدة التحكّم في الألعاب.
  • يمكن للّعبة التعرّف على أحداث الحركة الواردة من ذراع التحكّم في الألعاب.

تستند الأمثلة في هذا الدرس إلى أداة التنفيذ المرجعية. التي يوفّرها النموذج "ControllerSample.zip" المتاح للتنزيل أعلاه. يعرض هذا النموذج كيفية تنفيذ InputManagerCompat. للتوافق مع إصدارات مختلفة من Android. لتجميع العينة، يمكنك أن يستخدم الإصدار Android 4.1 (المستوى 16 من واجهة برمجة التطبيقات) أو الإصدارات الأحدث. بمجرد تجميعه، يعمل نموذج التطبيق تعمل على أي جهاز يعمل بنظام التشغيل Android 3.1 (المستوى 12 لواجهة برمجة التطبيقات) أو أعلى بصفته الإصدار الهدف.

الاستعداد لواجهات برمجة تطبيقات مختصرة لاستخدامها في وحدة التحكّم في الألعاب

لنفترض أنك تريد أن تتمكن من تحديد ما إذا كان اتصال وحدة التحكم في الألعاب تغيير الحالة على الأجهزة التي تعمل بنظام التشغيل Android 3.1 (المستوى 12 لواجهة برمجة التطبيقات). ومع ذلك، تتوفر واجهات برمجة التطبيقات في الإصدار 4.1 (المستوى 16 من واجهة برمجة التطبيقات) والإصدارات الأحدث فقط، لذلك إلى توفير طريقة تنفيذ تتوافق مع الإصدار 4.1 من نظام التشغيل Android والإصدارات الأحدث أثناء توفير آلية احتياطية متوافقة مع إصدارات Android 3.1 إلى Android 4.0.

ولمساعدتك في تحديد الميزات التي تتطلب مثل هذه الآلية الاحتياطية الإصدارات القديمة، يسرد الجدول 1 الاختلافات في دعم ذراع التحكم في الألعاب بين الإصدارَين Android 3.1 (المستوى 12 لواجهة برمجة التطبيقات) و4.1 (مستوى واجهة برمجة التطبيقات) 16.

الجدول 1. واجهات برمجة التطبيقات التي تتيح استخدام وحدات التحكّم في الألعاب بإصدارات Android المختلفة.

معلومات مسؤول التحكّم بالبيانات واجهة برمجة تطبيقات وحدة التحكم المستوى 12 من واجهة برمجة التطبيقات المستوى 16 من واجهة برمجة التطبيقات
تحديد الجهاز getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
حالة الاتصال بالإنترنت onInputDeviceAdded()  
onInputDeviceChanged()  
onInputDeviceRemoved()  
تحديد حدث الإدخال اضغط على لوحة التحكّم ( 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. تحديد واجهة Java وسيطة تستبعد تنفيذ ميزات وحدة التحكم في الألعاب التي تتطلبها لعبتك.
  2. إنشاء تنفيذ خادم وكيل للواجهة التي تستخدم واجهات برمجة التطبيقات في Android 4.1 والإصدارات الأحدث.
  3. إنشاء تنفيذ مخصّص للواجهة التي تستخدم واجهات برمجة التطبيقات المتاحة بين إصدارات Android 3.1 وAndroid 4.0.
  4. أنشئ منطقًا للتبديل بين هذه التطبيقات في وقت التشغيل، وبدء استخدام الواجهة في لعبتك

لإلقاء نظرة عامة على كيفية استخدام التجريد للتأكد من أن التطبيقات تعمل بطريقة متوافقة مع الأنظمة القديمة عبر إصدارات Android المختلفة، راجِع صناعة واجهات المستخدم المتوافقة مع الأنظمة القديمة

إضافة واجهة للتوافق مع الأنظمة القديمة

لتوفير التوافق مع الأنظمة القديمة، يمكنك إنشاء واجهة مخصصة ثم وإضافة عمليات تنفيذ خاصة بالإصدار. تتمثل إحدى ميزات هذا النهج في أنه تتيح لك إجراء نسخ مطابق للواجهات العامة على Android 4.1 (المستوى 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(). إلغاء تسجيل أداة معالجة الحدث في جهاز إدخال
onGenericMotionEvent()
المرايا onGenericMotionEvent(). السماح للعبتك باعتراض الطلبات والتعامل معها عناصر MotionEvent وقيم المحاور التي تمثل الأحداث مثل حركات ذراع التحكّم وضغطات المشغّل التناظري
onPause()
يوقف الاستطلاع في أحداث وحدات التحكم في الألعاب عند إيقاف النشاط الرئيسي مؤقتًا أو عندما تتوقف اللعبة عن التركيز
onResume()
يبدأ التصويت بشأن أحداث وحدة التحكم في الألعاب عند يتم استئناف النشاط الرئيسي أو عند بدء اللعبة وتشغيلها خلال المقدّمة
InputDeviceListener
يعكس InputManager.InputDeviceListener من واجهة pyplot. تسمح للّعبة بمعرفة ما إذا تمت إضافة ذراع تحكّم في الألعاب أو تغييرها أو إضافة وحدة تحكّم في الألعاب. تمت إزالته.

الخطوة التالية، إنشاء عمليات تنفيذ لـ InputManagerCompat ناجحة عبر إصدارات الأنظمة الأساسية المختلفة. إذا كانت لعبتك تعمل بنظام التشغيل Android 4.1 أو ويستدعي طريقة InputManagerCompat، فإن تنفيذ الخادم الوكيل الطريقة المكافئة في InputManager. ومع ذلك، إذا كانت لعبتك تعمل بإصدارات Android من 3.1 إلى 4.0، سيتم تطبيق معالجة الطلبات إلى InputManagerCompat طرق باستخدام فقط واجهات برمجة التطبيقات التي تم تقديمها في وقت لا يتجاوز الإصدار 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 يتوافق مع الإصدارات من 3.1 إلى Android 4.0 وحتى 4.0، يمكنك استخدام الكائنات التالية:

  • SparseArray من أرقام تعريف الأجهزة لتتبُّع وحدات التحكّم في الألعاب المرتبطة بالجهاز
  • Handler لمعالجة أحداث الجهاز. عند بدء تشغيل التطبيق أو استئنافه، يتلقّى Handler رسالة لبدء الاستطلاع. لإلغاء ربط وحدة التحكّم في الألعاب سيبدأ Handler للتحقّق من كل وحدة تحكّم معروفة في الألعاب مرتبطة ومعرفة ما إذا كان رقم تعريف الجهاز عاد. تشير القيمة المعروضة null إلى أنّ ذراع التحكّم في الألعاب غير متصل. يتوقف Handler عن التصويت عندما يكون التطبيق متوقف مؤقتًا.
  • Map من InputManagerCompat.InputDeviceListener الأخرى. ستستخدم المستمعين لتحديث حالة اتصال التتبع ذراع التحكّم في الألعاب.

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() . تتحقّق هذه الطريقة مما إذا تم تثبيت ذراع التحكّم في الألعاب المرفق. غير متصلين وتُعلم المستمعين المسجلين.

Kotlin

private class PollingMessageHandler(
        inputManager: InputManagerV9,
        private val mInputManager: WeakReference<InputManagerV9> = WeakReference(inputManager)
) : Handler() {

    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        when (msg.what) {
            MESSAGE_TEST_FOR_DISCONNECT -> {
                mInputManager.get()?.also { imv ->
                    val time = SystemClock.elapsedRealtime()
                    val size = imv.devices.size()
                    for (i in 0 until size) {
                        imv.devices.valueAt(i)?.also { lastContact ->
                            if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
                                // check to see if the device has been
                                // disconnected
                                val id = imv.devices.keyAt(i)
                                if (null == InputDevice.getDevice(id)) {
                                    // Notify the registered listeners
                                    // that the game controller is disconnected
                                    imv.devices.remove(id)
                                } else {
                                    lastContact[0] = time
                                }
                            }
                        }
                    }
                    sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, CHECK_ELAPSED_TIME)
                }
            }
        }
    }
}

Java

private static class PollingMessageHandler extends Handler {
    private final WeakReference<InputManagerV9> inputManager;

    PollingMessageHandler(InputManagerV9 im) {
        inputManager = new WeakReference<InputManagerV9>(im);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case MESSAGE_TEST_FOR_DISCONNECT:
                InputManagerV9 imv = inputManager.get();
                if (null != imv) {
                    long time = SystemClock.elapsedRealtime();
                    int size = imv.devices.size();
                    for (int i = 0; i < size; i++) {
                        long[] lastContact = imv.devices.valueAt(i);
                        if (null != lastContact) {
                            if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
                                // check to see if the device has been
                                // disconnected
                                int id = imv.devices.keyAt(i);
                                if (null == InputDevice.getDevice(id)) {
                                    // Notify the registered listeners
                                    // that the game controller is disconnected
                                    imv.devices.remove(id);
                                } else {
                                    lastContact[0] = time;
                                }
                            }
                        }
                    }
                    sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
                            CHECK_ELAPSED_TIME);
                }
                break;
        }
    }
}

لبدء إجراء الاستطلاعات وإيقافها لإلغاء ربط وحدة التحكّم في الألعاب، عليك إلغاء الإعداد. هاتين الطريقتين:

Kotlin

private const val MESSAGE_TEST_FOR_DISCONNECT = 101
private const val CHECK_ELAPSED_TIME = 3000L

class InputManagerV9(
        val devices: SparseArray<Array<Long>> = SparseArray(),
        private val listeners:
        MutableMap<InputManager.InputDeviceListener, Handler> = mutableMapOf()
) : InputManagerCompat {
    ...
    override fun onPause() {
        defaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT)
    }

    override fun onResume() {
        defaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, CHECK_ELAPSED_TIME)
    }
    ...
}

Java

private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
private static final long CHECK_ELAPSED_TIME = 3000L;

@Override
public void onPause() {
    defaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
}

@Override
public void onResume() {
    defaultHandler.sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
            CHECK_ELAPSED_TIME);
}

لرصد إضافة جهاز إدخال، يمكنك إلغاء طريقة onGenericMotionEvent(). عندما يُبلغ النظام عن حدث حركة، تحقَّق مما إذا كان مصدر هذا الحدث هو رقم تعريف جهاز تم تتبعه أو من معرّف الجهاز الجديد. إذا كان رقم تعريف الجهاز جديدًا، يُرجى إعلام المستمعين المسجَّلين.

Kotlin

override fun onGenericMotionEvent(event: MotionEvent) {
    // detect new devices
    val id = event.deviceId
    val timeArray: Array<Long> = mDevices.get(id) ?: run {
        // Notify the registered listeners that a game controller is added
        ...
        arrayOf<Long>().also {
            mDevices.put(id, it)
        }
    }
    timeArray[0] = SystemClock.elapsedRealtime()
}

Java

@Override
public void onGenericMotionEvent(MotionEvent event) {
    // detect new devices
    int id = event.getDeviceId();
    long[] timeArray = mDevices.get(id);
    if (null == timeArray) {
        // Notify the registered listeners that a game controller is added
        ...
        timeArray = new long[1];
        mDevices.put(id, timeArray);
    }
    long time = SystemClock.elapsedRealtime();
    timeArray[0] = time;
}

يتم تنفيذ إشعار المستمعين باستخدام عنصر Handler لإرسال DeviceEvent الكائن Runnable في قائمة انتظار الرسائل. DeviceEvent يحتوي على مرجع إلى InputManagerCompat.InputDeviceListener. فعندما تعمل DeviceEvent، وهي طريقة معاودة الاتصال المناسبة لخدمة المستمع لإرسال إشارة إلى ما إذا تمت إضافة وحدة التحكّم في الألعاب أو تغييرها أو إزالتها.

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: إحداهما تعمل على الأجهزة التي تعمل بنظام التشغيل 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 (المستوى 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. متوفر للتنزيل أعلاه.