Omówienie zdarzeń wejściowych

Wypróbuj sposób tworzenia wiadomości
Jetpack Compose to zalecany zestaw narzędzi UI na Androida. Dowiedz się, jak w funkcji tworzenia wiadomości używać dotyku i wprowadzania tekstu.

Na Androidzie istnieje kilka sposobów przechwytywania zdarzeń związanych z interakcjami użytkownika z aplikacją. Uwzględnienie zdarzeń występujących w interfejsie polega na przechwytywaniu zdarzeń z konkretnego obiektu View, z którym użytkownik wchodzi w interakcję. Klasa View umożliwia wykonanie tego zadania.

W ramach różnych klas widoku danych, których użyjesz do utworzenia układu, możesz zauważyć kilka publicznych metod wywołań zwrotnych, które wyglądają dobrze w przypadku zdarzeń interfejsu. Te metody są wywoływane przez platformę Androida, gdy odpowiednie działanie występuje na obiekcie. Na przykład po dotknięciu widoku (takiego jak przycisk) metoda onTouchEvent() jest wywoływana dla tego obiektu. Aby jednak to przechwycić, musisz rozszerzyć klasę i zastąpić metodę. Rozszerzanie każdego obiektu View w celu obsługi takiego zdarzenia nie byłoby jednak zbyt praktyczne. Właśnie dlatego klasa View zawiera też zbiór zagnieżdżonych interfejsów z wywołaniami zwrotnymi, które można znacznie łatwiej zdefiniować. Te interfejsy, nazywane detektorami zdarzeń, umożliwiają rejestrowanie interakcji użytkowników z interfejsem.

Chociaż detektory zdarzeń są częściej używane do nasłuchiwania interakcji użytkownika, może się zdarzyć, że zechcesz rozszerzyć klasę View w celu utworzenia komponentu niestandardowego. Może chcesz rozszerzyć zajęcia Button, aby nadać im bardziej fantazyjny charakter. W takim przypadku możesz zdefiniować domyślne zachowania klasy przy użyciu klasy modułów obsługi zdarzeń.

Detektory zdarzeń

Detektor zdarzeń to interfejs klasy View, który zawiera pojedynczą metodę wywołania zwrotnego. Te metody będą wywoływane przez platformę Androida, gdy widok, w którym został zarejestrowany detektor, jest wywoływany przez interakcję użytkownika z elementem w interfejsie.

Interfejsy detektora zdarzeń obejmują te metody wywołań zwrotnych:

onClick()
Od View.OnClickListener. Ta funkcja jest wywoływana, gdy użytkownik dotknie elementu (w trybie dotykowym) albo wybierze go przy użyciu klawiszy nawigacyjnych lub kulki i naciśnie odpowiedni klawisz Enter lub naciska kulkę.
onLongClick()
Od View.OnLongClickListener. Ta czynność jest wywoływana, gdy użytkownik dotknie i przytrzyma element (w trybie dotykowym) albo wybierze go przy użyciu klawiszy nawigacyjnych lub kulki i naciśnie i przytrzymasz odpowiedni klawisz Enter lub naciśnie i przytrzyma kulkę (przez sekundę).
onFocusChange()
Od View.OnFocusChangeListener. Jest ona wywoływana, gdy użytkownik nawiguje do elementu lub z niego wychodzi, za pomocą klawiszy nawigacyjnych lub kulki.
onKey()
Od View.OnKeyListener. Ta funkcja jest wywoływana, gdy użytkownik skupia się na produkcie i naciska lub zwalnia klawisz sprzętowy na urządzeniu.
onTouch()
Od View.OnTouchListener. Wywoływana, gdy użytkownik wykona działanie klasyfikowane jako zdarzenie dotyku, na przykład naciśnienie, zwolnienie lub dowolny gest na ekranie (w granicach elementu).
onCreateContextMenu()
Od View.OnCreateContextMenuListener. Ta funkcja jest wywoływana podczas tworzenia menu kontekstowego (w wyniku długotrwałego „długiego kliknięcia”). Zapoznaj się z opisem menu kontekstowego w przewodniku dla programistów Menu.

Korzystanie z tych metod jest jedynym elementem ich interfejsu. Aby zdefiniować jedną z tych metod i obsługiwać zdarzenia, zaimplementuj zagnieżdżony interfejs w Aktywności lub zdefiniuj ją jako klasę anonimową. Następnie przekaż instancję implementacji do odpowiedniej metody View.set...Listener(). (np. wywołaj setOnClickListener() i przekaż mu swoją implementację OnClickListener).

Przykład poniżej pokazuje, jak zarejestrować detektor kliknięcia przycisku.

Kotlin

protected void onCreate(savedValues: Bundle) {
    ...
    val button: Button = findViewById(R.id.corky)
    // Register the onClick listener with the implementation above
    button.setOnClickListener { view ->
        // do something when the button is clicked
    }
    ...
}

Java

// Create an anonymous implementation of OnClickListener
private OnClickListener corkyListener = new OnClickListener() {
    public void onClick(View v) {
      // do something when the button is clicked
    }
};

protected void onCreate(Bundle savedValues) {
    ...
    // Capture our button from layout
    Button button = (Button)findViewById(R.id.corky);
    // Register the onClick listener with the implementation above
    button.setOnClickListener(corkyListener);
    ...
}

Być może łatwiej będzie zaimplementować OnClickListener w ramach aktywności. Pozwoli to uniknąć dodatkowego obciążenia klas i przydziału obiektów. Na przykład:

Kotlin

class ExampleActivity : Activity(), OnClickListener {
  
    protected fun onCreate(savedValues: Bundle) {
        val button: Button = findViewById(R.id.corky)
        button.setOnClickListener(this)
    }

    // Implement the OnClickListener callback
    fun onClick(v: View) {
        // do something when the button is clicked
    }
}

Java

public class ExampleActivity extends Activity implements OnClickListener {
    protected void onCreate(Bundle savedValues) {
        ...
        Button button = (Button)findViewById(R.id.corky);
        button.setOnClickListener(this);
    }

    // Implement the OnClickListener callback
    public void onClick(View v) {
      // do something when the button is clicked
    }
    ...
}

Zwróć uwagę, że wywołanie zwrotne onClick() w powyższym przykładzie nie ma zwracanej wartości, ale niektóre inne metody detektora zdarzeń muszą zwracać wartość logiczną. Powód zależy od zdarzenia. Oto kilka powodów, dla których:

  • onLongClick() – zwraca wartość logiczną wskazującą, czy zdarzenie zostało wykorzystane i nie powinno być dalej przenoszone. Oznacza to, że zwróć wartość true, aby wskazać, że zdarzenie zostało przez Ciebie obsługiwane i powinno się tu skończyć. Zwróć false, jeśli zdarzenie nie zostało przez Ciebie obsługiwane i/lub zdarzenie powinno być kontynuowane przez wszystkie inne detektory kliknięć.
  • onKey() – zwraca wartość logiczną wskazującą, czy zdarzenie zostało wykorzystane i nie powinno być dalej przenoszone. Oznacza to, że zwróć wartość true, aby wskazać, że zdarzenie zostało wykonane i powinno się tu zatrzymać. Zwróć wartość false, jeśli zdarzenie nie zostało przez Ciebie obsługiwane lub powinno ono być kontynuowane przez wszystkie inne detektory powiązane z kluczem.
  • onTouch() – zwraca wartość logiczną wskazującą, czy detektor wykorzystuje to zdarzenie. Ważne jest, że to zdarzenie może zawierać wiele działań, które następują po sobie. Jeśli więc zwracasz wartość false po otrzymaniu zdarzenia działania w dół, oznacza to, że nie korzystasz ze zdarzenia i nie interesują Cię jego kolejne działania. Oznacza to, że użytkownik nie będzie wywoływany w przypadku żadnych innych działań, takich jak gest palcem czy ostateczny wynik działania.

Pamiętaj, że kluczowe zdarzenia sprzętowe są zawsze wyświetlane w aktualnie widocznym widoku. Są one wysyłane od góry w hierarchii widoku i w dół aż do odpowiedniego miejsca docelowego. Jeśli widok (lub element podrzędny Twojego widoku) jest obecnie zaznaczony, możesz zobaczyć przebieg zdarzenia za pomocą metody dispatchKeyEvent(). Zamiast rejestrować kluczowe zdarzenia w widoku danych możesz też otrzymywać wszystkie zdarzenia w aktywności za pomocą narzędzi onKeyDown() i onKeyUp().

Jeśli chodzi o wprowadzanie tekstu w aplikacji, pamiętaj, że wiele urządzeń ma tylko programowe metody wprowadzania. Te metody nie muszą być oparte na klawiszu. Niektóre mogą korzystać z rozpoznawania mowy, pisma odręcznego itd. Nawet jeśli metoda wprowadzania ma interfejs podobny do klawiatury, zasadniczo nie wywoła rodziny zdarzeń onKeyDown(). Nigdy nie twórz interfejsu użytkownika, który wymaga sterowania naciśnięciami określonych klawiszy, chyba że chcesz ograniczyć aplikację do urządzeń z klawiaturą sprzętową. W szczególności nie korzystaj z tych metod do weryfikowania danych wejściowych, gdy użytkownik naciśnie klawisz Return. Zamiast tego używaj działań takich jak IME_ACTION_DONE, aby zasygnalizować metodę wejściową oczekiwanej reakcji aplikacji. Może to w znaczący sposób zmienić interfejs. Unikaj przekonywania, jak powinna działać programowa metoda wprowadzania danych, i upewnij się, że dostarcza ona do Twojej aplikacji sformatowany tekst.

Uwaga: Android najpierw wywoła moduły obsługi zdarzeń, a potem odpowiednie domyślne moduły obsługi z definicji klasy. Dlatego zwrócenie przez te detektory zdarzeń wartości true spowoduje zatrzymanie propagacji zdarzenia do innych detektorów i zablokuje wywołanie zwrotne do domyślnego modułu obsługi zdarzeń w widoku danych. Upewnij się więc, że chcesz zakończyć wydarzenie, kiedy zwrócisz wartość true.

Moduły obsługi zdarzeń

Jeśli tworzysz komponent niestandardowy z poziomu widoku danych, możesz zdefiniować kilka metod wywołań zwrotnych, które są używane jako domyślne moduły obsługi zdarzeń. W dokumencie poświęconym komponentom widoku niestandardowego poznasz niektóre typowe wywołania zwrotne do obsługi zdarzeń, m.in.:

Istnieją też inne metody, które nie należą do klasy View, ale mogą bezpośrednio wpływać na sposób obsługi zdarzeń. Przy zarządzaniu bardziej złożonymi zdarzeniami w układzie możesz więc użyć innych metod:

Tryb dotykowy

Kiedy użytkownik porusza się po interfejsie za pomocą klawiszy kierunkowych lub kulki, trzeba zaznaczyć elementy, które można wykonać (np. przyciski), aby mógł zobaczyć, co przyjmie. Jeśli jednak urządzenie obsługuje funkcje dotykowe, a użytkownik rozpocznie interakcję z interfejsem, klikając go, nie ma już potrzeby wyróżniania elementów ani wskazywania konkretnego widoku. Istnieje więc tryb interakcji o nazwie „tryb dotykowy”.

W przypadku urządzenia z funkcją dotykową, gdy użytkownik dotknie ekranu, urządzenie przejdzie w tryb dotykowy. Od tej pory możliwe będzie zaznaczenie tylko widoków, w których przypadku isFocusableInTouchMode() ma wartość Prawda, tak jak widżety edycji tekstu. Inne widoki, które są dotykowe, takie jak przyciski, nie zapalą ostrości po dotknięciu. Po prostu uruchamiają one detektory kliknięć, które są naciśnięte.

Za każdym razem, gdy użytkownik naciśnie klawisz kierunkowy lub przewinie ekran przy użyciu kulki, urządzenie wyjdzie z trybu dotykowego i znajdzie widok, na którym trzeba się skupić. Użytkownik może teraz wznowić korzystanie z interfejsu bez dotykania ekranu.

Stan trybu dotykowego jest utrzymywany w całym systemie (we wszystkich oknach i czynnościach). Aby zapytać o bieżący stan, wywołaj isInTouchMode(), by sprawdzić, czy urządzenie jest obecnie w trybie dotykowym.

Koncentracja na radzeniu sobie z problemem

Platforma obsługuje rutynowe ruchy skupione w odpowiedzi na dane wejściowe użytkownika. Obejmuje to zmienianie zaznaczenia podczas usuwania lub ukrywania widoków albo po udostępnieniu nowych widoków. Wyświetlenia wskazują, że chcą się skupić za pomocą metody isFocusable(). Aby zmienić ustawienie ostrości w widoku, wywołaj setFocusable(). W trybie dotykowym możesz zapytać, czy widok umożliwia zaznaczanie przy użyciu klawisza isFocusableInTouchMode(). Możesz to zmienić w setFocusableInTouchMode().

Na urządzeniach z Androidem 9 (poziom interfejsu API 28) lub nowszym działania nie powodują początkowego fokusu. Zamiast tego w razie potrzeby musisz wyraźnie poprosić o początkowe zaznaczenie.

Ruch skupienia opiera się na algorytmie, który znajduje najbliższego sąsiada w określonym kierunku. W rzadkich przypadkach domyślny algorytm może nie pasować do zamierzonego działania dewelopera. W takich sytuacjach w pliku układu możesz podać jawne zastąpienia z tymi atrybutami XML: nextFocusDown, nextFocusLeft, nextFocusRight i nextFocusUp. Dodaj jeden z tych atrybutów do widoku, z którego aktualnie przechodzisz. Określ wartość atrybutu jako identyfikator widoku do, który należy zaznaczyć. Na przykład:

<LinearLayout
    android:orientation="vertical"
    ... >
  <Button android:id="@+id/top"
          android:nextFocusUp="@+id/bottom"
          ... />
  <Button android:id="@+id/bottom"
          android:nextFocusDown="@+id/top"
          ... />
</LinearLayout>

Normalnie w układzie pionowym przejście z pierwszego przycisku w górę nie spowodowałoby przejścia z drugiego przycisku ani przejścia z drugiego przycisku. Przycisk na górze ma już wartość nextFocusUp (i na odwrót), więc fokus nawigacji będzie się zmieniać od góry do dołu i od dołu do góry.

Jeśli chcesz zadeklarować widok jako możliwy do zaznaczenia w swoim interfejsie (gdy zwykle nie jest on dostępny), dodaj atrybut XML android:focusable do widoku w deklaracji układu. Ustaw wartość true. Możesz też zadeklarować widok jako z możliwością zaznaczenia w trybie dotykowym za pomocą funkcji android:focusableInTouchMode.

Aby poprosić o konkretny widok danych, zadzwoń pod numer requestFocus().

Aby wykrywać zdarzenia skupienia (otrzymywać powiadomienia, gdy widok danych otrzymuje lub traci fokus), użyj metody onFocusChange(). Jak omówiono w sekcji Detektory zdarzeń.