Obsługa kontrolerów w różnych wersjach Androida

Jeśli Twoja gra obsługuje kontrolery, musisz zadbać o to, aby reagowała na nie w spójny sposób na różnych urządzeniach z różnymi wersjami Androida. Dzięki temu Twoja gra dotrze do szerszego grona odbiorców, a gracze będą mogli cieszyć się płynną rozgrywką za pomocą kontrolerów nawet wtedy, gdy zmienią lub ulepszą swoje urządzenia z Androidem.

W tej lekcji dowiesz się, jak korzystać z interfejsów API dostępnych w Androidzie 4.1 i nowszym w sposób zgodny wstecznie, aby gra obsługiwała te funkcje na urządzeniach z Androidem 3.1 i nowszym:

  • Gra może wykryć, czy nowy kontroler został dodany, zmieniony lub usunięty.
  • Gra może sprawdzać możliwości kontrolera do gier.
  • Gra może rozpoznawać przychodzące zdarzenia ruchu z kontrolera do gier.

Przygotowanie do abstrakcji interfejsów API na potrzeby obsługi kontrolera do gier

Załóżmy, że chcesz określić, czy stan połączenia kontrolera do gier zmienił się na urządzeniach z Androidem 3.1 (poziom interfejsu API 12). Interfejsy API są jednak dostępne tylko w Androidzie 4.1 (poziom interfejsu API 16) i nowszych wersjach, więc musisz udostępnić implementację, która obsługuje Androida 4.1 i nowsze wersje, a jednocześnie zapewnić mechanizm rezerwowy, który obsługuje Androida 3.1 i nowsze wersje aż do Androida 4.0.

Aby pomóc Ci określić, które funkcje wymagają mechanizmu rezerwowego w przypadku starszych wersji, w tabeli 1 znajdziesz różnice w obsłudze kontrolerów do gier między Androidem 3.1 (poziom API 12) a 4.1 (poziom API 16).

Tabela 1. interfejsy API do obsługi kontrolerów do gier w różnych wersjach Androida;

Informacje o administratorze Controller API Poziom API 12 Poziom API 16
Identyfikacja urządzenia getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
Stan połączenia onInputDeviceAdded()  
onInputDeviceChanged()  
onInputDeviceRemoved()  
Identyfikacja zdarzenia wejściowego Naciśnięcie pada kierunkowego ( KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_CENTER)
Naciśnięcie przycisku gamepada ( BUTTON_A, BUTTON_B, BUTTON_THUMBL, BUTTON_THUMBR, BUTTON_SELECT, BUTTON_START, BUTTON_R1, BUTTON_L1, BUTTON_R2, BUTTON_L2)
Ruch dżojstika i przełącznika kierunkowego ( AXIS_X, AXIS_Y, AXIS_Z, AXIS_RZ, AXIS_HAT_X, AXIS_HAT_Y)
Naciśnięcie spustu analogowego ( AXIS_LTRIGGER, AXIS_RTRIGGER)

Możesz użyć abstrakcji, aby utworzyć obsługę kontrolera do gier z uwzględnieniem wersji, która działa na różnych platformach. To podejście obejmuje te kroki:

  1. Zdefiniuj pośredni interfejs Javy, który abstrahuje implementację funkcji kontrolera do gier wymaganych przez Twoją grę.
  2. Utwórz implementację interfejsu proxy, która korzysta z interfejsów API w Androidzie 4.1 i nowszych.
  3. Utwórz niestandardową implementację interfejsu, która korzysta z interfejsów API dostępnych w Androidzie w wersji od 3.1 do 4.0.
  4. Utwórz logikę przełączania się między tymi implementacjami w czasie działania i zacznij używać interfejsu w swojej grze.

Ogólne informacje o tym, jak abstrakcja może być używana do weryfikowania, czy aplikacje mogą działać w sposób zgodny wstecznie w różnych wersjach Androida, znajdziesz w artykule Tworzenie interfejsów zgodnych wstecznie.

Dodawanie interfejsu w celu zapewnienia zgodności wstecznej

Aby zapewnić zgodność wsteczną, możesz utworzyć interfejs niestandardowy, a następnie dodać implementacje specyficzne dla wersji. Jedną z zalet tego podejścia jest to, że umożliwia ono odzwierciedlenie interfejsów publicznych w Androidzie 4.1 (poziom API 16), które obsługują kontrolery do gier.

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

Interfejs InputManagerCompat udostępnia te metody:

getInputDevice()
LustragetInputDevice() Pobiera InputDeviceobiekt reprezentujący możliwości kontrolera do gier.
getInputDeviceIds()
LustragetInputDeviceIds() Zwraca tablicę liczb całkowitych, z których każda jest identyfikatorem innego urządzenia wejściowego. Jest to przydatne, jeśli tworzysz grę obsługującą wielu graczy i chcesz wykryć, ile kontrolerów jest podłączonych.
registerInputDeviceListener()
LustraregisterInputDeviceListener() Umożliwia zarejestrowanie się w celu otrzymywania informacji o dodaniu, zmianie lub usunięciu nowego urządzenia.
unregisterInputDeviceListener()
LustraunregisterInputDeviceListener() Wyrejestrowuje detektor zdarzeń urządzenia wejściowego.
onGenericMotionEvent()
LustraonGenericMotionEvent() Umożliwia grze przechwytywanie i obsługiwanie MotionEventobiektów i wartości osi, które reprezentują zdarzenia
, takie jak ruchy joysticka i naciśnięcia analogowych spustów.
onPause()
Przestaje odpytywać o zdarzenia kontrolera do gier, gdy główna aktywność jest wstrzymana lub gdy gra nie jest już aktywna.
onResume()
Rozpoczyna odpytywanie o zdarzenia kontrolera do gier po wznowieniu głównej aktywności lub po uruchomieniu gry i działaniu na pierwszym planie.
InputDeviceListener
Odzwierciedla interfejs InputManager.InputDeviceListener. Informuje grę o dodaniu, zmianie lub usunięciu kontrolera.

Następnie utwórz implementacje InputManagerCompat, które działają w różnych wersjach platformy. Jeśli gra działa na Androidzie 4.1 lub nowszym i wywołuje metodę InputManagerCompat, implementacja proxy wywołuje odpowiednią metodę w InputManager. Jeśli jednak gra działa na Androidzie w wersji od 3.1 do 4.0, niestandardowa implementacja przetwarza wywołania metod InputManagerCompat, korzystając tylko z interfejsów API wprowadzonych nie później niż w Androidzie 3.1. Niezależnie od tego, która implementacja specyficzna dla wersji jest używana w czasie działania, implementacja przekazuje wyniki wywołania z powrotem do gry w sposób przejrzysty.

Diagram klas interfejsu i implementacji specyficznych dla wersji.
Rysunek 1. Diagram klas interfejsu i implementacji specyficznych dla wersji.

Wdrażanie interfejsu na urządzeniach z Androidem w wersji 4.1 lub nowszej

InputManagerCompatV16 to implementacja interfejsu InputManagerCompat, która przekazuje wywołania metod do rzeczywistego elementu InputManagerInputManager.InputDeviceListener. InputManager jest pobierany z systemu 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
    }

}

Wdrażanie interfejsu na Androidzie w wersji od 3.1 do 4.0

Aby utworzyć implementację InputManagerCompat, która obsługuje Androida w wersji od 3.1 do 4.0, możesz użyć tych obiektów:

  • SparseArray identyfikatorów urządzeń, aby śledzić kontrolery do gier podłączone do urządzenia.
  • Handler do przetwarzania zdarzeń dotyczących urządzeń. Gdy aplikacja zostanie uruchomiona lub wznowiona, usługa Handler otrzyma komunikat o rozpoczęciu sprawdzania, czy kontroler do gier nie został odłączony. Handler rozpocznie pętlę, aby sprawdzić każdy znany podłączony kontroler do gier i czy zwracany jest identyfikator urządzenia. Wartość zwracana A null oznacza, że kontroler do gier jest odłączony. Handler przestaje wysyłać zapytania, gdy aplikacja jest wstrzymana.
  • Map obiektów:InputManagerCompat.InputDeviceListener Będziesz używać odbiorników do aktualizowania stanu połączenia śledzonych kontrolerów do gier.

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

Zaimplementuj obiekt PollingMessageHandler, który rozszerza klasę Handler, i zastąp metodę handleMessage(). Ta metoda sprawdza, czy podłączony kontroler do gier został odłączony, i powiadamia zarejestrowanych odbiorców.

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

Aby rozpocząć i zatrzymać sprawdzanie rozłączenia kontrolera do gier, zastąp te metody:

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

Aby wykryć dodanie urządzenia wejściowego, zastąp metodę onGenericMotionEvent(). Gdy system zgłosi zdarzenie ruchu, sprawdź, czy pochodzi ono z identyfikatora urządzenia, który jest już śledzony, czy z nowego identyfikatora urządzenia. Jeśli identyfikator urządzenia jest nowy, powiadom zarejestrowanych odbiorców.

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

Powiadamianie odbiorców jest realizowane za pomocą obiektu Handler, który wysyła obiekt DeviceEvent Runnable do kolejki wiadomości. Element DeviceEvent zawiera odwołanie do elementu InputManagerCompat.InputDeviceListener. Gdy działa DeviceEvent, wywoływana jest odpowiednia metoda zwrotna odbiornika, aby zasygnalizować, czy kontroler do gier został dodany, zmieniony czy usunięty.

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

Masz teraz 2 implementacje InputManagerCompat: jedną, która działa na urządzeniach z Androidem 4.1 i nowszym, oraz drugą, która działa na urządzeniach z Androidem od wersji 3.1 do 4.0.

Użyj implementacji specyficznej dla wersji

Logika przełączania specyficzna dla wersji jest zaimplementowana w klasie, która działa jako fabryka.

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

Teraz możesz utworzyć instancję obiektu InputManagerCompat i zarejestrować InputManagerCompat.InputDeviceListener w głównej funkcji View. Dzięki skonfigurowanej logice przełączania wersji gra automatycznie używa implementacji odpowiedniej dla wersji Androida, na której działa urządzenie.

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

Następnie zastąp metodę onGenericMotionEvent() w głównym widoku, jak opisano w artykule Obsługa zdarzenia MotionEvent z kontrolera do gier (w języku angielskim). Twoja gra powinna teraz być w stanie konsekwentnie przetwarzać zdarzenia kontrolera gier na urządzeniach z Androidem 3.1 (poziom interfejsu 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);
}

Pełną implementację tego kodu zgodności znajdziesz w klasie GameView w przykładowym ControllerSample.zip, który możesz pobrać.