תמיכה בבקרים בגרסאות שונות של Android

אם אתם תומכים בבקרי משחקים במשחק שלכם, באחריותכם לוודא שהמשחק מגיב לבקרים באופן עקבי במכשירים שפועלים בגרסאות שונות של Android. כך המשחק שלכם יכול להגיע לקהל רחב יותר, והשחקנים יכולים ליהנות מחוויית משחק חלקה עם הבקרים שלהם גם כשהם מחליפים את מכשירי Android או משדרגים אותם.

בשיעור הזה נסביר איך להשתמש בממשקי API שזמינים ב-Android 4.1 ומעלה בצורה שתואמת לאחור, כדי שהמשחק שלכם יתמוך בתכונות הבאות במכשירים עם Android 3.1 ומעלה:

  • המשחק יכול לזהות אם נוסף בקר משחק חדש, אם הוא השתנה או אם הוא הוסר.
  • המשחק יכול לשלוח שאילתה לגבי היכולות של בקר משחקים.
  • המשחק יכול לזהות אירועי תנועה נכנסים משלט משחק.

הכנה להפשטת ממשקי API לתמיכה בבקרי משחקים

נניח שאתם רוצים לקבוע אם סטטוס החיבור של בקר משחקים השתנה במכשירים שמריצים Android 3.1 (רמת API‏ 12). עם זאת, ממשקי ה-API זמינים רק ב-Android מגרסה 4.1 (רמת API‏ 16) ואילך, ולכן צריך לספק הטמעה שתומכת ב-Android מגרסה 4.1 ואילך, וגם מנגנון חלופי שתומך ב-Android מגרסה 3.1 עד Android 4.0.

כדי לעזור לכם להבין אילו תכונות דורשות מנגנון חלופי לגרסאות ישנות יותר, בטבלה 1 מפורטים ההבדלים בתמיכה בבקרי משחקים בין Android 3.1 (רמת API‏ 12) לבין Android 4.1 (רמת API‏ 16).

טבלה 1. ממשקי API לתמיכה בבקרי משחקים בגרסאות שונות של Android.

פרטי בקר Controller API רמת API‏ 12 רמת API‏ 16
זיהוי מכשיר getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
סטטוס החיבור onInputDeviceAdded()  
onInputDeviceChanged()  
onInputDeviceRemoved()  
זיהוי אירוע קלט לחיצה על לחצן הכיוונים (D-pad) ‏( KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_CENTER)
לחיצה על לחצן ב-Gamepad ‏( 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. יוצרים הטמעה של פרוקסי לממשק שמשתמש בממשקי API ב-Android מגרסה 4.1 ומעלה.
  3. יוצרים הטמעה מותאמת אישית של הממשק באמצעות ממשקי API שזמינים בגרסאות Android 3.1 עד Android 4.0.
  4. יוצרים את הלוגיקה למעבר בין ההטמעות האלה בזמן ריצה, ומתחילים להשתמש בממשק במשחק.

במאמר יצירת ממשקי משתמש שתואמים לאחור מוסבר איך אפשר להשתמש באבסטרקציה כדי לוודא שאפליקציות יכולות לפעול בצורה שתואמת לאחור בגרסאות שונות של Android.

הוספת ממשק לתאימות לאחור

כדי לספק תאימות לאחור, אפשר ליצור ממשק בהתאמה אישית ואז להוסיף הטמעות ספציפיות לגרסה. אחד היתרונות של הגישה הזו הוא שהיא מאפשרת לשקף את הממשקים הציבוריים ב-Android 4.1 (רמת API‏ 16) שתומכים בבקרי משחקים.

Kotlin

// The InputManagerCompat interface is a reference example.
// The full code is provided in the ControllerSample.zip sample.
interface InputManagerCompat {
    val inputDeviceIds: IntArray
    fun getInputDevice(id: Int): InputDevice

    fun registerInputDeviceListener(
            listener: InputManager.InputDeviceListener,
            handler: Handler?
    )

    fun unregisterInputDeviceListener(listener:InputManager.InputDeviceListener)

    fun onGenericMotionEvent(event: MotionEvent)

    fun onPause()
    fun onResume()

    interface InputDeviceListener {
        fun onInputDeviceAdded(deviceId: Int)
        fun onInputDeviceChanged(deviceId: Int)
        fun onInputDeviceRemoved(deviceId: Int)
    }
}

Java

// The InputManagerCompat interface is a reference example.
// The full code is provided in the ControllerSample.zip sample.
public interface InputManagerCompat {
    ...
    public InputDevice getInputDevice(int id);
    public int[] getInputDeviceIds();

    public void registerInputDeviceListener(
            InputManagerCompat.InputDeviceListener listener,
            Handler handler);
    public void unregisterInputDeviceListener(
            InputManagerCompat.InputDeviceListener listener);

    public void onGenericMotionEvent(MotionEvent event);

    public void onPause();
    public void onResume();

    public interface InputDeviceListener {
        void onInputDeviceAdded(int deviceId);
        void onInputDeviceChanged(int deviceId);
        void onInputDeviceRemoved(int deviceId);
    }
    ...
}

ממשק InputManagerCompat מספק את השיטות הבאות:

getInputDevice()
שיקוף מסך getInputDevice(). מקבל את אובייקט InputDevice שמייצג את היכולות של בקר משחקים.
getInputDeviceIds()
שיקוף מסך getInputDeviceIds(). הפונקציה מחזירה מערך של מספרים שלמים, שכל אחד מהם הוא מזהה של מכשיר קלט שונה. האפשרות הזו שימושית אם אתם מפתחים משחק עם תמיכה בכמה שחקנים ורוצים לזהות כמה בקרים מחוברים.
registerInputDeviceListener()
שיקוף מסך registerInputDeviceListener(). מאפשר להירשם לקבלת הודעה כשמכשיר חדש מתווסף, משתנה או מוסר.
unregisterInputDeviceListener()
שיקוף מסך unregisterInputDeviceListener(). ביטול הרישום של מעבד אירוע של מכשיר קלט.
onGenericMotionEvent()
שיקוף מסך onGenericMotionEvent(). מאפשר למשחק ליירט ולטפל באובייקטים ובערכי ציר שמייצגים אירועים כמו תנועות של ג'ויסטיק ולחיצות על הדק אנלוגי.
MotionEvent
onPause()
ההגדרה מפסיקה את הסקר לגבי אירועים של בקר משחקים כשהפעילות הראשית מושהית או כשהמשחק כבר לא נמצא במוקד.
onResume()
התחלת שליחת בקשות (polling) לאירועים של בקר משחקים כשהפעילות הראשית ממשיכה, או כשהמשחק מתחיל ופועל בחזית.
InputDeviceListener
משקף את הממשק.InputManager.InputDeviceListener מאפשרת למשחק לדעת מתי נוסף בקר משחק, מתי הוא השתנה או מתי הוא הוסר.

לאחר מכן, יוצרים הטמעות של InputManagerCompat שפועלות בגרסאות שונות של הפלטפורמה. אם המשחק שלכם פועל ב-Android 4.1 או בגרסה מתקדמת יותר ומפעיל method של InputManagerCompat, הטמעת ה-proxy מפעילה את ה-method המקביל ב-InputManager. עם זאת, אם המשחק שלכם פועל ב-Android מגרסה 3.1 עד גרסה 4.0, ההטמעה המותאמת אישית מעבדת קריאות לשיטות InputManagerCompat באמצעות ממשקי API שהוצגו לכל המאוחר ב-Android 3.1. לא משנה באיזו הטמעה ספציפית לגרסה נעשה שימוש בזמן הריצה, ההטמעה מעבירה את תוצאות הקריאה בחזרה למשחק באופן שקוף.

דיאגרמת מחלקות של ממשק והטמעות ספציפיות לגרסה.
איור 1.: דיאגרמת מחלקות של ממשק ויישומים ספציפיים לגרסה.

הטמעה של הממשק ב-Android מגרסה 4.1 ואילך

InputManagerCompatV16 הוא הטמעה של הממשק InputManagerCompat שמעביר קריאות של מתודות ל-InputManager ו-InputManager.InputDeviceListener בפועל. ה-InputManager מתקבל מContext המערכת.

Kotlin

// The InputManagerCompatV16 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV16(
        context: Context,
        private val inputManager: InputManager =
            context.getSystemService(Context.INPUT_SERVICE) as InputManager,
        private val listeners:
            MutableMap<InputManager.InputDeviceListener, V16InputDeviceListener> = mutableMapOf()
) : InputManagerCompat {
    override val inputDeviceIds: IntArray = inputManager.inputDeviceIds

    override fun getInputDevice(id: Int): InputDevice = inputManager.getInputDevice(id)

    override fun registerInputDeviceListener(
            listener: InputManager.InputDeviceListener,
            handler: Handler?
    ) {
        V16InputDeviceListener(listener).also { v16listener ->
            inputManager.registerInputDeviceListener(v16listener, handler)
            listeners += listener to v16listener
        }
    }

    // Do the same for unregistering an input device listener
    ...

    override fun onGenericMotionEvent(event: MotionEvent) {
        // unused in V16
    }

    override fun onPause() {
        // unused in V16
    }

    override fun onResume() {
        // unused in V16
    }

}

class V16InputDeviceListener(
        private val idl: InputManager.InputDeviceListener
) : InputManager.InputDeviceListener {

    override fun onInputDeviceAdded(deviceId: Int) {
        idl.onInputDeviceAdded(deviceId)
    }
    // Do the same for device change and removal
    ...
}

Java

// The InputManagerCompatV16 class is a reference implementation.
// The full code is provided in the ControllerSample.zip sample.
public class InputManagerV16 implements InputManagerCompat {

    private final InputManager inputManager;
    private final Map<InputManagerCompat.InputDeviceListener,
            V16InputDeviceListener> listeners;

    public InputManagerV16(Context context) {
        inputManager = (InputManager)
                context.getSystemService(Context.INPUT_SERVICE);
        listeners = new HashMap<InputManagerCompat.InputDeviceListener,
                V16InputDeviceListener>();
    }

    @Override
    public InputDevice getInputDevice(int id) {
        return inputManager.getInputDevice(id);
    }

    @Override
    public int[] getInputDeviceIds() {
        return inputManager.getInputDeviceIds();
    }

    static class V16InputDeviceListener implements
            InputManager.InputDeviceListener {
        final InputManagerCompat.InputDeviceListener mIDL;

        public V16InputDeviceListener(InputDeviceListener idl) {
            mIDL = idl;
        }

        @Override
        public void onInputDeviceAdded(int deviceId) {
            mIDL.onInputDeviceAdded(deviceId);
        }

        // Do the same for device change and removal
        ...
    }

    @Override
    public void registerInputDeviceListener(InputDeviceListener listener,
            Handler handler) {
        V16InputDeviceListener v16Listener = new
                V16InputDeviceListener(listener);
        inputManager.registerInputDeviceListener(v16Listener, handler);
        listeners.put(listener, v16Listener);
    }

    // Do the same for unregistering an input device listener
    ...

    @Override
    public void onGenericMotionEvent(MotionEvent event) {
        // unused in V16
    }

    @Override
    public void onPause() {
        // unused in V16
    }

    @Override
    public void onResume() {
        // unused in V16
    }

}

הטמעה של הממשק ב-Android מגרסה 3.1 עד גרסה 4.0

כדי ליצור הטמעה של InputManagerCompat שתומכת ב-Android מגרסה 3.1 עד גרסה 4.0, אפשר להשתמש באובייקטים הבאים:

  • SparseArray של מזהי מכשירים למעקב אחרי השלטים לגיימינג שמחוברים למכשיר.
  • Handler לעיבוד אירועים במכשיר. כשמפעילים או מפעילים מחדש אפליקציה, Handler מקבל הודעה להתחיל לבצע סקר כדי לבדוק אם בקר המשחקים מנותק. הסקריפט Handler יתחיל לולאה כדי לבדוק כל שלט גיימינג מחובר מוכר ולראות אם מוחזר מזהה מכשיר. ערך ההחזרה null מציין שהשלט של משחק המחשב מנותק. האפליקציה Handler מפסיקה את הסקר כשהיא מושהית.
  • Map של אובייקטים מסוג InputManagerCompat.InputDeviceListener. תשתמשו ב-listeners כדי לעדכן את סטטוס החיבור של בקרי המשחקים שעוקבים אחריהם.

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(). השיטה הזו בודקת אם שלט משחק שמחובר למכשיר התנתק, ומודיעה על כך ל-listeners רשומים.

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 מבקר משחקים (גרסה 3.0 ואילך). המשחק שלך אמור להיות מסוגל לעבד עכשיו אירועים של בקר משחקים באופן עקבי במכשירים עם Android 3.1 (רמת API).

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 שזמינה להורדה.