Podstawy aplikacji

Skalowanie interakcji wejściowych poza ekran dotykowy za pomocą Jetpack Compose

Głośniki

  • Alex Vanyo

    Inżynier oprogramowania

  • Jeremy Walker

    Inżynier oprogramowania Compose

Informacje

Informacje o wystąpieniu

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.

Podsumowanie

  • Jetpack Compose przetwarza przychodzące zdarzenia wskaźnika Androida w ramach 6-etapowego potoku. Ten potok utrzymuje drzewo ścieżki trafienia i wysyła zdarzenia w 3 przejściach przez węzły interfejsu.
  • Dane wejściowe z trackpada w Compose są teraz mapowane na zdarzenia myszy, a nie dotknięcia. Compose konwertuje też fałszywe zdarzenia palca na platformie na dedykowane zdarzenia wskaźnika przesuwania i skalowania.
  • Trackpady inteligentnych okularów wprowadzają pośrednie typy danych wejściowych, a nie dotknięcia współrzędnych ekranu. Używają głównej osi ruchu kierunkowego, aby mapować przesunięcia poziome na nawigację pionową do następnego lub poprzedniego elementu.
  • Obsługa testowania została rozszerzona poza dotyk o nowe metody, takie jak 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.

Gemini Notebook

Tematy

  • jetpack-compose
  • pointer-input
  • trackpady
  • gesty
  • inteligentne okulary
  • ułatwienia dostępu

Transkrypcja

Pobierz transkrypcję

Wprowadzenie i filozofia wprowadzania danych

  • 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.

6 faz obsługi danych wejściowych ze 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.

Aktualizacje trackpada i gesty z użyciem wielu palców

  • 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.

Wprowadzenie do wprowadzania pośredniego

  • 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.

Projektowanie interfejsu API pod kątem pośredniego wprowadzania danych i nowych formatów

  • 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.

Testowanie pośredniego wprowadzania danych za pomocą wskaźnika

  • 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?

Podsumowanie i najważniejsze informacje

  • 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.

Kod

Fragmenty kodu

Fragment kodu w Kotlinie w 15:47 w filmie „Aktualizacje trackpada i gesty z użyciem wielu palców”
onNodeWithContentDescription("Photo")
    .performTrackpadInput {
        click(center)

        pan(Offset(0f, 100f))

        scale(0.9f)
    }
Fragment kodu w języku Kotlin w 25:08 w filmie „Projektowanie interfejsu API na potrzeby wprowadzania pośredniego i nowych formatów”
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()
Fragment kodu w języku Kotlin w 25:32 w filmie „Projektowanie interfejsu API na potrzeby wprowadzania pośredniego i nowych formatów”
// Glimmer Library
Box(
    modifier =
        Modifier.fillMaxSize()
            .onIndirectPointerGesture(
                enabled = true,
                onSwipeForward = { /* onSwipeForward */ },
                onSwipeBackward = { /* onSwipeBackward */ },
                onClick = { /* onClick */ },
            )
            .focusTarget()
) {
    // App()
Fragment kodu w języku Kotlin w 26:11 w filmie „Testowanie pośrednich danych wejściowych ze wskaźnika”
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }

}
Fragment kodu w Kotlinie w 27:08 w filmie „Testowanie pośredniego wprowadzania danych za pomocą wskaźnika”
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}
Fragment kodu w języku Kotlin w 27:23 w filmie „Testowanie pośredniego wprowadzania danych za pomocą wskaźnika”
@Test
clickTest {

rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }

}