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

Jeśli Twoja gra obsługuje kontrolery do gier, ponosisz odpowiedzialność aby gra spójnie reaguje na kontrolery na różnych urządzeniach na różnych wersjach Androida. Dzięki temu gra może dotrzeć do szerszego grona odbiorców a gracze mogą cieszyć się płynną rozgrywką dzięki nawet w przypadku zmiany urządzenia z Androidem na nowszą wersję.

Z tej lekcji dowiesz się, jak korzystać z interfejsów API dostępnych w Androidzie 4.1 i nowszych w sposób zgodny wstecznie, dzięki czemu gra będzie obsługiwać te funkcje na urządzeniach z Androidem 3.1 lub nowszym:

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

Przykłady w tej lekcji wykorzystują referencyjną implementację podane w próbce ControllerSample.zip dostępnej do pobrania powyżej. Ten przykład pokazuje, jak wdrożyć InputManagerCompat obsługę różnych wersji Androida. Aby skompilować przykład, musi używać Androida 4.1 (poziom interfejsu API 16) lub nowszego. Po skompilowaniu aplikacja przykładowa działa na każdym urządzeniu z Androidem 3.1 (poziom interfejsu API 12) lub nowszym. cel.

Przygotowanie do abstrakcyjnych interfejsów API do obsługi kontrolera gier

Załóżmy, że chcesz móc określić, czy połączenie kontrolera do gier zmienił się stan urządzeń z Androidem 3.1 (poziom interfejsu API 12). Pamiętaj jednak: interfejsy API są dostępne tylko w Androidzie 4.1 (poziom API 16) i nowszych, muszą zapewnić implementację, która obsługuje Androida 4.1 lub nowszego, udostępniając mechanizm kreacji zastępczych, który obsługuje Androida od 3.1 do 4.0.

Pomaga określić, które funkcje wymagają takiego mechanizmu awaryjnego starszych wersji w tabeli 1 przedstawiliśmy różnice w obsłudze kontrolerów gier. między Androidem 3.1 (poziom interfejsu API 12) a 4.1 (poziom interfejsu API) 16)

Tabela 1. Interfejsy API do obsługi kontrolera gier w różnych wersji Androida.

Informacje o administratorze Interfejs API kontrolera Poziom API 12 Poziom interfejsu API 16
Identyfikacja urządzenia getInputDeviceIds()  
getInputDevice()  
getVibrator()  
SOURCE_JOYSTICK
SOURCE_GAMEPAD
Stan połączenia onInputDeviceAdded()  
onInputDeviceChanged()  
onInputDeviceRemoved()  
Identyfikacja zdarzenia wejściowego Naciśnij pad kierunkowy ( KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_CENTER).
Naciśnięcie przycisku na padzie do gier ( BUTTON_A, BUTTON_B, BUTTON_THUMBL, BUTTON_THUMBR, BUTTON_SELECT, BUTTON_START, BUTTON_R1 BUTTON_L1, BUTTON_R2, BUTTON_L2)
Ruch przełącznikiem joysticka i kapelusza ( AXIS_X, AXIS_Y, AXIS_Z, AXIS_RZ, AXIS_HAT_X, AXIS_HAT_Y)
Naciśnięcie aktywatora analogowego ( AXIS_LTRIGGER, AXIS_RTRIGGER).

Możesz wykorzystać abstrakcję, 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 Java, który abstrakcyjna będzie implementacja funkcje kontrolera do gier wymagane przez grę.
  2. Utwórz implementację proxy interfejsu korzystającego z interfejsów API w Androidzie. w wersji 4.1 i nowszych.
  3. Tworzenie niestandardowej implementacji interfejsu wykorzystującej dostępne interfejsy API od Androida 3.1 do 4.0.
  4. Utwórz logikę przełączania między tymi implementacjami w czasie działania, i zacznij korzystać z interfejsu w grze.

Omówienie sposobów wykorzystania abstrakcji do zapewnienia, że aplikacje mogą działać w sposób zgodny wstecznie na różnych wersjach Androida, patrz Tworzenie Interfejsy API zgodne wstecz

Dodaj interfejs, aby zapewnić zgodność wsteczną

Aby zapewnić zgodność wsteczną, możesz utworzyć niestandardowy interfejs, a następnie i dodawać implementacje w określonej wersji. Jedną z zalet tego podejścia jest to, pozwala powielać publiczne interfejsy Androida 4.1 (poziom API 16), 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()
Odzwierciedla getInputDevice(). Pobiera InputDevice reprezentujący możliwości kontrolera gry.
getInputDeviceIds()
Odzwierciedla getInputDeviceIds(). Zwraca tablicę liczb całkowitych, z których każda który jest identyfikatorem innego urządzenia wejściowego. Jest to przydatne, jeśli tworzysz To gra dla wielu graczy i chcesz określić, ilu są podłączone.
registerInputDeviceListener()
Odzwierciedla registerInputDeviceListener(). Pozwala zarejestrować się, aby otrzymywać informacje o nowych po dodaniu, zmianie lub usunięciu urządzenia.
unregisterInputDeviceListener()
Odzwierciedla unregisterInputDeviceListener(). Wyrejestrowuje detektor urządzenia wejściowego.
onGenericMotionEvent()
Odzwierciedla onGenericMotionEvent(). Przechwytywanie i obsługa gry MotionEvent obiektów i wartości osi, które reprezentują zdarzenia takie jak ruchy joysticka i naciśnięcia spustu analogowego.
onPause()
Przestaje odpytywać zdarzenia kontrolera gier, gdy główna aktywność jest wstrzymana lub gra nie jest już skupiona.
onResume()
Rozpoczyna odpytywanie zdarzeń kontrolera gier, gdy główna aktywność zostanie wznowiona lub po rozpoczęciu gry na pierwszym planie.
InputDeviceListener
Odzwierciedla InputManager.InputDeviceListener za pomocą prostego interfejsu online. Informuje grę, gdy kontroler został dodany, zmieniony lub usunięto.

Następnie utwórz działające implementacje interfejsu InputManagerCompat na różnych wersjach platformy. Jeśli gra działa na Androidzie 4.1 lub i wywołuje metodę InputManagerCompat, implementację serwera proxy wywołuje równoważną metodę w InputManager. Jeśli jednak gra działa na Androidzie od 3.1 do 4.0, niestandardowa implementacja przetwarza wywołania metod InputManagerCompat za pomocą tylko interfejsy API wprowadzone nie później niż do Androida 3.1. Bez względu na to jest używana w czasie wykonywania, implementacja musi a wyniki rozmowy w przejrzysty sposób zostaną zwrócone do gry.

Rysunek 1. Diagram klasy interfejsu w zależności od wersji implementacji.

Implementacja interfejsu w Androidzie 4.1 i nowszych

InputManagerCompatV16 to implementacja InputManagerCompat, który pośredniczy w wywołaniach metody wartości rzeczywiste InputManager i InputManager.InputDeviceListener. Wartość InputManager jest pobierana 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
    }

}

Implementacja interfejsu w systemach Android od 3.1 do 4.0

Aby utworzyć implementację InputManagerCompat, która obsługuje Androida od 3.1 do 4.0, możesz użyć następujące obiekty:

  • SparseArray identyfikatorów urządzeń do śledzenia kontrolerów gier podłączonych do urządzenia.
  • Handler do przetwarzania zdarzeń dotyczących urządzenia. Po uruchomieniu aplikacji lub wznowione, Handler otrzymuje wiadomość z prośbą o rozpoczęcie odpytywania . Handler rozpocznie w pętli, aby sprawdzić każdy znany podłączony kontroler do gier i sprawdzić, czy identyfikator urządzenia . Zwracana wartość null oznacza, że kontroler do gier jest – rozłączono. Handler przestaje odpytywać, gdy aplikacja jest wstrzymane.
  • Map z InputManagerCompat.InputDeviceListener obiektów. Będziesz używać detektorów do aktualizowania stanu połączenia śledzonego kontrolerach 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 rozciąga się Handler i zastąp handleMessage() . Ta metoda sprawdza, czy podłączony kontroler do gier został jest rozłączony i wysyła powiadomienia do zarejestrowanych detektoró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ć odpytywanie o odłączenie kontrolera gry, zastąp ustawienie 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ć, że dodano urządzenie wejściowe, zastąp Metoda onGenericMotionEvent(). Gdy system zgłosi zdarzenie ruchu, Sprawdź, czy zdarzenie pochodzi z identyfikatora urządzenia, które jest już śledzone nowego identyfikatora urządzenia. Jeśli identyfikator urządzenia jest nowy, powiadom zarejestrowanych słuchaczy.

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

Powiadomienia detektorów są implementowane za pomocą polecenia Handler obiekt do wysłania DeviceEvent Runnable obiekt do kolejki wiadomości. DeviceEvent zawiera odwołanie do elementu InputManagerCompat.InputDeviceListener. Kiedy gdy uruchomi się DeviceEvent, odpowiednia metoda wywołania zwrotnego detektora jest wywoływana, 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 kodu InputManagerCompat: jedną, która działa na urządzeniach z Androidem 4.1 i nowszym oraz na innych który działa na urządzeniach z Androidem w wersji od 3.1 do 4.0.

Użyj implementacji w zależności od wersji

Logika przełączania określonej 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 po prostu utworzyć instancję obiektu InputManagerCompat zarejestruj InputManagerCompat.InputDeviceListener w swojej głównej View Ze względu na ustawiony przez Ciebie mechanizm przełączania wersji gra automatycznie stosuje implementację, która pasuje do wersji Androida zainstalowanej na urządzeniu.

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 onGenericMotionEvent() w widoku głównym, tak jak to opisano w Obsługa zdarzenia MotionEvent z gry Kontroler. Twoja gra powinna teraz przetwarzać zdarzenia kontrolera na urządzeniach z Androidem 3.1 (poziom interfejsu API 12) lub nowszym.

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 GameView klasa podana w przykładzie ControllerSample.zip dostępne do pobrania powyżej.