Warto znać kilka terminów i koncepcji podczas pracy nad obsługą gestów w aplikacji. Na tej stronie objaśniamy warunki wskaźników, zdarzeń wskaźnika i gestów oraz wprowadza różne abstrakcje poziomy gestów. Szczegółowo omawia też wykorzystywanie zdarzeń propagacja.
Definicje
Aby zrozumieć różne pojęcia występujące na tej stronie, musisz poznać pewne używanej terminologii:
- Wskaźnik: fizyczny obiekt, którego możesz używać do interakcji z aplikacją.
Na urządzeniach mobilnych wskaźnikiem są najczęściej
ekranu dotykowego. Możesz też użyć rysika, aby zastąpić palec.
W przypadku dużych ekranów do pośredniej interakcji możesz używać myszy lub trackpada.
wyświetlacz. Urządzenie wejściowe musi być w stanie wskazać na koordynacji
jest uważany za wskaźnik, dlatego klawiatury nie można traktować jako
wskaźnik. W przypadku tworzenia wiadomości typ wskaźnika jest uwzględniany przy zmianach wskaźników za pomocą tagów
PointerType
- Zdarzenie wskaźnika: opisuje interakcję niskiego poziomu polegającą na użyciu co najmniej 1 wskaźnika.
z aplikacją w danym momencie. Każda interakcja ze wskaźnikiem, np. umieszczenie
palcem na ekranie lub przeciągnięcia myszą, wywoła zdarzenie. W
wszystkie istotne informacje o takim zdarzeniu są zawarte w
PointerEvent
. - Gest: sekwencja zdarzeń wskaźnika, którą można zinterpretować jako pojedynczy działania. Na przykład gest dotknięcia może być traktowany jako sekwencja kliknięcia w dół, po którym następuje zdarzenie dodatkowe. Wiele gestów używa wielu gestów, np. do klikania, przeciągania i przekształcania, ale możesz też tworzyć własne w razie potrzeby gestu.
Różne poziomy abstrakcji
Jetpack Compose umożliwia obsługę gestów na różnych poziomach abstrakcji.
Najwyższy poziom to obsługa komponentów. Kompozycje, takie jak Button
automatycznie uwzględniać obsługę gestów. Aby dodać obsługę gestów do ustawień niestandardowych
komponentów, możesz dodawać modyfikatory gestów, takie jak clickable
, w dowolnym
elementów kompozycyjnych. Jeśli potrzebujesz gestu niestandardowego, możesz użyć
pointerInput
.
Bazując na najwyższym poziomie abstrakcji, który zapewnia
potrzebne funkcje. Dzięki temu możesz korzystać z dodanych sprawdzonych metod
w warstwie. Na przykład pole Button
zawiera więcej informacji semantycznych, które są używane do
ułatwień dostępu niż clickable
, który zawiera więcej informacji niż
Implementacja pointerInput
.
Obsługa komponentów
Wiele gotowych komponentów w funkcji tworzenia wiadomości zawiera swego rodzaju gest wewnętrzny
z obsługą klienta. Na przykład LazyColumn
reaguje na gesty przeciągania przez:
podczas przewijania strony, wyświetlana jest ikona Button
, która po naciśnięciu
a komponent SwipeToDismiss
zawiera funkcje logiczne przesuwania w celu zamknięcia
. Ten rodzaj obsługi gestami działa automatycznie.
Oprócz wewnętrznej obsługi gestów, wiele elementów wymaga od rozmówcy
i wykonać gest. Na przykład Button
automatycznie wykrywa kliknięcia.
i wywołuje zdarzenie kliknięcia. Podajesz parametr lambda onClick
do: Button
,
zareaguje na gest. I w podobny sposób dodajesz funkcję lambda onValueChange
do:
Slider
zareaguje na przeciąganie uchwytu suwaka przez użytkownika.
Jeśli ma to zastosowanie w Twoim przypadku, preferuj gesty zawarte w komponentach, ponieważ
zawierają gotowe funkcje dotyczące skupienia i ułatwień dostępu.
były dobrze przetestowane. Na przykład pole Button
jest oznaczone w specjalny sposób,
usługi ułatwień dostępu prawidłowo opisują go jako przycisk, a nie zwykły przycisk
klikalny element:
// Talkback: "Click me!, Button, double tap to activate" Button(onClick = { /* TODO */ }) { Text("Click me!") } // Talkback: "Click me!, double tap to activate" Box(Modifier.clickable { /* TODO */ }) { Text("Click me!") }
Więcej informacji o ułatwieniach dostępu w funkcji Compose znajdziesz w sekcji Ułatwienia dostępu w Utwórz.
Dodawanie określonych gestów do dowolnych elementów kompozycyjnych z modyfikatorami
Możesz zastosować modyfikatory gestów do dowolnego elementu kompozycyjnego, aby
lub funkcji kompozycyjnej, słuchając gestów. Możesz na przykład zezwolić na używanie ogólnego Box
obsługuje gesty dotykania, ustawiając go jako clickable
, lub pozostaw Column
i obsługuje przewijanie w pionie, stosując verticalScroll
.
Do obsługi różnych rodzajów gestów dostępnych jest wiele modyfikatorów:
- Obsługuj dotknięcia za pomocą przycisków
clickable
,combinedClickable
,selectable
,toggleable
i modyfikatorytriStateToggleable
. - Obsługuj przewijanie za pomocą przycisków
horizontalScroll
,verticalScroll
i bardziej ogólnescrollable
modyfikatory. - Przeciągaj za pomocą przycisków
draggable
iswipeable
modyfikator. - Obsługuj gesty wielodotykowe, takie jak przesuwanie, obracanie i powiększanie,
modyfikator
transformable
.
Zazwyczaj wolą od razu gotowe modyfikatory gestów zamiast obsługi niestandardowych gestów.
Modyfikatory zapewniają większą funkcjonalność oprócz obsługi zdarzeń wskaźnika.
Na przykład modyfikator clickable
dodaje nie tylko wykrywanie naciśnięć
ale też dodaje informacje semantyczne i wizualne wskazówki dotyczące interakcji,
najeżdżanie kursorem, zaznaczenie i obsługę klawiatury. Żeby zapoznać się z kodem źródłowym,
clickable
, aby zobaczyć, jak funkcja
jest dodawana.
Dodaj niestandardowy gest do dowolnych elementów kompozycyjnych za pomocą modyfikatora pointerInput
Nie każdy gest można zastosować za pomocą gotowego modyfikatora gestów. Dla:
nie można na przykład użyć modyfikatora, aby zareagować na przeciąganie po przytrzymaniu,
z naciśniętym klawiszem Control lub 3 palcami. Zamiast tego możesz napisać własny gest
do identyfikowania tych gestów niestandardowych. Moduł obsługi gestów możesz utworzyć w aplikacji
modyfikator pointerInput
, który zapewnia dostęp do surowego wskaźnika.
zdarzeń.
Ten kod nasłuchuje nieprzetworzonych zdarzeń wskaźnika:
@Composable private fun LogPointerEvents(filter: PointerEventType? = null) { var log by remember { mutableStateOf("") } Column { Text(log) Box( Modifier .size(100.dp) .background(Color.Red) .pointerInput(filter) { awaitPointerEventScope { while (true) { val event = awaitPointerEvent() // handle pointer event if (filter == null || event.type == filter) { log = "${event.type}, ${event.changes.first().position}" } } } } ) } }
Po podzieleniu tego fragmentu główne komponenty to:
- Modyfikator
pointerInput
. Musisz przekazać co najmniej 1 klucz. Gdy zmieni się wartość jednego z tych kluczy, wartość modyfikatora lambda wyniesie wykonane ponownie. Przykład przekazuje opcjonalny filtr do funkcji kompozycyjnej. Jeśli wartość tego filtra się zmieni, moduł obsługi zdarzeń wskaźnika powinien i sprawdzić, czy są rejestrowane odpowiednie zdarzenia. awaitPointerEventScope
tworzy zakres współrzędny, którego można użyć do poczekaj na zdarzenia wskaźnika.awaitPointerEvent
zawiesza współrzędną do następnego zdarzenia wskaźnika ma miejsce.
Odsłuchiwanie nieprzetworzonych zdarzeń wejściowych jest bardzo przydatne, ale zapis niestandardowy gest wygenerowany na podstawie nieprzetworzonych danych. Aby uprościć tworzenie list niestandardowych gestów, dostępnych jest wiele metod.
Wykrywanie pełnych gestów
Zamiast obsługiwać nieprzetworzone zdarzenia wskaźnika możesz nasłuchiwać określonych gestów
i odpowiednio zareagować. AwaitPointerEventScope
zapewnia
metod nasłuchiwania:
- Naciśnij i przytrzymaj:
detectTapGestures
- Przeciąganie:
detectHorizontalDragGestures
,detectVerticalDragGestures
,detectDragGestures
idetectDragGesturesAfterLongPress
- Przekształcenia:
detectTransformGestures
Są to detektory najwyższego poziomu, więc nie możesz dodać ich wielu w jednym
Modyfikator pointerInput
. Ten fragment kodu wykrywa tylko kliknięcia,
przeciąganie:
var log by remember { mutableStateOf("") } Column { Text(log) Box( Modifier .size(100.dp) .background(Color.Red) .pointerInput(Unit) { detectTapGestures { log = "Tap!" } // Never reached detectDragGestures { _, _ -> log = "Dragging" } } ) }
Wewnętrznie metoda detectTapGestures
blokuje współrzędną, a druga funkcja
nie udało się uzyskać dostępu do wzorca. Jeśli chcesz dodać więcej niż jeden detektor gestów do
funkcję kompozycyjną, użyj zamiast niej osobnych instancji modyfikatora pointerInput
:
var log by remember { mutableStateOf("") } Column { Text(log) Box( Modifier .size(100.dp) .background(Color.Red) .pointerInput(Unit) { detectTapGestures { log = "Tap!" } } .pointerInput(Unit) { // These drag events will correctly be triggered detectDragGestures { _, _ -> log = "Dragging" } } ) }
Obsługa zdarzeń według gestu
Zgodnie z definicją gesty zaczynają się od zdarzenia wskaźnika w dół. Za pomocą
awaitEachGesture
zamiast pętli while(true)
, która
przez każde nieprzetworzone zdarzenie. Metoda awaitEachGesture
uruchamia ponownie
który zawiera blok po podniesieniu wszystkich wskaźników, co wskazuje, że gest to
ukończono:
@Composable private fun SimpleClickable(onClick: () -> Unit) { Box( Modifier .size(100.dp) .pointerInput(onClick) { awaitEachGesture { awaitFirstDown().also { it.consume() } val up = waitForUpOrCancellation() if (up != null) { up.consume() onClick() } } } ) }
W praktyce prawie zawsze będziesz chcieć używać usługi awaitEachGesture
, chyba że
reaguje na zdarzenia wskaźnika bez identyfikowania gestów. Na przykład:
hoverable
, który nie reaguje na zdarzenia wskaźnika w dół ani w górę.
musi wiedzieć, kiedy wskaźnik pojawi się na obszarze lub z niego wyjść.
Poczekaj na konkretne zdarzenie lub podgest
Dostępny jest zestaw metod, które pomagają rozpoznawać typowe części gestów:
- Zawieś do momentu, gdy wskaźnik
awaitFirstDown
przestanie spadać, lub poczekaj na wszystkie wskazówki dotyczącewaitForUpOrCancellation
. - Tworzenie detektora przeciągania niskiego poziomu za pomocą narzędzia
awaitTouchSlopOrCancellation
iawaitDragOrCancellation
. Moduł obsługi gestów zostanie zawieszony do momentu, wskaźnik osiąga obszar dotknięcia, a następnie wstrzymuje działanie do momentu pierwszego przeciągania z czymś się dzieje. Jeśli interesuje Cię tylko przeciąganie wzdłuż jednej osi, użyjawaitHorizontalTouchSlopOrCancellation
i więcejawaitHorizontalDragOrCancellation
lubawaitVerticalTouchSlopOrCancellation
plusawaitVerticalDragOrCancellation
. - Zawieś, dopóki nie przytrzymasz
awaitLongPressOrCancellation
. - Użyj metody
drag
, aby stale nasłuchiwać zdarzeń przeciągania.horizontalDrag
lubverticalDrag
, aby nasłuchiwać zdarzeń przeciągania na jednym urządzeniu .
Zastosuj obliczenia dotyczące zdarzeń wielokrotnego dotyku
Gdy użytkownik wykonuje gest wielodotykowy za pomocą więcej niż 1 wskaźnika,
trudno jest zrozumieć wymagane przekształcenie na podstawie nieprzetworzonych wartości.
Jeśli modyfikator transformable
lub detectTransformGestures
nie dają wystarczającej szczegółowej kontroli nad danym przypadkiem użycia,
nasłuchiwać nieprzetworzonych zdarzeń i stosować na nich obliczenia. Te metody pomocnicze
to calculateCentroid
, calculateCentroidSize
,
calculatePan
, calculateRotation
i calculateZoom
.
Wysyłka zdarzeń i testowanie trafień
Nie każde zdarzenie wskaźnika jest wysyłane do każdego modyfikatora pointerInput
. Wydarzenie
Wysyłka przebiega w ten sposób:
- Zdarzenia wskaźnika są wysyłane do hierarchii kompozycyjnej. Moment, w którym nowy wskaźnik uruchamia pierwsze zdarzenie wskaźnika, system rozpoczyna testowanie działań „odpowiednie” elementów kompozycyjnych. Utwór kompozycyjny jest uznawany za odpowiedni, jeśli ma możliwości obsługi danych wejściowych wskaźnika. Przepływy testowania trafień dostępne od góry interfejsu użytkownika aż po drzewo graniczne. Element kompozycyjny to „działanie” kiedy wystąpiło zdarzenie wskaźnika. w granicach tego elementu kompozycyjnego. W efekcie powstaje łańcuch elementów kompozycyjnych, które osiągają pozytywne wyniki.
- Domyślnie, gdy na tym samym poziomie
drzewo, tylko funkcja kompozycyjna o najwyższej wartości z-index to „hit”. Dla:
Jeśli np. dodasz do elementu
Box
dwie nakładające się dwie kompozycjeButton
, zostaną dodane tylko to wyświetlane na górze otrzymuje wszystkie zdarzenia wskaźnika. Teoretycznie można zastąp to działanie, tworząc własnePointerInputModifierNode
implementacji i ustawianie parametrusharePointerInputWithSiblings
na „true”. - Kolejne zdarzenia dla tego samego wskaźnika są wysyłane do tego samego łańcucha elementów kompozycyjnych i działają zgodnie z logiką propagacji zdarzeń. System nie wykonuje więcej testów trafień dla tego wskaźnika. Oznacza to, że każdy funkcja kompozycyjna w łańcuchu odbiera wszystkie zdarzenia dla tego wskaźnika, nawet jeśli które występują poza granicami danego kompozycyjnego. Elementy kompozycyjne, które nie są w łańcuchu nigdy nie odbiera zdarzeń wskaźnika, nawet jeśli wskaźnik jest poza ich granicami.
Zdarzenia najechania kursorem, wywoływane po najechaniu myszą lub rysikiem, stanowią wyjątek reguł zdefiniowanych w tym miejscu. Zdarzenia najechania są wysyłane do każdego tworzonego elementu kompozycyjnego. No więc gdy użytkownik najedzie kursorem na obszar między jednym a drugim, zamiast wysyłać zdarzenia do pierwszego elementu kompozycyjnego, są one wysyłane do funkcji element kompozycyjny.
Wykorzystanie zdarzeń
Jeśli więcej niż jeden element kompozycyjny ma przypisany moduł obsługi gestów, moduły obsługi nie powinny kolidować. Przyjrzyjmy się na przykład temu interfejsowi:
Gdy użytkownik kliknie przycisk zakładki, funkcja lambda onClick
tego przycisku zajmie się
gest. Gdy użytkownik kliknie inną część elementu listy, ListItem
wykonuje ten gest i przechodzi do artykułu. Jeśli chodzi o dane wejściowe,
przycisk musi przetwarzać to zdarzenie, tak by jego element nadrzędny wie, że nie może
już na nią nie zareagować. Gesty występujące w gotowych komponentach oraz
typowe modyfikatory gestów obejmują ten sposób użycia, ale jeśli
własnego gestu, musisz przetwarzać zdarzenia ręcznie. Zrób to
metody PointerInputChange.consume
:
Modifier.pointerInput(Unit) { awaitEachGesture { while (true) { val event = awaitPointerEvent() // consume all changes event.changes.forEach { it.consume() } } } }
Wykorzystanie zdarzenia nie zatrzymuje jego propagacji do innych elementów kompozycyjnych. O funkcja kompozycyjna musi bezpośrednio ignorować wykorzystane zdarzenia. Podczas pisania za pomocą niestandardowych gestów, należy sprawdzić, czy zdarzenie nie zostało już wykorzystane przez inne element:
Modifier.pointerInput(Unit) { awaitEachGesture { while (true) { val event = awaitPointerEvent() if (event.changes.any { it.isConsumed }) { // A pointer is consumed by another gesture handler } else { // Handle unconsumed event } } } }
Propagacja zdarzeń
Jak już wspomnieliśmy, zmiany wskaźników są przekazywane do każdego elementu kompozycyjnego, w którym działa.
Jeśli jednak istnieje więcej niż jeden taki element kompozycyjny, w jakiej kolejności
propagować? W przykładzie z poprzedniej sekcji interfejs ten zmieni się
przedstawione poniżej drzewo interfejsu, w którym odpowiadają tylko ListItem
i Button
zdarzenia wskaźnika:
Zdarzenia wskaźnika przechodzą przez każdy z tych elementów kompozycyjnych 3 razy: "karty":
- W polu Początkowe potwierdzenie zdarzenie przepływa z góry drzewa interfejsu do
w dół. Dzięki temu rodzic może przechwycić zdarzenie, zanim dziecko zacznie
ich spożyć. Na przykład etykietki muszą przechwytywać
i przytrzymać, zamiast przekazywać ją dzieciom. W naszym
Na przykład
ListItem
otrzymuje zdarzenie przedButton
. - W ramach przekazu głównego zdarzenie przepływa od węzłów liści drzewa interfejsu do
pierwiastka drzewa UI. To faza, w której zwykle używasz gestów.
jest domyślnym biletem podczas nasłuchiwania zdarzeń. Obsługa gestów na tej karcie
co oznacza, że węzły liści mają pierwszeństwo przed węzłami nadrzędnymi, czyli
w przypadku większości gestów. W naszym przykładzie
Button
otrzymuje wydarzenie przedListItem
. - W ramach przebiegu ostatniego zdarzenie pojawia się jeszcze raz od góry interfejsu użytkownika. aż do węzłów liści. Dzięki temu elementy znajdujące się wyżej na stosie reagowanie na wykorzystanie zdarzeń przez rodzica. Na przykład przycisk usuwa wskazuje fazę, gdy naciśnięcie zmieni się w przeciągnięcie elementu nadrzędnego, który można przewijać.
Przebieg zdarzeń można przedstawić wizualnie w taki sposób:
Po przyjęciu zmiany danych wejściowych te informacje są przekazywane w dalszym ciągu:
W kodzie możesz określić kartę, która Cię interesuje:
Modifier.pointerInput(Unit) { awaitPointerEventScope { val eventOnInitialPass = awaitPointerEvent(PointerEventPass.Initial) val eventOnMainPass = awaitPointerEvent(PointerEventPass.Main) // default val eventOnFinalPass = awaitPointerEvent(PointerEventPass.Final) } }
W tym fragmencie kodu to samo zdarzenie jest zwracane przez każde oczekiwania na wywołania metod, chociaż dane o wykorzystaniu mogą mieć została zmieniona.
Przetestuj gesty
W metodach testowania możesz ręcznie wysyłać zdarzenia wskaźnika za pomocą
performTouchInput
. Dzięki temu możesz podejmować
wyższe działania
pełne gesty (np. ściąganie palców lub długie kliknięcie) albo gesty niskiego poziomu (np.
przesunięcie kursora o określoną liczbę pikseli):
composeTestRule.onNodeWithTag("MyList").performTouchInput { swipeUp() swipeDown() click() }
Więcej przykładów znajdziesz w dokumentacji performTouchInput
.
Więcej informacji
Więcej informacji o gestach w Jetpack Compose znajdziesz w tych artykułach: zasoby:
.Polecane dla Ciebie
- Uwaga: tekst linku wyświetla się, gdy JavaScript jest wyłączony
- Ułatwienia dostępu w funkcji tworzenia wiadomości
- Przewiń
- Naciśnij i naciśnij