Obsługa zmian danych wejściowych

Chromebooki oferują użytkownikom wiele różnych opcji wprowadzania danych: klawiaturę, mysz, trackpad, ekran dotykowy, rysik, MIDI oraz kontrolery do gier i kontrolery Bluetooth. Oznacza to, że to samo urządzenie może stać się stanowiskiem DJ-a, płótnem artysty lub ulubioną platformą gracza do strumieniowania gier AAA.

Jako deweloper możesz tworzyć wszechstronne i ciekawe aplikacje, które wykorzystują urządzenia wejściowe, jakie użytkownicy mają pod ręką – od podłączonej klawiatury po rysik i kontroler do gier Stadia. Wszystkie te możliwości wymagają jednak przemyślenia interfejsu, aby korzystanie z aplikacji było płynne i logiczne. Jest to szczególnie ważne, jeśli aplikacja lub gra została zaprojektowana z myślą o telefonach komórkowych. Jeśli na przykład w grze jest joystick sterowany dotykowo na ekranie telefonu, prawdopodobnie zechcesz go ukryć, gdy użytkownik gra za pomocą klawiatury.

Na tej stronie znajdziesz główne problemy, o których warto pamiętać, gdy myślisz o wielu źródłach danych wejściowych, oraz strategie ich rozwiązywania.

Odkrywanie przez użytkowników obsługiwanych metod wprowadzania

Idealnie byłoby, gdyby aplikacja płynnie reagowała na wszystkie dane wejściowe wybrane przez użytkownika. Często jest to proste i nie wymaga podawania użytkownikowi dodatkowych informacji. Przycisk powinien działać, gdy użytkownik kliknie go myszą, trackpadem, ekranem dotykowym, rysikiem itp. Nie musisz informować użytkownika, że może używać tych różnych urządzeń do aktywowania przycisku.

Są jednak sytuacje, w których metoda wprowadzania może poprawić wrażenia użytkownika, dlatego warto poinformować go, że Twoja aplikacja ją obsługuje. Oto kilka przykładów:

  • Aplikacja do odtwarzania multimediów może obsługiwać wiele przydatnych skrótów klawiszowych, których użytkownik nie jest w stanie łatwo odgadnąć.
  • Jeśli utworzysz aplikację dla DJ-ów, użytkownik może początkowo korzystać z ekranu dotykowego i nie zdawać sobie sprawy, że zezwalasz mu na używanie klawiatury lub trackpada w celu uzyskania dostępu do niektórych funkcji. Mogą też nie wiedzieć, że obsługujesz wiele kontrolerów DJ-skich MIDI. Zachęcenie ich do sprawdzenia obsługiwanego sprzętu może zapewnić im bardziej autentyczne wrażenia podczas miksowania.
  • Twoja gra może świetnie działać na ekranie dotykowym i przy użyciu klawiatury lub myszy, ale użytkownicy mogą nie wiedzieć, że obsługuje też wiele kontrolerów Bluetooth. Połączenie jednego z nich może zwiększyć satysfakcję i zaangażowanie użytkowników.

Możesz pomóc użytkownikom w odkrywaniu opcji wprowadzania danych, wyświetlając komunikaty w odpowiednim czasie. Implementacja będzie wyglądać inaczej w przypadku każdej aplikacji. Przykłady:

  • wyskakujące okienka z informacjami dla nowych użytkowników lub z poradami na dany dzień,
  • Opcje konfiguracji w panelach ustawień mogą pasywnie informować użytkowników o dostępności pomocy. Na przykład karta „Kontroler do gier” w panelu ustawień gry wskazuje, że kontrolery są obsługiwane.
  • Wiadomości kontekstowe. Jeśli na przykład wykryjesz klawiaturę fizyczną i zauważysz, że użytkownik klika działanie za pomocą myszy lub ekranu dotykowego, możesz wyświetlić przydatną wskazówkę z sugestią skrótu klawiszowego.
  • Listy skrótów klawiszowych. Gdy wykryta zostanie klawiatura fizyczna, wyświetlenie w interfejsie informacji o sposobie wyświetlania listy dostępnych skrótów klawiszowych ma podwójne zastosowanie: informuje o obsłudze klawiatury i ułatwia użytkownikom wyświetlanie i zapamiętywanie obsługiwanych skrótów.

Etykietowanie/układ interfejsu w przypadku odmiany danych wejściowych

W idealnej sytuacji interfejs wizualny nie powinien się zbytnio zmieniać, gdy używane jest inne urządzenie wejściowe. Wszystkie możliwe dane wejściowe powinny „po prostu działać”. Istnieją jednak ważne wyjątki. Dwa najważniejsze to interfejs użytkownika dostosowany do obsługi dotykowej i wyświetlane na ekranie podpowiedzi.

Interfejs dostosowany do obsługi dotykowej

Jeśli w aplikacji znajdują się elementy interfejsu użytkownika, które wymagają dotyku, np. joystick na ekranie w grze, zastanów się, jak będzie wyglądać korzystanie z aplikacji, gdy nie będzie można jej dotykać. W niektórych grach mobilnych znaczna część ekranu jest zajęta przez elementy sterujące, które są niezbędne do gry dotykowej, ale niepotrzebne, jeśli użytkownik gra za pomocą pada lub klawiatury. Aplikacja lub gra powinna zawierać logikę wykrywającą, która metoda wprowadzania jest aktywnie używana, i odpowiednio dostosowywać interfejs. Przykłady znajdziesz w sekcji Implementacja poniżej.

Interfejsy gier wyścigowych – jeden z elementami sterującymi na ekranie, a drugi z klawiaturą

Instrukcje wyświetlane na ekranie

Aplikacja może wyświetlać użytkownikom przydatne komunikaty na ekranie. Upewnij się, że nie zależą one od urządzenia wejściowego. Na przykład:

  • Przesuń, aby…
  • Dotknij gdziekolwiek, aby zamknąć
  • Ściągnij, aby powiększyć
  • Naciśnij „X”, aby…
  • Naciśnij i przytrzymaj, aby aktywować

Niektóre aplikacje mogą dostosowywać treść, aby nie zależała od sposobu wprowadzania danych. Jest to preferowane rozwiązanie, gdy ma to sens, ale w wielu przypadkach ważna jest szczegółowość i może być konieczne wyświetlanie różnych komunikatów w zależności od używanej metody wprowadzania, zwłaszcza w trybach samouczkowych, np. podczas pierwszego uruchomienia aplikacji.

Wskazówki dotyczące wielu języków

Jeśli Twoja aplikacja obsługuje wiele języków, musisz dobrze przemyśleć architekturę ciągów znaków. Jeśli na przykład obsługujesz 3 tryby wprowadzania i 5 języków, może to oznaczać 15 różnych wersji każdego komunikatu interfejsu. Zwiększy to nakład pracy potrzebny do dodania nowych funkcji i zwiększy prawdopodobieństwo wystąpienia błędów pisowni.

Jednym ze sposobów jest traktowanie działań interfejsu jako oddzielnego zestawu ciągów tekstowych. Jeśli na przykład zdefiniujesz działanie „zamknij” jako osobną zmienną tekstową z wariantami zależnymi od danych wejściowych, takimi jak „Kliknij dowolne miejsce, aby zamknąć”, „Naciśnij Esc, aby zamknąć”, „Kliknij dowolne miejsce, aby zamknąć”, „Naciśnij dowolny przycisk, aby zamknąć”, wszystkie komunikaty interfejsu, które mają informować użytkownika, jak coś zamknąć, mogą używać tej jednej zmiennej tekstowej „zamknij”. Gdy zmieni się metoda wprowadzania, po prostu zmień wartość tej zmiennej.

Wpisywanie z klawiatury ekranowej lub edytora IME

Pamiętaj, że jeśli użytkownik korzysta z aplikacji bez klawiatury fizycznej, tekst może być wprowadzany za pomocą klawiatury ekranowej. Sprawdź, czy po pojawieniu się klawiatury ekranowej niezbędne elementy interfejsu nie są zasłonięte. Więcej informacji znajdziesz w dokumentacji dotyczącej widoczności edytora IME na Androidzie.

Implementacja

W większości przypadków aplikacje i gry powinny prawidłowo reagować na wszystkie prawidłowe dane wejściowe, niezależnie od tego, co jest wyświetlane na ekranie. Jeśli użytkownik korzysta z ekranu dotykowego przez 10 minut, a potem nagle zaczyna używać klawiatury, wpisywanie z klawiatury powinno działać niezależnie od wizualnych podpowiedzi lub elementów sterujących na ekranie. Innymi słowy, funkcjonalność powinna mieć priorytet przed elementami wizualnymi lub tekstem.Dzięki temu aplikacja lub gra będzie użyteczna nawet wtedy, gdy logika wykrywania danych wejściowych będzie zawierać błąd lub wystąpi nieoczekiwana sytuacja.

Następnym krokiem jest wyświetlenie odpowiedniego interfejsu metody wprowadzania, która jest obecnie używana. Jak dokładnie to wykrywasz? Co się stanie, jeśli użytkownicy będą przełączać się między różnymi metodami wprowadzania tekstu podczas korzystania z Twojej aplikacji? Co zrobić, jeśli użytkownik korzysta z kilku metod jednocześnie?

Automat stanowy z określonym priorytetem na podstawie otrzymanych zdarzeń

Jednym ze sposobów jest śledzenie bieżącego „aktywnego stanu wprowadzania”, który reprezentuje wyświetlane obecnie na ekranie użytkownika prośby o wprowadzenie danych. W tym celu należy śledzić rzeczywiste zdarzenia wprowadzania odbierane przez aplikację i przechodzić między stanami za pomocą logiki priorytetowej.

Priorytet

Dlaczego warto ustalać priorytety stanów wejściowych? Użytkownicy wchodzą w interakcje z urządzeniami na różne sposoby, a Twoja aplikacja powinna obsługiwać ich wybory. Na przykład, jeśli użytkownik zdecyduje się używać jednocześnie ekranu dotykowego i myszy Bluetooth, powinno to być możliwe. Ale które wyświetlane na ekranie podpowiedzi i elementy sterujące powinny być widoczne? Mysz czy dotyk?

Zdefiniowanie każdego zestawu promptów jako „stanu wejściowego” i ustalenie ich priorytetów może pomóc w konsekwentnym podejmowaniu decyzji.

Stan jest określany przez odebrane zdarzenia wprowadzania danych

Dlaczego należy reagować tylko na otrzymane zdarzenia wejściowe? Możesz myśleć, że możesz śledzić połączenia Bluetooth, aby sprawdzić, czy kontroler Bluetooth jest podłączony, lub obserwować połączenia USB, aby sprawdzić, czy urządzenia USB są podłączone. Nie jest to zalecane z 2 głównych powodów.

Po pierwsze, informacje, które możesz odgadnąć na temat podłączonych urządzeń na podstawie zmiennych interfejsu API, nie są spójne, a liczba urządzeń Bluetooth, sprzętowych i rysików stale rośnie.

Drugi powód, dla którego warto używać odebranych zdarzeń zamiast stanu połączenia, jest taki, że użytkownicy mogą mieć podłączoną mysz, kontroler Bluetooth, kontroler MIDI itp., ale nie używać ich aktywnie do interakcji z aplikacją lub grą.

Odpowiadając na zdarzenia wejściowe, które zostały aktywnie odebrane przez aplikację, masz pewność, że reagujesz na rzeczywiste działania użytkowników w czasie rzeczywistym, a nie próbujesz odgadnąć ich intencji na podstawie niepełnych informacji.

Przykład: gra z obsługą dotyku, klawiatury i myszy oraz kontrolera

Wyobraź sobie, że stworzyliśmy grę wyścigową na telefony komórkowe z ekranem dotykowym. Gracze mogą przyspieszać, zwalniać, skręcać w prawo i w lewo oraz używać nitro, aby zwiększyć prędkość.

Obecny interfejs ekranu dotykowego składa się z joysticka na ekranie w lewym dolnym rogu, który służy do sterowania prędkością i kierunkiem, oraz przycisku w prawym dolnym rogu, który służy do włączania nitro. Użytkownik może zbierać na torze kanistry z nitro. Gdy pasek nitro u dołu ekranu jest pełny, nad przyciskiem pojawia się komunikat „Naciśnij, aby użyć nitro!”. Jeśli użytkownik gra po raz pierwszy lub przez jakiś czas nie wykonuje żadnych działań, nad joystickiem pojawia się tekst „samouczka”, który pokazuje, jak poruszać samochodem.

Chcesz dodać obsługę klawiatury i kontrolera Bluetooth. Od czego zacząć?

Wyścigi samochodowe z sterowaniem dotykowym

Stany wejściowe

Zacznij od określenia wszystkich stanów wejściowych, w których może działać Twoja gra, a następnie wymień wszystkie parametry, które chcesz zmienić w każdym stanie.

                                                                                                                                                                        
DotykKlawiatura/myszKontroler gier
       

Reaguje na

     
       

Wszystkie dane wejściowe

     
       

Wszystkie dane wejściowe

     
       

Wszystkie dane wejściowe

     
       

Elementy sterujące na ekranie

     
       

– Joystick ekranowy
– Przycisk nitro

     
       

– Brak joysticka
– Brak przycisku nitro

     
       

– Brak joysticka
– Brak przycisku nitro

     
       

Text

     
       

Kliknij, aby użyć Nitro.

     
       

Naciśnij „N”, aby przejść do Nitro.

     
       

Naciśnij „A”, aby użyć Nitro!

     
       

Samouczek

     
       

Obraz joysticka do sterowania prędkością i kierunkiem

     
       

Obraz klawiszy strzałek i WASD do zmiany szybkości i kierunku

     
       

Obraz gamepada do sterowania prędkością i kierunkiem

     

Śledź stan „aktywnego wprowadzania”, a następnie w razie potrzeby aktualizuj interfejs na podstawie tego stanu.

Uwaga: pamiętaj, że gra lub aplikacja powinna reagować na wszystkie metody wprowadzania danych, niezależnie od stanu. Jeśli na przykład użytkownik prowadzi samochód za pomocą ekranu dotykowego, ale naciśnie klawisz N na klawiaturze, powinna zostać wywołana akcja nitro.

Zmiany stanu z priorytetami

Niektórzy użytkownicy mogą jednocześnie korzystać z ekranu dotykowego i klawiatury. Niektórzy mogą zacząć korzystać z Twojej gry lub aplikacji na kanapie w trybie tabletu, a potem przejść do korzystania z klawiatury przy stole. Inni mogą podłączać lub odłączać kontrolery do gier w trakcie rozgrywki.

Najlepiej unikać nieprawidłowych elementów interfejsu, np. informowania użytkownika o konieczności naciśnięcia klawisza n, gdy korzysta on z ekranu dotykowego. Jednocześnie w przypadku użytkowników korzystających jednocześnie z kilku urządzeń wejściowych, takich jak ekran dotykowy i klawiatura, nie chcesz, aby interfejs użytkownika ciągle przełączał się między tymi dwoma stanami.

Jednym ze sposobów na rozwiązanie tego problemu jest ustalenie priorytetów typów danych wejściowych i wprowadzenie opóźnienia między zmianami stanu. W przypadku powyższej gry samochodowej zawsze warto zadbać o to, aby joystick na ekranie był widoczny za każdym razem, gdy odbierane są zdarzenia dotknięcia ekranu. W przeciwnym razie gra może wydawać się użytkownikowi bezużyteczna. Spowoduje to, że ekran dotykowy będzie urządzeniem o najwyższym priorytecie.

Jeśli zdarzenia klawiatury i ekranu dotykowego były odbierane jednocześnie, gra powinna pozostać w trybie ekranu dotykowego, ale nadal reagować na dane wejściowe z klawiatury. Jeśli po 5 sekundach nie zostanie odebrane żadne wejście z ekranu dotykowego, a zdarzenia klawiatury będą nadal odbierane, elementy sterujące na ekranie mogą zniknąć, a gra przejdzie w stan klawiatury.

Dane wejściowe z kontrolera do gier będą działać podobnie: interfejs kontrolera będzie miał niższy priorytet niż klawiatura, mysz i ekran dotykowy i będzie się pojawiać tylko wtedy, gdy będą odbierane dane wejściowe z kontrolera do gier, a nie z innych urządzeń. Gra zawsze będzie reagować na sygnały z kontrolera.

Poniżej znajdziesz diagram stanów i tabelę przejść dla tego przykładu. Dostosuj pomysł do swojej aplikacji lub gry.

Automat stanowy z nadawaniem priorytetów – ekran dotykowy, klawiatura/mysz, kontroler do gier

                                                                                                                                        
#1 Ekran dotykowy#2 Klawiatura#3 Pad do gier
       

Przenieś na pozycję 1

     
       

Nie dotyczy

     
       

– Odebrano dane wejściowe dotyku
– Natychmiastowe przejście do stanu danych wejściowych dotyku

     
       

– Odebrano dane wejściowe dotyku
– Natychmiastowe przejście do stanu danych wejściowych dotyku

     
       

Przejdź do kroku 2

     
       

- Brak dotyku przez 5 s
- Odebrano dane z klawiatury
- Przejście do stanu wprowadzania z klawiatury

     
       

Nie dotyczy

     
       

– Odebrano dane wejściowe z klawiatury
(natychmiastowe przejście do stanu wprowadzania z klawiatury)

     
       

Przejdź do kroku 3

     
       

- Brak dotyku przez 5 sekund
- Brak klawiatury przez 5 sekund
- Odebrano dane wejściowe z pada do gier
- Przejście do stanu danych wejściowych z pada do gier

     
       

- Brak klawiatury przez 5 sekund
- Odebrano dane wejściowe z pada do gier
- Przejście do stanu danych wejściowych z pada do gier

     
       

Nie dotyczy

     

Uwaga: zwróć uwagę, jak określanie priorytetów pomaga ustalić, który typ danych wejściowych powinien być dominujący. Stan wejścia natychmiast zyskuje wyższy priorytet:

3. Gamepad -> 2. Klawiatura -> 1. Dotyk

gdy tylko zostanie użyte urządzenie o wyższym priorytecie, ale powoli obniża się w hierarchii priorytetów dopiero po upływie okresu opóźnienia i tylko wtedy, gdy urządzenie o niższym priorytecie jest aktywnie używane.

Zdarzenia wprowadzania danych

Oto przykładowy kod pokazujący, jak wykrywać zdarzenia wejściowe z różnych rodzajów urządzeń wejściowych za pomocą standardowych interfejsów API Androida. Używaj tych zdarzeń do sterowania automatem stanów, tak jak powyżej. Ogólną koncepcję należy dostosować do typów oczekiwanych zdarzeń wejściowych oraz do aplikacji lub gry.

Przyciski klawiatury i kontrolera

// Drive the state machine based on the received input type
// onKeyDown drives the state machine, but does not trigger game actions
// Both keyboard and game controller events come through as key events
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    if (event != null) {
        // Check input source
        val outputMessage = when (event.source) {
            SOURCE_KEYBOARD -> {
                MyStateMachine.KeyboardEventReceived()
                "Keyboard event"
            }
            SOURCE_GAMEPAD -> {
                MyStateMachine.ControllerEventReceived()
                "Game controller event"
            }
            else -> "Other key event: ${event.source}"
        }
        // Do something based on source type
        findViewById(R.id.text_message).text = outputMessage
    }
    // Pass event up to system
    return super.onKeyDown(keyCode, event)
}
// Trigger game events based on key release
// Both keyboard and game controller events come through as key events
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
   when(keyCode) {
       KeyEvent.KEYCODE_N -> {
           MyStateMachine.keyboardEventReceived()
           engageNitro()
           return true // event handled here, return true
       }
   }
   // If event not handled, pass up to system
   return super.onKeyUp(keyCode, event)
}

Uwaga: w przypadku zdarzeń kluczowych możesz użyć onKeyDown() lub onKeyUp(). W tym przypadku symbol onKeyDown() służy do sterowania automatem stanów, a symbol onKeyUp() – do wywoływania zdarzeń w grze.

Jeśli użytkownik naciśnie i przytrzyma przycisk, zdarzenie onKeyUp() zostanie wywołane tylko raz na naciśnięcie klawisza, a zdarzenie onKeyDown() zostanie wywołane wielokrotnie. Jeśli chcesz reagować na naciśnięcie, obsługuj zdarzenia w grze w funkcji onKeyDown() i wdrażaj logikę, która będzie uwzględniać powtarzające się zdarzenia. Więcej informacji znajdziesz w dokumentacji Obsługa działań klawiatury.

Dotyk i rysik

// Touch and stylus events come through as touch events
override fun onTouchEvent(event: MotionEvent?): Boolean {
   if (event != null) {
       // Get tool type
       val pointerIndex = event.action and ACTION_POINTER_INDEX_MASK shr ACTION_POINTER_INDEX_SHIFT
       val toolType = event.getToolType(pointerIndex)

       // Check tool type
       val outputMessage = when (toolType) {
           TOOL_TYPE_FINGER -> {
               MyStateMachine.TouchEventReceived()
               "Touch event"
           }
           TOOL_TYPE_STYLUS -> {
                MyStateMachine.StylusEventReceived()
               "Stylus event"
           }
           else -> "Other touch event: ${toolType}"
       }

       // Do something based on tool type, return true if event handled
       findViewById(R.id.text_message).text = outputMessage
   }
   // If event not handled, pass up to system
   return super.onGenericMotionEvent(event)
}

Mysz i joystick

// Mouse and joystick events come through as generic events
override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
   if (event != null) {
       // Check input source
       val outputMessage = when (event.source) {
           SOURCE_JOYSTICK -> {
                MyStateMachine.ControllerEventReceived()
               "Controller event"
           }
           SOURCE_MOUSE -> {
                MyStateMachine.MouseEventReceived()
               "Mouse event"
           }
           else -> "Other generic event: ${event.source}"
       }
       // Do something based on source type, return true if event handled
       findViewById(R.id.text_message).text = outputMessage
   }
   // If event not handled, pass up to system
   return super.onGenericMotionEvent(event)
}