Nowe formaty wykraczają poza ekrany dotykowe, dlatego system wprowadzania danych w Compose ewoluował, aby sprostać temu wyzwaniu. Jego warstwowa architektura stanowi podstawę do bezproblemowej obsługi wszystkiego, od zaawansowanych gestów na trackpadzie po nowe urządzenia wejściowe.
Dzięki przejrzystym analizom architektury i praktycznym przykładom kodu możesz prześledzić cykl życia zdarzenia wejściowego od warstwy platformy systemu operacyjnego aż po detektory gestów i komponenty interfejsu Compose. Dowiesz się, jak śledzić zdarzenia w całym stosie, stosować kluczowe wzorce architektoniczne do nowych typów danych wejściowych i skutecznie debugować złożone interakcje gestów we własnych aplikacjach.
performTrackpadInput. Te interfejsy API udostępniają znane interfejsy testowe, które umożliwiają sprawdzenie, czy aplikacje prawidłowo obsługują urządzenia bez ekranu dotykowego.Transkrypcja
Cześć,
Mamy nadzieję, że dobrze się bawicie na Android Dev Summit. Dziękujemy za udział w naszej prezentacji.
Nazywam się Alex Vanyo i jestem inżynierem oprogramowania w zespole Compose. Jest ze mną Jeremy, który również jest inżynierem oprogramowania w zespole Compose.
Obaj pracujemy nad ulepszeniem systemu wprowadzania danych w Jetpack Compose. Dzisiaj opowiemy o tym, jak rozszerzyliśmy go poza ekrany dotykowe, aby ułatwić obsługę innych metod wprowadzania danych.
Dane wejściowe to ogólnie każdy sposób, w jaki użytkownik wchodzi w interakcję z aplikacją.
Ekrany dotykowe to prawdopodobnie pierwsza rzecz, o której myślisz w kontekście aplikacji mobilnej, ale zdecydowanie nie jest to jedyny typ danych wejściowych.
Inne typy urządzeń wejściowych to klawiatury fizyczne, myszy, trackpady, rysiki, kontrolery i urządzenia obrotowe.
Pojawiają się też nowe typy danych wejściowych, takie jak trackpady na inteligentnych okularach, śledzenie wzroku i rąk w XR oraz gesty palców i nadgarstków na Wear.
Jeśli chcesz pójść jeszcze bardziej ogólnie, istnieją też inne typy danych wejściowych: takie jak oprogramowanie IME do edycji tekstu, mikrofony, kamery i inne czujniki urządzenia.
Musisz się liczyć z tym, że Twoje aplikacje będą używane z różnymi typami danych wejściowych.
Jednym z powodów jest coraz większa różnorodność urządzeń, na których działają aplikacje, oraz coraz większe możliwości dostosowywania tych urządzeń.
Formaty urządzeń mogą mieć najczęściej używaną metodę wprowadzania danych, ale nie należy zakładać, że dany format urządzenia oznacza użycie określonego typu wprowadzania danych.
Chromebook ma wbudowany trackpad i klawiaturę, ale te typy danych wejściowych nie są unikalne dla tego formatu.
Można oczekiwać, że klawiatury fizyczne, trackpady i myszy będą używane na telefonach, tabletach i telewizorach, a niektóre telewizory mają nawet ekrany dotykowe.
Drugim ważnym powodem, dla którego warto oczekiwać i obsługiwać różne typy danych wejściowych, jest ułatwienie dostępu.
Umożliwienie użytkownikom korzystania z wybranego urządzenia wejściowego i przełączania się między urządzeniami wejściowymi zgodnie z ich potrzebami to właściwy sposób na zwiększenie dostępności i użyteczności aplikacji.
W zależności od jurysdykcji może być wymagane, aby aplikacja działała w określony, zgodny sposób w przypadku tych typów danych wejściowych, zwłaszcza w przypadku klawiatur i myszy.
Krótko mówiąc, należy zapewnić obsługę różnych typów danych wejściowych niezależnie od formatu.
Zobaczmy, jak Jetpack Compose pomaga aplikacjom spełniać te potrzeby użytkowników.
Zgodnie z filozofią Compose te różne typy danych wejściowych powinny działać prawidłowo ze standardowymi komponentami bez konieczności wykonywania dodatkowych czynności.
Następnie powinny być dostępne narzędzia, które pozwolą Ci dostosować i zmodyfikować działanie zgodnie z Twoimi potrzebami.
Oprócz tych początkowych typów danych wejściowych, w miarę jak platforma będzie obsługiwać ich więcej, chcemy dodawać ich obsługę do komponentów Compose, aby ulepszać działanie aplikacji przez samo aktualizowanie wersji Compose.
W tej prezentacji omówimy 2 takie przykłady, które niedawno dodaliśmy do Compose.
Przypadkowo oba są urządzeniami podobnymi do trackpada, ale jak zobaczysz, są używane w bardzo różne sposoby.
Zacznijmy od niedawnych prac nad trackpadami w laptopach.
Trackpad to urządzenie wejściowe ze wskaźnikiem.
Mówiąc dokładniej, gdy używasz trackpada, otrzymujesz współrzędne dwuwymiarowe.
Jednak w przeciwieństwie do ekranu dotykowego współrzędne palców na trackpadzie nie znajdują się bezpośrednio w przestrzeni współrzędnych interfejsu.
Zamiast tego, w przypadku trackpada podobnego do laptopa, ruchy są przekładane na sterowanie kursorem.
W ramach tego tłumaczenia możesz spodziewać się wielu skomplikowanych konwersji platformy, takich jak przyspieszenie kursora i odrzucanie dotknięć dłonią.
Po stronie Compose otrzymujesz wskaźnik odpowiadający lokalizacji kursora, który zwykle działa jak sygnał myszy.
Z platformy Androida otrzymujemy informacje o najechaniu kursorem, kliknięciach, w tym kliknięciach dodatkowych lub trzeciorzędnych, a wszystkie te informacje są zawarte w zdarzeniach MotionEvent.
Aby zrozumieć, jak ulepszyliśmy działanie w najnowszych wersjach Compose, przyjrzyjmy się najpierw ogólnemu opisowi tego, jak Compose obsługuje te zdarzenia wskaźnika.
Podzielę ten proces na 6 etapów, które omówię po kolei.
Pierwsza faza to przeniesienie zdarzeń MotionEvent do Compose.
Compose to w zasadzie tylko widok – bardzo skomplikowany widok, który robi wiele rzeczy, ale to tylko widok, co oznacza, że musi działać w ramach ogólnego systemu Androida w przypadku danych wejściowych ze wskaźnika.
W Compose oznacza to, że Compose, jako że znajduje się w zwykłym oknie w zwykłej aktywności, otrzymuje zdarzenia MotionEvents: zdarzenia MotionEvents platformy Androida.
Następnie Compose musi je obsłużyć i wykonać wszystkie czynności, które chcesz wykonać w Compose w związku z danymi wejściowymi ze wskaźnika.
Pierwszym etapem jest przekształcenie tych zdarzeń MotionEvent w wewnętrzną, niezależną od platformy reprezentację w Compose, którą nazywamy PointerInputEvent.
Jest to typ wewnętrzny, więc nie zobaczysz go bezpośrednio.
Jeśli chcesz poznać wszystkie szczegóły tego, co się tutaj dzieje, możesz zajrzeć do MotionEventAdapter, ale tutaj pominiemy te szczegóły.
Mamy więc teraz w Compose reprezentację zdarzenia MotionEvent niezależną od platformy.
Okazuje się, że dane wejściowe są zwykle wprowadzane w czasie.
Zdarzenia wejściowe następują jedno po drugim, więc warto wiedzieć, co dzieje się w czasie.
Gesty, śledzenie prędkości, wykrywanie nachylenia – wszystkie te elementy muszą mieć informacje o tym, co dzieje się w każdym momencie.
Kolejnym etapem w tym procesie jest dodanie informacji o czasie i przeprowadzenie wstępnego przetwarzania na podstawie kolejnych zdarzeń wskaźnika.
W kodzie można to postrzegać jako pobranie bieżącego zdarzenia PointerInputEvent, połączenie go z tym, co wydarzyło się w poprzednim, a następnie utworzenie zdarzenia InternalPointerEvent uwzględniającego czas.
To kolejny typ wewnętrzny, jak sugeruje nazwa, ale można go traktować jako mapę różnych identyfikatorów wskaźników do zmian danych wejściowych wskaźnika, a PointerInputChanges jest typem publicznym.
To jest nasza pierwsza.
Jeśli chodzi o różne identyfikatory wskaźników, możesz zauważyć, że teraz, gdy śledzimy czas, możemy też śledzić, kiedy identyfikatory wskaźników pojawiają się i znikają.
Nie ma tylko jednego.
Jeśli masz kilka palców na ekranie dotykowym, możesz mieć kilka wskaźników jednocześnie, które pojawiają się i znikają, i musimy to obsłużyć.
Jeśli chcesz poznać szczegóły, możesz zajrzeć do PointerInputChangeEventProducer, który wykonuje wszystkie te czynności, ale my to pominiemy.
Mamy jeszcze dużo do omówienia.
Mamy więc teraz reprezentację danych wejściowych w Compose, która zawiera informacje o czasie.
Następnym krokiem jest ustalenie, co zrobić z tym zdarzeniem.
Gdzie mamy je wysłać?
To prowadzi do testowania ścieżki trafień.
Wyobraźmy sobie sytuację, w której wysyłany jest MotionEvent, a ComposeView wypełnia całe okno, a może nawet cały ekran.
Musimy ustalić, gdzie go wysłać.
Compose to tylko widok, ale zawiera mnóstwo elementów, takich jak różne węzły, przyciski i inne elementy, z którymi można wchodzić w interakcje. Dlatego w Compose musimy wykonać całą pracę związaną z ustaleniem, gdzie wysłać zdarzenie, które występuje w określonej pozycji, i który węzeł jest nim zainteresowany.
Te węzły układu mają współrzędne, granice i kształty, więc musimy ustalić, który z nich został kliknięty.
Ale to też nie jest najbardziej ogólny widok.
Okazuje się, że lubimy umieszczać elementy interaktywne w innych elementach interaktywnych, np. przycisk na liście.
Okazuje się, że robimy to bardzo często.
W pełnym ogólnym przypadku konkretne zdarzenie wejściowe tworzy pełną ścieżkę trafienia, która przechodzi przez każdy węzeł interaktywny trafiony przez współrzędne zdarzenia wejściowego.
W tym przypadku mamy więc 3 poziomy hierarchii zagnieżdżania.
Mamy więc ścieżkę trafienia, ale to nie wystarczy.
Pamiętasz, jak mówiłem, że mamy wiele zdarzeń wejściowych wskaźnika i wiele wskaźników, które mogą być naciśnięte?
Potrzebujemy pełnego drzewa ścieżki trafień.
Jeśli masz kilka wskaźników, nie ma tylko jednej ścieżki trafienia.
Kolejnym etapem jest utworzenie i aktualizowanie drzewa ścieżek działań.
To bardziej szczegółowe omówienie tego, co się dzieje.
Po lewej stronie mamy węzły, które są rozmieszczone wizualnie, a w lewym dolnym rogu widać reprezentację hierarchiczną, czyli które węzły znajdują się wewnątrz innych węzłów, które są przodkami, a które potomkami.
Gdy wskaźnik zostanie naciśnięty, śledzimy ścieżkę trafienia, czyli pełną ścieżkę przez drzewo, a następnie uzyskujemy ścieżkę trafienia dla tego konkretnego wskaźnika.
Gdy otrzymamy drugi wskaźnik, ta ścieżka trafień stanie się drzewem.
Może przecinać się z niektórymi z tych samych węzłów co druga ścieżka, ale może też wchodzić w interakcje z innymi.
Po prawej stronie znajduje się drzewo ścieżek trafień, które obecnie utrzymujemy.
Wskaźniki mogą się pojawiać i znikać, więc musimy sami dbać o to, aby mieć pełną listę węzłów, które są zainteresowane każdym trwającym wydarzeniem w danym momencie.
OK.
Mamy teraz drzewo ścieżki trafień, które utrzymujemy dla tych zdarzeń wejściowych, co oznacza, że przechodzimy do następnej fazy.
Teraz wiemy, gdzie wysłać te zdarzenia wejściowe wskaźnika i które węzły są zainteresowane konkretnym zdarzeniem.
Teraz, gdy już to wiemy, możemy przekształcić współrzędne.
Okazuje się, że bardzo wygodnie jest, gdy węzeł otrzymuje zdarzenia wejściowe w swojej lokalnej przestrzeni współrzędnych, dzięki czemu nie trzeba samodzielnie wykonywać wielu obliczeń współrzędnych.
Gdy już to wiemy, możemy pobrać zdarzenie wejściowe wskaźnika, połączyć je ze współrzędnymi węzła, który nas interesuje, i w ten sposób utworzyć publiczne zdarzenie PointerEvent, które zostanie wysłane do wszystkich użytkowników i modyfikatora wejściowego wskaźnika.
To ostatni element fazy 6, czyli wysłanie zdarzenia.
Jest to zdarzenie wejściowe wskaźnika, które miało miejsce i jest wysyłane do węzła, aby ten mógł z nim zrobić, co chce.
Robimy to w 3 etapach.
Mamy etap wstępny, główny i końcowy.
Powodem, dla którego mamy 3 różne przebiegi, jest to, że umożliwia to węzłom komunikowanie się ze sobą i przekazywanie informacji o zainteresowaniu określonymi częściami określonych zdarzeń wejściowych wskaźnika, a następnie wykorzystywanie różnych ich części w ramach wysyłania.
Pierwszy etap, czyli etap początkowy, przechodzi od przodka do potomka.
Przykładem sytuacji, w której jest to przydatne, jest sytuacja, gdy jakiś zewnętrzny węzeł układu może być kontenerem przewijania i wie, że zaczął się przewijać, i chce zatrzymać wszystkie zdarzenia wskaźnika, aby nie docierały do żadnego z jego elementów podrzędnych.
Dzięki temu w pierwszym przebiegu może on wykorzystać zdarzenie, a elementy podrzędne nie otrzymają już żadnych zdarzeń.
Drugi przebieg jest główny.
Jest to jedyna relacja, która przechodzi od potomka do przodka.
Jak sugeruje nazwa, jest to podstawowy przebieg, który wykonuje zadania.
Na przykład w tym miejscu element podrzędny wykorzysta zdarzenie naciśnięcia, rozpoczynając obsługę kliknięcia, a następnie będzie mógł to zrobić przed dowolnym elementem nadrzędnym, jeśli masz zagnieżdżone elementy klikalne.
Ostatni etap wygląda tak samo jak pierwszy, czyli przechodzi od przodka do potomka.
W ten sposób dziecko może poinformować rodzica, że obejrzało część wydarzenia.
Jeśli więc dziecko zaczęło obsługiwać kliknięcie, ale rodzic zaczął przewijać, dziecko może zobaczyć, że rodzic to zrobił, i anulować kliknięcie, które zaczęło obsługiwać.
To były 3 etapy i 6 faz.
To podstawa wszystkiego, co znajduje się nad systemem wprowadzania danych za pomocą wskaźnika.
Wszystkie gesty, przewijanie, przeciąganie, klikanie i niestandardowe dane wejściowe ze wskaźnika są oparte na tej podstawie.
Teraz możemy omówić szczegóły dotyczące tego, jak ulepszyliśmy obsługę trackpada w Compose.
Dlatego przed Compose 1.12 zdarzenia wejściowe trackpada były zgłaszane jako dotyk, ponieważ to palec fizycznie wchodzi w interakcję z trackpadem, aby sterować kursorem.
Użytkownicy oczekują jednak, że ten typ danych wejściowych będzie działać podobnie jak fizyczna mysz.
Zmieniliśmy więc typ wskaźnika z PointerType.Touch na PointerType.Mouse.
Dzięki tej zmianie użytkownicy będą mogli korzystać z myszy i trackpada w bardziej spójny sposób w przypadku każdego komponentu, który korzysta z tych informacji.
Przykładem może być zachowanie etykietki: prawdopodobnie chcesz, aby etykietki zachowywały się tak samo w przypadku trackpada i myszy.
Druga duża zmiana, którą wprowadziliśmy w trackpadzie, dotyczy gestów z użyciem wielu palców.
Wcześniej Compose korzystał z pomysłowej, ale nieco dziwnej koncepcji fałszywych palców na platformie Androida.
Zobaczmy, co to oznacza.
Gdy używasz 2 palców na trackpadzie do przesuwania, Android generuje pojedynczy fałszywy palec na ekranie dotykowym, który pojawia się w miejscu kursora.
Następnie, gdy przesuwasz palce po trackpadzie, sztuczny palec na ekranie dotykowym przesuwa się o odpowiednią odległość i w odpowiednim kierunku, aby naśladować pożądane zachowanie, a następnie się odrywa.
Dlatego przesunięcie 2 palcami w górę na trackpadzie jest interpretowane jako przesunięcie 1 palcem w górę na ekranie dotykowym, co powoduje przewinięcie w dół.
Jeśli tak jak ja używasz funkcji odwróconego przewijania na trackpadzie, możesz chcieć, aby przewijanie działało w drugą stronę.
Można to osiągnąć, zmieniając kierunek, w którym porusza się fałszywy palec.
Teraz ruch 2 palcami w górę na trackpadzie jest tłumaczony na ruch 1 fałszywym palcem w dół na ekranie dotykowym, co powoduje przewinięcie w górę, czyli odwrotnie niż wcześniej.
Skalowanie działa w podobny sposób.
Gdy używasz 2 palców na trackpadzie, aby zmienić rozmiar, Android generuje 2 fałszywe palce na ekranie dotykowym, które przesuwają się w dół, a następnie zbliżają się do siebie lub oddalają, w zależności od gestu, aby naśladować pożądane zachowanie powiększania lub pomniejszania.
Dlatego od wersji Compose 1.12 w przypadku przesuwania i Compose 1.13 w przypadku skalowania przechwytujemy te fałszywe palce.
Nie wysyłamy ich w takiej postaci, w jakiej otrzymujemy je od platformy, ale interpretujemy je jako nowe typy zdarzeń wskaźnika: PanStart, PanMove i PanEnd w przypadku przesuwania oraz ScaleStart, ScaleChange i ScaleEnd w przypadku skalowania.
Robimy to na etapie konwersji zdarzeń z Androida na Compose, o którym wspomniałem wcześniej.
Dlatego konwertujemy zdarzenia Fake Finger na odpowiednie typy, a następnie wysyłamy te zdefiniowane przez platformę gesty do ścieżki trafienia, która odpowiada kursorowi.
Zobaczmy, dlaczego to ważne.
Dlaczego w ogóle to robimy?
Załóżmy, że to pole to ładny obraz, który chcemy powiększyć.
Gdy używasz 2 palców na trackpadzie do skalowania, może się zdarzyć, że jeden z nich nie trafi w element.
Ścieżka trafienia nie przecinałaby się z obrazem i zaczynałaby się poza granicami.
Zamiast skalowania obrazu może nastąpić jego przewijanie.
Jeśli poza obrazem byłby inny element, który można przewijać, być może on też zacząłby się przewijać.
Nie o to chodziło użytkownikowi.
Chciała zwiększyć skalę działalności.
Zamiast wysyłać te fałszywe palce, wysyłamy teraz gest skalowania tylko do elementu pod pozycją kursora, co prowadzi do prawidłowego działania.
Niezależnie od tego, gdzie znajduje się kursor i jak blisko jest krawędzi elementu, który chcesz przeskalować, skalowanie będzie teraz działać prawidłowo.
Dobra wiadomość jest taka, że te zinterpretowane na nowo zdarzenia wejściowe są domyślnie używane w komponentach takich jak scrollable i transformable, aby zapewnić większą wierność zachowania w przypadku danych wejściowych z trackpada, jeśli używasz tych podstawowych elementów.
Jeśli używasz czegoś, co zostało na nich zbudowane, ulepszone działanie uzyskasz, aktualizując Compose do najnowszej wersji.
Jeśli piszesz niestandardowe modyfikatory danych wejściowych ze wskaźnika, zachęcam do zapoznania się z tymi nowymi interfejsami API i upewnienia się, że je obsługujesz, aby zapewnić użytkownikom jak najlepsze wrażenia na wszystkich tych urządzeniach wejściowych.
Dodaliśmy też interfejsy API do testowania, dzięki którym możesz sprawdzić, czy aplikacja prawidłowo obsługuje wszystkie te różne typy danych wejściowych.
performTrackpadInput wygląda więc bardzo podobnie do istniejących interfejsów API do testowania myszy i dotyku.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}Korzystając z tych wszystkich narzędzi, możesz sprawdzić, czy aplikacja działa prawidłowo, niezależnie od tego, jakiego urządzenia wejściowego używa użytkownik.
Mam nadzieję, że teraz lepiej rozumiesz, jak zdarzenie wskaźnika przechodzi od palca na touchpadzie do aplikacji.
Teraz przekażę głos Jeremy'emu, który opowie o zupełnie innym sposobie korzystania z trackpadów.
Dziękujemy.
OK.
Cześć, nazywam się Jeremy Walker.
Często pracuję nad wprowadzaniem danych i pomyślałem, że warto byłoby opowiedzieć o nowych funkcjach, które wprowadzamy w Compose, i o tym, jak wpływają one na Ciebie.
A konkretnie z tym typem wprowadzania pośredniego, który powstał dzięki nowemu sposobowi wprowadzania danych opartemu na nowym formacie: inteligentnych okularach.
Dane wejściowe można przekazywać do okularów na kilka sposobów: można na coś patrzeć, można używać głosu, można używać gestów, ale ja skupię się na touchpadzie.
Te są nieco inne niż te, o których mówił Alex, ponieważ znajdują się z boku głowy, są pionowe, bardzo szerokie i niezwykle krótkie.
Gdy zastanawialiśmy się, jak to będzie działać w Compose, wyobraziliśmy sobie, że patrzysz na mały przycisk przed sobą. Jeśli przesuniesz kursor, aby go dotknąć, nie będzie to zbyt wygodne, prawda?
Jak więc będziemy je wspierać?
Tak wygląda interfejs okularów.
Zostały zaprojektowane tak, aby płynnie nakładać treści na pole widzenia.
To bardzo proste. Staramy się, żeby było proste.
W tym przykładzie widać, że jest to lista.
Skupia się na określonym elemencie w centrum.
Co tak naprawdę chcemy zrobić?
Chcemy go wybrać lub przejść do poprzedniego albo następnego.
Nie musimy znać lokalizacji ekranu.
Ten jest nieco bardziej skomplikowany, ale widać, że są tu 3 opcje: rozłączenie, wyłączenie wyciszenia i wyłączenie obrazu.
Ale tak naprawdę chcesz go wybrać lub przejść do innego i wybrać ten inny.
Dlatego uważamy, że nie musimy mapować bezpośrednio na lokalizację na ekranie, czyli w tym przypadku na oczy.
Tak to wygląda.
Na przykład tutaj przechodzę między elementami w interfejsie okularów i to jest mniej więcej to, czego chcemy.
Możesz też kliknąć.
Przyjrzeliśmy się Androidowi, aby sprawdzić, co jest dostępne i co moglibyśmy wykorzystać do obsługi tej funkcji.
Jeśli kiedykolwiek… Przepraszam.
Przeskoczmy do przodu.
Pozwól mi wrócić.
I o to chodzi.
OK.
Jeśli więc korzystaliście kiedyś z jednego z tych urządzeń i połączyliście je z urządzeniem z Androidem, wiecie, że można łatwo przechodzić między elementami interfejsu z jednego urządzenia na drugie.
Na przykład za pomocą pilota możesz zobaczyć, że jeśli masz go połączonego z Google TV i masz całą siatkę filmów, możesz nacisnąć przyciski strzałek, aby przejść do wybranego filmu, i nacisnąć przycisk OK, aby go wybrać.
Jeśli masz menu gry i używasz tego kontrolera, możesz użyć pada kierunkowego lub joysticka, aby przejść do elementu interfejsu, na przykład w menu, i go wybrać.
To samo dotyczy klawiatury.
To pasuje do tego, co chcemy osiągnąć w Compose.
Oto te elementy, żebyście mogli zobaczyć, jak to wygląda.
Jeśli cofniemy się jeszcze bardziej, zobaczymy ten touchpad, który był sparowany z telewizorem i wykorzystywał funkcję nawigacji dotykowej.
Chodzi o to, że współrzędne X i Y nie są w ogóle powiązane z ekranem.
Są one używane tylko do wykrywania gestów.
Jeśli przesuniesz palcem w prawo, to tak, jakbyś nacisnął strzałkę, aby przejść dalej.
Jeśli przesuniesz palcem w lewo, przejdziesz w lewo, w górę i w dół.
W ten sposób uzyskujemy mniej więcej to, czego chcemy.
Współrzędne x i y są używane tylko do śledzenia prędkości, aby określić przeciągnięcia i przesunięcia itp. To daje nam większość tego, czego potrzebujemy, aby zacząć korzystać z Compose.
Nie jest to dokładne, ponieważ jest to w pewnym sensie mapowane na telewizor, więc musimy upewnić się, że nie pójdziemy tą drogą i nie zepsujemy później telewizorów. Musimy więc upewnić się, że je rozdzielimy, ale do tego dojdziemy za chwilę.
ale daje nam punkt wyjścia.
Możemy więc używać tego typu nawigacji dotykowej.
No dobrze.
Jak będzie wyglądać projekt interfejsu API?
W Compose najbardziej ogólnym lub najczęstszym sposobem wprowadzania danych jest dotyk, prawda?
Mamy więc dużo pracy w tym zakresie.
Musieliśmy więc wymyślić, jak to się ma do tego.
Przypominamy, że tak wygląda PointerEvent.
Tutaj widzisz typ, który jest rodzajem ruchu w dół i w górę, a następnie PointerInputChange, który jest po prostu listą. Możesz o tym myśleć jak o palcach.
Jeśli masz 3 palce, na liście będą 3 pozycje.
Chcieliśmy, żeby wyglądało to mniej więcej tak.
Nie jest to moduł interfejsu, który jest znacznie niższym modułem niż ten, którego używa większość deweloperów, gdy zajmują się danymi wejściowymi.
Będą one warstwą powyżej warstwy podstawowej.
To tam znajdują się wszystkie nasze detektory gestów.
Ponieważ jako deweloperzy nie przejmujecie się takimi szczegółami.
Chcesz tylko wiedzieć, czy nastąpiło przeciągnięcie, kliknięcie lub przesunięcie, i na tej podstawie wykonać działanie.
Mamy więc sporo kodu, który robi to za Ciebie, a w tle działają te elementy.
Zaczęliśmy więc myśleć, jak ułatwić kodowi istniejącemu w podstawie obsługę tych nowych typów.
Stworzyliśmy więc nowy typ, który wygląda bardzo podobnie.
Jest to określane jako pośrednie.
Pośrednie oznacza, że nie dotykasz bezpośrednio ekranu.
Dzięki temu wiesz, że coś się zbliża. Nie myśl o tym z perspektywy punktu styczności.
Pomyśl o tym, że obsługujemy tu tylko gesty i poruszamy się między elementami interfejsu.
Typ jest taki sam.
Jest tam lista palców.
W tym przypadku jest to IndirectPointerInputChange.
Zrobiliśmy to i zadziałało świetnie.
Natknęliśmy się jednak na jeszcze jeden element, który nazwaliśmy główną osią ruchu kierunkowego.
To dość skomplikowane, ale wszystko stanie się jasne, gdy to wyjaśnię, bo to, jak do tego doszło, jest dość interesujące.
Jak do tego doszło?
Z boku głowy masz panel dotykowy, który jest szeroki i krótki.
W tym momencie mieliśmy już pomysł na nawigację dotykową.
Możemy przesuwać palcem w lewo i w prawo.
To działa bardzo dobrze.
Ale jeśli pomyślisz o tym, jak obecnie wyglądają trackpady, prawda?
Gdy przesuniesz palcem w lewo lub w prawo, będziesz poruszać się w lewo lub w prawo po trackpadzie z treścią interfejsu.
Jeśli przesuniesz palcem w górę lub w dół, przewiniesz listę w górę lub w dół.
Jeśli spróbujesz zrobić to w okularach, nie jest tak źle, prawda?
Ale jeśli to robisz, nie jest to najlepsze rozwiązanie.
Odkryliśmy, że najbardziej naturalnym ruchem jest oczywiście dotknięcie, ale też przesunięcie palcem po boku okularów.
Przeprowadziliśmy więc szereg badań z użytkownikami i okazało się, że gdy przesuwają palcem w poziomie, oczekują, że treść nie będzie się przesuwać w poziomie, tylko w pionie.
Możesz to sobie wyobrazić, jeśli spróbujesz to zrobić w głowie, np. gdy przesuwasz palcem tutaj, jeśli masz przed sobą listę, możesz iść do przodu, możesz iść do przodu po liście lub możesz wrócić do listy.
Mamy więc dane wejściowe w 2 wymiarach i staramy się je przeanalizować w 1 wymiarze.
Stąd wziął się pomysł na główną oś ruchu, dzięki której można przesuwać palcem w poziomie, a obraz będzie się przesuwał w pionie.
Możesz więc określić je na urządzeniu.
Możesz powiedzieć, że to nie jest zwykły trackpad.
Chcę, aby główny kierunek był poziomy i jest to jedyna oś, wzdłuż której chcę się poruszać.
Stąd się to wzięło.
OK.
Więc jesteśmy zadowoleni.
Compose zajmuje się wszystkim.
I masz tę ładną implementację pionową.
Możesz się nad tym zastanowić.
W przypadku definicji poziomej mamy tylko opcje „Do przodu” i „Do tyłu”.
Nie ma góry, dołu, lewej ani prawej strony.
Mamy tylko przyciski do przodu i do tyłu.
Możemy to zmapować, jeśli znasz opcję zaznaczania następnego i poprzedniego elementu.
Teraz przejdziemy do następnego i poprzedniego.
To świetnie sprawdza się w przypadku filmów w pionie.
I jest jeszcze ładniejszy.
Działa od razu w orientacji poziomej, prawda?
Ponieważ masz tylko kilka elementów, na których się skupiasz.
Możesz przejść do następnego lub poprzedniego i będzie to działać w obu przypadkach.
W przypadku Compose wszystko to zdefiniowaliśmy i rozwiązaliśmy, ale byłoby naprawdę dobrze, gdyby urządzenie samo mogło to określić, ponieważ wie, czy ma nietypowy kształt i czy chce tylko tej osi głównej, ale nie ma możliwości, aby to zdefiniować.
Musieliśmy więc przenieść tę koncepcję na platformę i powiedzieć: „OK, musimy to zdefiniować na platformie, abyśmy mogli to ustandaryzować, a same urządzenia mogły to określić, dzięki czemu Compose będzie mógł to w pełni obsługiwać”.
W tym przypadku pracowaliśmy w Compose, poszliśmy do przodu, a potem cofnęliśmy się i przenieśliśmy to rozwiązanie.
I właśnie to zrobiliśmy.
Utworzyliśmy ten nowy interfejs API, aby umożliwić Ci określenie tego lub aby urządzenie mogło to określić.
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()Wcześniej, gdy mówiłem o dotykowym wprowadzaniu danych, a raczej o nawigacji dotykowej, wspomniałem, że jest ona przeznaczona głównie do telewizorów.
Nie chcieliśmy ich psuć.
Dodaliśmy więc kolejne pole, aby określić ten nowy typ danych wejściowych dotyczących szkła.
OK.
Co to oznacza dla Ciebie?
Jest to bardziej szczegółowa wersja tego, co otrzymujesz jako detektor gestów.
// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()Jeśli używasz Foundation, od razu po zainstalowaniu będzie działać z detektorami gestów.
Zrobiliśmy to za Ciebie, więc nie musisz nic robić.
Jeśli jednak chcesz stworzyć aplikację na okulary, możesz użyć naszej biblioteki Glimmer, a następnie specjalnego detektora gestów, który zapewnia tylko… Nie musisz ich łączyć, możesz użyć tylko tego jednego, aby uzyskać kliknięcie, klatkę do przodu i do tyłu.
OK.
Omówiliśmy już działanie interfejsu API na platformie i w Compose.
Co z testowaniem?
Tak wygląda test dotyku.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}Wystarczy powiedzieć performTouchInput i wykonać gest.
Istnieje wiele różnych gestów, a my tworzymy wszystkie drobne zdarzenia, które umożliwiają ich wykonywanie.
Jak to działa?
Mamy moduł interfejsu Compose i moduł testowania interfejsu, które komunikują się ze sobą, przynajmniej w zakresie danych wejściowych, za pomocą MotionEvents.
Moduł testowania interfejsu korzysta też z mechanizmu kolejkowania, który jest oparty na MotionEvent.
Dlaczego o tym mówimy?
Nie można programowo utworzyć zdarzenia MotionEvent, które jest podobne do zdarzenia wejściowego pośredniego, co było dla mnie świetne, ponieważ oznaczało, że musiałem przepisać całą komunikację między modułami a systemem kolejkowania.
Ale udało mi się.
Co to oznacza dla Ciebie?
Jeśli masz doświadczenie z testowaniem dotykowego wprowadzania danych, tak wygląda testowanie wprowadzania pośredniego.
Wystarczy dodać do nazwy słowo „pośredni”.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}Teraz przekazuję dodatkowe parametry.
Prawdopodobnie rozpoznajesz pierwszą z nich – to tam określasz branżę.
Rozmiar wejściowy byłby oczywiście rozmiarem touchpada.
To niewiele zmienia.
Jeśli spojrzysz na kod źródłowy, zobaczysz, że napisałem funkcję rozszerzenia, która wygląda tak.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}OK.
To powinno dać Wam wyobrażenie o tym, ile pracy włożyliśmy w to, żeby trackpady działały na oprawkach lub inteligentnych okularach.
Wykonujemy te ćwiczenia, ja też je wykonuję, aby śledzić ruchy oczu i śledzenie rąk, więc jeszcze wiele przed nami.
Podsumowując, co chcę, abyście zapamiętali z mojej części?
Niezależnie od tego, czy używasz trackpada z telefonem, okularami czy innym urządzeniem wejściowym, myślimy o tym i staramy się działać z wyprzedzeniem.
Jeśli więc używasz tych detektorów gestów Foundation, wszystko działa od razu.
To po pierwsze.
Po drugie, Compose to podstawowy zestaw narzędzi UI. Będziemy nadal odkrywać i obsługiwać nowe typy danych wejściowych zarówno na platformie, jak i w zestawie narzędzi, co widać na tym przykładzie.
W przypadku interfejsów API do testowania chcę zachować znajomy interfejs i paradygmat, aby jak najbardziej ułatwić migrację i testowanie tych nowych typów.
Dziękuję, mam nadzieję, że te informacje okazały się przydatne.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}