Controller für verschiedene Android-Versionen unterstützen

Wenn du in deinem Spiel Gamecontroller unterstützt, liegt es in deiner Verantwortung, dass dein Spiel auf allen Geräten mit verschiedenen Android-Versionen einheitlich auf Controller reagiert. So kann Ihr Spiel eine größere Zielgruppe erreichen und Ihre Spieler können selbst dann ein nahtloses Gameplay mit ihren Controllern erleben, wenn sie ihr Android-Gerät wechseln oder upgraden.

In dieser Lektion erfahren Sie, wie Sie die ab Android 4.1 verfügbaren APIs abwärtskompatibel nutzen, damit Ihr Spiel die folgenden Funktionen auf Geräten mit Android 3.1 und höher unterstützt:

  • Das Spiel kann erkennen, ob ein neuer Controller hinzugefügt, geändert oder entfernt wird.
  • Das Spiel kann die Funktionen eines Gamecontrollers abfragen.
  • Das Spiel kann von einem Gamecontroller eingehende Bewegungsereignisse erkennen.

Die Beispiele in dieser Lektion basieren auf der Referenzimplementierung aus dem Beispiel-ControllerSample.zip, das oben heruntergeladen werden kann. In diesem Beispiel wird gezeigt, wie die InputManagerCompat-Schnittstelle implementiert wird, um verschiedene Android-Versionen zu unterstützen. Für die Kompilierung des Beispiels müssen Sie Android 4.1 (API-Level 16) oder höher verwenden. Nach der Kompilierung wird die Beispiel-App auf jedem Gerät mit Android 3.1 (API-Level 12) oder höher als Build-Ziel ausgeführt.

Abstrakte APIs für die Unterstützung von Gamecontrollern vorbereiten

Angenommen, Sie möchten feststellen, ob sich der Verbindungsstatus eines Spielecontrollers auf Geräten mit Android 3.1 (API-Level 12) geändert hat. Die APIs sind jedoch nur in Android 4.1 (API-Level 16) und höher verfügbar. Sie müssen also eine Implementierung bereitstellen, die Android 4.1 und höher unterstützt, und einen Fallback-Mechanismus bereitstellen, der Android 3.1 bis Android 4.0 unterstützt.

Damit ihr feststellen könnt, für welche Funktionen ein solcher Fallback-Mechanismus für ältere Versionen erforderlich ist, sind in Tabelle 1 die Unterschiede in der Gamecontroller-Unterstützung zwischen Android 3.1 (API-Level 12) und 4.1 (API-Level 16) aufgeführt.

Tabelle 1 APIs für Gamecontroller werden in verschiedenen Android-Versionen unterstützt.

Daten zum Verantwortlichen Controller-API API-Level 12 API-Level 16
Geräteidentifikation getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
Verbindungsstatus onInputDeviceAdded()  
onInputDeviceChanged()  
onInputDeviceRemoved()  
Identifizierung des Eingabeereignisses Steuerkreuz drücken ( KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_CENTER)
Drücken der Gamepad-Taste ( BUTTON_A, BUTTON_B, BUTTON_THUMBL, BUTTON_THUMBR, BUTTON_SELECT, BUTTON_START, BUTTON_R1, BUTTON_L1, BUTTON_R2, BUTTON_L2)
Bewegung des Joystick- und Hutschalters ( AXIS_X, AXIS_Y, AXIS_Z, AXIS_RZ, AXIS_HAT_X, AXIS_HAT_Y)
Drücken des analogen Triggers (AXIS_LTRIGGER, AXIS_RTRIGGER)

Mit Abstraktion können Sie eine versionssensitive Gamecontroller-Unterstützung entwickeln, die plattformübergreifend funktioniert. Dieser Ansatz umfasst die folgenden Schritte:

  1. Definieren Sie eine Java-Zwischenschnittstelle, die die für das Spiel erforderliche Implementierung der Controller-Funktionen abstrahiert.
  2. Erstellen Sie eine Proxyimplementierung Ihrer Schnittstelle, die APIs in Android 4.1 und höher verwendet.
  3. Erstellen Sie eine benutzerdefinierte Implementierung Ihrer Schnittstelle, die APIs verwendet, die zwischen Android 3.1 und Android 4.0 verfügbar sind.
  4. Erstellen Sie die Logik für den Wechsel zwischen diesen Implementierungen zur Laufzeit und verwenden Sie die Schnittstelle in Ihrem Spiel.

Einen Überblick darüber, wie Sie mit Abstraktion dafür sorgen können, dass Anwendungen in verschiedenen Android-Versionen abwärtskompatibel funktionieren, finden Sie unter Abwärtskompatible UIs erstellen.

Schnittstelle für Abwärtskompatibilität hinzufügen

Für Abwärtskompatibilität kannst du eine benutzerdefinierte Schnittstelle erstellen und dann versionsspezifische Implementierungen hinzufügen. Ein Vorteil dieses Ansatzes besteht darin, dass Sie die öffentlichen Schnittstellen unter Android 4.1 (API-Level 16) spiegeln können, die Gamecontroller unterstützen.

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

Die Schnittstelle InputManagerCompat bietet folgende Methoden:

getInputDevice()
Spiegelt getInputDevice(). Ruft das Objekt InputDevice ab, das die Funktionen eines Gamecontrollers darstellt.
getInputDeviceIds()
Spiegelt getInputDeviceIds(). Gibt ein Array mit Ganzzahlen zurück, von denen jede eine ID für ein anderes Eingabegerät ist. Dies ist nützlich, wenn Sie ein Spiel erstellen, das mehrere Spieler unterstützt, und feststellen möchten, wie viele Controller verbunden sind.
registerInputDeviceListener()
Spiegelt registerInputDeviceListener(). Sie können sich registrieren, um informiert zu werden, wenn ein neues Gerät hinzugefügt, geändert oder entfernt wird.
unregisterInputDeviceListener()
Spiegelt unregisterInputDeviceListener(). Hebt die Registrierung eines Eingabegeräte-Listeners auf.
onGenericMotionEvent()
Spiegelt onGenericMotionEvent(). Ermöglicht es Ihrem Spiel, MotionEvent-Objekte und Achsenwerte, die Ereignisse wie Joystickbewegungen und Drücken von analogen Triggern darstellen, abzufangen und zu verarbeiten.
onPause()
Beendet die Abfrage von Controller-Ereignissen, wenn die Hauptaktivität pausiert ist oder das Spiel nicht mehr im Fokus steht.
onResume()
Startet das Abfragen von Controller-Ereignissen, wenn die Hauptaktivität fortgesetzt wird oder wenn das Spiel gestartet und im Vordergrund ausgeführt wird.
InputDeviceListener
Spiegelt die Schnittstelle InputManager.InputDeviceListener. Informiert das Spiel darüber, wenn ein Controller hinzugefügt, geändert oder entfernt wurde.

Erstelle als Nächstes Implementierungen für InputManagerCompat, die auf verschiedenen Plattformversionen funktionieren. Wenn in Ihrem Spiel Android 4.1 oder höher ausgeführt wird und eine InputManagerCompat-Methode aufgerufen wird, ruft die Proxy-Implementierung die entsprechende Methode in InputManager auf. Wenn Ihr Spiel jedoch mit Android 3.1 bis Android 4.0 läuft, verarbeitet die benutzerdefinierte Implementierung Aufrufe an InputManagerCompat-Methoden mithilfe von APIs, die ab Android 3.1 eingeführt wurden. Unabhängig davon, welche versionspezifische Implementierung zur Laufzeit verwendet wird, übergibt die Implementierung die Aufrufergebnisse transparent an das Spiel.

Abbildung 1: Klassendiagramm für schnittstellen- und versionsspezifische Implementierungen

Die Oberfläche unter Android 4.1 und höher implementieren

InputManagerCompatV16 ist eine Implementierung der InputManagerCompat-Schnittstelle, die Methodenaufrufe an eine tatsächliche InputManager und InputManager.InputDeviceListener weiterleitet. Der InputManager wird aus dem System-Context abgerufen.

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
    }

}

Die Oberfläche unter Android 3.1 bis Android 4.0 implementieren

Du kannst die folgenden Objekte verwenden, um eine Implementierung von InputManagerCompat zu erstellen, die Android 3.1 bis Android 4.0 unterstützt:

  • Ein SparseArray mit Geräte-IDs zum Erfassen der Gamecontroller, die mit dem Gerät verbunden sind.
  • Ein Handler zum Verarbeiten von Geräteereignissen. Wenn eine App gestartet oder fortgesetzt wird, empfängt Handler eine Nachricht, mit der das Trennen der Verbindung zum Spiele-Controller gestartet werden kann. Handler startet eine Schleife, um jeden bekannten verbundenen Gamecontroller zu prüfen und festzustellen, ob eine Geräte-ID zurückgegeben wird. Der Rückgabewert null gibt an, dass der Gamecontroller getrennt ist. Handler beendet die Abfrage, wenn die Anwendung pausiert wird.
  • Ein Map mit InputManagerCompat.InputDeviceListener-Objekten. Die Listener werden verwendet, um den Verbindungsstatus der beobachteten Game-Controller zu aktualisieren.

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

Implementieren Sie ein PollingMessageHandler-Objekt, das Handler erweitert, und überschreiben Sie die Methode handleMessage(). Diese Methode prüft, ob ein angehängter Gamecontroller getrennt wurde, und benachrichtigt registrierte Listener.

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

Wenn Sie die Abfragen zum Trennen der Verbindung zu Spielecontrollern starten und beenden möchten, überschreiben Sie diese Methoden:

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

Wenn Sie feststellen möchten, ob ein Eingabegerät hinzugefügt wurde, überschreiben Sie die Methode onGenericMotionEvent(). Wenn das System ein Bewegungsereignis meldet, prüfen Sie, ob dieses Ereignis von einer Geräte-ID stammt, die bereits erfasst wird, oder von einer neuen Geräte-ID. Wenn die Geräte-ID neu ist, benachrichtigen Sie die registrierten Listener.

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

Um Listener zu benachrichtigen, wird mit dem Objekt Handler ein DeviceEvent-Runnable-Objekt an die Nachrichtenwarteschlange gesendet. DeviceEvent enthält einen Verweis auf ein InputManagerCompat.InputDeviceListener. Bei der Ausführung von DeviceEvent wird die entsprechende Callback-Methode des Listeners aufgerufen, um zu signalisieren, ob der Gamecontroller hinzugefügt, geändert oder entfernt wurde.

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

Es gibt jetzt zwei Implementierungen von InputManagerCompat: eine für Geräte mit Android 4.1 und höher und eine für Geräte mit Android 3.1 bis Android 4.0.

Versionsspezifische Implementierung verwenden

Die versionsspezifische Wechsellogik ist in einer Klasse implementiert, die als Factory fungiert.

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

Jetzt können Sie einfach ein InputManagerCompat-Objekt instanziieren und ein InputManagerCompat.InputDeviceListener in Ihrer Haupt-View registrieren. Durch die von Ihnen eingerichtete Logik für den Versionswechsel verwendet Ihr Spiel automatisch die Implementierung, die für die Android-Version, die auf dem Gerät ausgeführt wird, angemessen ist.

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

Als Nächstes überschreiben Sie die Methode onGenericMotionEvent() in der Hauptansicht, wie unter Bewegungsereignisse von einem Game-Controller verarbeiten beschrieben. Ihr Spiel sollte jetzt Controller-Ereignisse auf Geräten mit Android 3.1 (API-Level 12) und höher konsistent verarbeiten können.

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

Eine vollständige Implementierung dieses Kompatibilitätscodes finden Sie in der Klasse GameView im Beispiel-ControllerSample.zip, das oben heruntergeladen werden kann.