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.
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ąć?
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.
| Dotyk | Klawiatura/mysz | Kontroler gier | |
|---|---|---|---|
|
Reaguje na |
Wszystkie dane wejściowe |
Wszystkie dane wejściowe |
Wszystkie dane wejściowe |
|
Elementy sterujące na ekranie |
– Joystick ekranowy |
– Brak joysticka |
– Brak joysticka |
|
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.
| #1 Ekran dotykowy | #2 Klawiatura | #3 Pad do gier | |
|---|---|---|---|
|
Przenieś na pozycję 1 |
Nie dotyczy |
– Odebrano dane wejściowe dotyku |
– Odebrano dane wejściowe dotyku |
|
Przejdź do kroku 2 |
- Brak dotyku przez 5 s |
Nie dotyczy |
– Odebrano dane wejściowe z klawiatury |
|
Przejdź do kroku 3 |
- Brak dotyku przez 5 sekund |
- Brak klawiatury przez 5 sekund |
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) }