1. Zanim zaczniesz
Te warsztaty są częścią kursu Zaawansowany Android w Kotlinie. Najwięcej korzyści przyniesie Ci ukończenie wszystkich ćwiczeń w kolejności, ale nie jest to obowiązkowe. Wszystkie ćwiczenia w Codelabs są wymienione na stronie docelowej ćwiczeń w Codelabs dotyczących zaawansowanego Androida w Kotlinie.
MotionLayout to biblioteka, która umożliwia dodawanie do aplikacji na Androida zaawansowanych animacji. Jest oparta na ConstraintLayout, i pozwala animować wszystko, co można utworzyć za pomocą ConstraintLayout.
Możesz używać MotionLayout do animowania lokalizacji, rozmiaru, widoczności, wartości alfa, koloru, wysokości, rotacji i innych atrybutów wielu widoków jednocześnie. Za pomocą deklaratywnego kodu XML możesz tworzyć skoordynowane animacje obejmujące wiele widoków, które trudno uzyskać w kodzie.
Animacje to świetny sposób na zwiększenie wygody korzystania z aplikacji. Animacje umożliwiają:
- Wyświetlanie zmian – animacja przejść między stanami pozwala użytkownikowi naturalnie śledzić zmiany w interfejsie.
- Przyciąganie uwagi – używaj animacji, aby przyciągać uwagę do ważnych elementów interfejsu.
- Twórz atrakcyjne projekty – efektywny ruch w projekcie sprawia, że aplikacje wyglądają profesjonalnie.
Wymagania wstępne
Te ćwiczenia z programowania są przeznaczone dla programistów z doświadczeniem w tworzeniu aplikacji na Androida. Zanim zaczniesz to ćwiczenie, musisz:
- Dowiedz się, jak utworzyć aplikację z aktywnością i podstawowym układem oraz uruchomić ją na urządzeniu lub w emulatorze za pomocą Android Studio. Zapoznaj się z
ConstraintLayout. Więcej informacji oConstraintLayoutznajdziesz w samouczku Constraint Layout.
Co musisz zrobić
- Zdefiniuj animację za pomocą
ConstraintSetsiMotionLayout - Animowanie na podstawie zdarzeń przeciągania
- Zmień animację za pomocą
KeyPosition - Zmiana atrybutów za pomocą
KeyAttribute - Uruchamianie animacji za pomocą kodu
- Animowanie zwijanych nagłówków za pomocą
MotionLayout
Czego potrzebujesz
- Android Studio 4.0 (edytor
MotionLayoutdziała tylko w tej wersji Android Studio).
2. Pierwsze kroki
Aby pobrać przykładową aplikację, możesz:
… lub sklonuj repozytorium GitHub z wiersza poleceń, używając tego polecenia:
$ git clone https://github.com/googlecodelabs/motionlayout.git
3. Tworzenie animacji za pomocą MotionLayout
Najpierw utworzysz animację, która w odpowiedzi na kliknięcia użytkownika przesuwa widok od górnego początku ekranu do dolnego końca.
Aby utworzyć animację z kodu startowego, potrzebujesz tych elementów:
MotionLayout,, która jest podklasąConstraintLayout. Wszystkie widoki, które mają być animowane, określasz w taguMotionLayout.MotionScene,, czyli plik XML opisujący animację dlaMotionLayout..Transition,, która jest częściąMotionScenei określa czas trwania animacji, wyzwalacz i sposób przesuwania widoków.ConstraintSet, która określa zarówno początkowe, jak i końcowe ograniczenia przejścia.
Przyjrzyjmy się każdemu z nich po kolei, zaczynając od MotionLayout.
Krok 1. Zapoznaj się z istniejącym kodem
MotionLayout jest podklasą ConstraintLayout, więc obsługuje wszystkie te same funkcje, a dodatkowo animację. Aby używać funkcji MotionLayout, dodaj widok MotionLayout w miejscu, w którym używasz funkcji ConstraintLayout..
- W
res/layoutotwórzactivity_step1.xml.. Znajduje się tuConstraintLayoutz jednąImageViewgwiazdą, w której zastosowano odcień.
activity_step1.xml
<!-- initial code -->
<androidx.constraintlayout.widget.ConstraintLayout
...
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/red_star"
...
/>
</androidx.constraintlayout.motion.widget.MotionLayout>
Ten element ConstraintLayout nie ma żadnych ograniczeń, więc jeśli uruchomisz teraz aplikację, zobaczysz gwiazdę bez ograniczeń, co oznacza, że będzie ona umieszczona w nieznanym miejscu. Android Studio wyświetli ostrzeżenie o braku ograniczeń.
Krok 2. Przekonwertuj na MotionLayout
Aby animować za pomocą MotionLayout,, musisz przekształcić ConstraintLayout w MotionLayout.
Aby układ korzystał ze sceny ruchu, musi na nią wskazywać.
- Aby to zrobić, otwórz obszar projektowania. W Androidzie Studio 4.0 możesz otworzyć obszar projektowania, klikając ikonę podziału lub projektu w prawym górnym rogu podczas przeglądania pliku XML układu.

- Po otwarciu obszaru projektowania kliknij prawym przyciskiem myszy podgląd i wybierz Convert to MotionLayout (Przekonwertuj na MotionLayout).

Spowoduje to zastąpienie tagu ConstraintLayout tagiem MotionLayout i dodanie do tagu MotionLayout atrybutu motion:layoutDescription, który wskazuje @xml/activity_step1_scene..
activity_step1**.xml**
<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:layoutDescription="@xml/activity_step1_scene">
Scena ruchu to pojedynczy plik XML, który opisuje animację w MotionLayout.
Gdy tylko przekonwertujesz na MotionLayout, na obszarze projektowania pojawi się Edytor ruchu.

W Motion Editorze są 3 nowe elementy interfejsu:
- Przegląd – to okno wyboru, które umożliwia wybieranie różnych części animacji. Na tym obrazie wybrano
startConstraintSet. Możesz też wybrać przejście międzystartaend, klikając strzałkę między nimi. - Sekcja – pod omówieniem znajduje się okno sekcji, które zmienia się w zależności od aktualnie wybranego elementu omówienia. Na tym obrazie w oknie wyboru wyświetlają się informacje
startConstraintSet. - Atrybut – panel atrybutów wyświetla atrybuty aktualnie wybranego elementu z okna przeglądu lub wyboru i umożliwia ich edytowanie. Na tym obrazie widać atrybuty dla
startConstraintSet.
Krok 3. Określ ograniczenia dotyczące rozpoczęcia i zakończenia
Wszystkie animacje można zdefiniować za pomocą początku i końca. Początek opisuje, jak wygląda ekran przed animacją, a koniec – jak wygląda po jej zakończeniu. MotionLayout odpowiada za określenie, jak animować przejście między stanem początkowym a końcowym (w czasie).
MotionScene używa tagu ConstraintSet do określania stanów początkowego i końcowego. ConstraintSet to zestaw ograniczeń, które można zastosować do widoków. Obejmuje to ograniczenia dotyczące szerokości, wysokości i ConstraintLayout. Zawiera też niektóre atrybuty, np. alpha. Nie zawiera on samych widoków, tylko ograniczenia dotyczące tych widoków.
Wszelkie ograniczenia określone w pliku ConstraintSet zastąpią ograniczenia określone w pliku układu. Jeśli zdefiniujesz ograniczenia zarówno w układzie, jak i w MotionScene, zastosowane zostaną tylko ograniczenia w MotionScene.
W tym kroku ograniczysz widok gwiazdy tak, aby zaczynał się u góry ekranu, a kończył u dołu.
Możesz to zrobić w edytorze ruchu lub bezpośrednio edytując tekst activity_step1_scene.xml.
- W panelu przeglądu wybierz
startConstraintSet.

- W panelu wyboru kliknij
red_star. Obecnie wyświetla się Źródłolayout– oznacza to, że w tymConstraintSetnie ma ograniczeń. W prawym górnym rogu kliknij ikonę ołówka, aby utworzyć ograniczenie.

- Sprawdź, czy po wybraniu w panelu przeglądu opcji
startConstraintSetw sekcjired_starwyświetla się Źródłostart. - W panelu Atrybuty, gdy w sekcji
startConstraintSetjest zaznaczony elementred_star, dodaj u góry ograniczenie i zacznij od kliknięcia niebieskich przycisków +.

- Otwórz plik
xml/activity_step1_scene.xml, aby zobaczyć kod wygenerowany przez Edytor ruchu dla tego ograniczenia.
activity_step1_scene.xml
<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
Element ConstraintSet ma wartość id równą @id/start i określa wszystkie ograniczenia, które mają być stosowane do wszystkich widoków w elemencie MotionLayout. Ta MotionLayout ma tylko jeden widok, więc potrzebuje tylko jednego Constraint.
Symbol Constraint w ConstraintSet określa identyfikator widoku, do którego jest ograniczony, @id/red_star zdefiniowany w activity_step1.xml. Warto pamiętać, że tagi Constraint określają tylko ograniczenia i informacje o układzie. Tag Constraint nie wie, że jest stosowany do ImageView.
To ograniczenie określa wysokość, szerokość i 2 inne ograniczenia potrzebne do ograniczenia widoku red_star do górnego początku elementu nadrzędnego.
- W panelu przeglądu wybierz
endConstraintSet.

- Wykonaj te same czynności co wcześniej, aby dodać
Constraintdlared_starwendConstraintSet. - Aby wykonać ten krok za pomocą Edytora ruchu, dodaj ograniczenie do elementów
bottomiend, klikając niebieskie przyciski +.

- Kod w XML wygląda tak:
activitiy_step1_scene.xml
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
Podobnie jak w przypadku @id/start, ten ConstraintSet ma pojedynczy Constraint na @id/red_star. Tym razem jest on ograniczony do dolnej części ekranu.
Nie musisz nazywać ich @id/start i @id/end, ale jest to wygodne.
Krok 4. Określ przejście
Każdy element MotionScene musi też zawierać co najmniej 1 przejście. Przejście definiuje każdy element animacji od początku do końca.
Przejście musi określać początkowy i końcowy ConstraintSet. Przejście może też określać inne sposoby modyfikowania animacji, np. czas jej trwania lub sposób animowania przez przeciąganie widoków.
- Motion Editor utworzył dla nas domyślnie przejście podczas tworzenia pliku MotionScene. Otwórz plik
activity_step1_scene.xml, aby zobaczyć wygenerowane przejście.
activity_step1_scene.xml
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<KeyFrameSet>
</KeyFrameSet>
</Transition>
To wszystko, czego MotionLayout potrzebuje do utworzenia animacji. Analiza poszczególnych atrybutów:
constraintSetStartzostanie zastosowany do wyświetleń po rozpoczęciu animacji.constraintSetEndzostanie zastosowane do wyświetleń na końcu animacji.durationokreśla, jak długo ma trwać animacja (w milisekundach).
MotionLayout wyznaczy ścieżkę między ograniczeniami początkowymi i końcowymi i animuje ją przez określony czas.
Krok 5. Wyświetl podgląd animacji w Motion Editorze

Animacja: film przedstawiający podgląd przejścia w Motion Editor
- Otwórz Motion Editor i wybierz przejście, klikając strzałkę między ikonami
startiendw panelu podglądu.

- Gdy przejście jest zaznaczone, w panelu wyboru wyświetlają się elementy sterujące odtwarzaniem i pasek przewijania. Aby wyświetlić podgląd animacji, kliknij przycisk odtwarzania lub przeciągnij bieżącą pozycję.

Krok 6. Dodaj obsługę kliknięcia
Musisz mieć sposób na rozpoczęcie animacji. Możesz to zrobić, sprawiając, że element MotionLayout będzie reagować na zdarzenia kliknięcia elementu @id/red_star.
- Otwórz edytor ruchu i wybierz przejście, klikając strzałkę między początkiem a końcem w panelu przeglądu.

- Na pasku narzędzi panelu przeglądu kliknij
Utwórz obsługę kliknięcia lub przesunięcia . Spowoduje to dodanie modułu obsługi, który rozpocznie przejście. - W menu wybierz Click Handler (Obsługa kliknięć).

- Zmień Widok do kliknięcia na
red_star.

- Kliknij Dodaj. Obsługa kliknięcia jest reprezentowana przez małą kropkę w przejściu w Motion Editor.

- Gdy w panelu przeglądu wybrane jest przejście, dodaj atrybut
clickActiono wartościtoggledo obsługi zdarzenia OnClick, którą właśnie dodano w panelu atrybutów.

- Otwórz
activity_step1_scene.xml, aby zobaczyć kod wygenerowany przez Motion Editor.
activity_step1_scene.xml
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
<OnClick
motion:targetId="@id/red_star"
motion:clickAction="toggle" />
</Transition>
Symbol Transition informuje MotionLayout, że animacja ma być uruchamiana w odpowiedzi na zdarzenia kliknięcia za pomocą tagu <OnClick>. Analiza poszczególnych atrybutów:
targetIdto widok, w którym możesz śledzić kliknięcia.clickActionztogglebędzie przełączać się między stanem początkowym a końcowym po kliknięciu. Więcej opcjiclickActionznajdziesz w dokumentacji.
- Uruchom kod, kliknij Krok 1, a potem kliknij czerwoną gwiazdkę i zobacz animację.
Krok 5. Animacje w praktyce
Uruchom aplikację. Po kliknięciu gwiazdki powinna się uruchomić animacja.

Ukończony plik sceny ruchu definiuje jeden element Transition, który wskazuje początek i koniec elementu ConstraintSet.
Na początku animacji (@id/start) ikona gwiazdy jest przyciągana do górnej krawędzi ekranu. Na końcu animacji (@id/end) ikona gwiazdy jest przyciągana do dolnej krawędzi ekranu.
<?xml version="1.0" encoding="utf-8"?>
<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
<OnClick
motion:targetId="@id/red_star"
motion:clickAction="toggle" />
</Transition>
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
</MotionScene>
4. Animowanie na podstawie zdarzeń przeciągania
W tym kroku utworzysz animację, która będzie reagować na zdarzenie przeciągnięcia przez użytkownika (gdy użytkownik przesunie palcem po ekranie). MotionLayout obsługuje śledzenie zdarzeń dotknięcia w celu przesuwania widoków, a także gesty przeciągnięcia oparte na fizyce, które zapewniają płynność ruchu.
Krok 1. Sprawdź początkowy kod
- Aby rozpocząć, otwórz plik układu
activity_step2.xml, który zawiera istniejący elementMotionLayout. Sprawdź kod.
activity_step2.xml
<!-- initial code -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:layoutDescription="@xml/step2" >
<ImageView
android:id="@+id/left_star"
...
/>
<ImageView
android:id="@+id/right_star"
...
/>
<ImageView
android:id="@+id/red_star"
...
/>
<TextView
android:id="@+id/credits"
...
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
Ten układ określa wszystkie widoki animacji. Trzy ikony gwiazdek nie są ograniczone w układzie, ponieważ będą animowane w scenie ruchu.
Napisy TextView mają zastosowane ograniczenia, ponieważ pozostają w tym samym miejscu przez całą animację i nie modyfikują żadnych atrybutów.
Krok 2. Animowanie sceny
Podobnie jak w przypadku ostatniej animacji, animacja będzie zdefiniowana przez początek i koniec ConstraintSet, oraz Transition.
Określ początkowy ConstraintSet.
- Otwórz scenę ruchu
xml/step2.xml, aby zdefiniować animację. - Dodaj ograniczenia dla ograniczenia początkowego
start. Na początku wszystkie 3 gwiazdki są wyśrodkowane u dołu ekranu. Prawa i lewa gwiazda mają wartośćalpharówną0.0, co oznacza, że są całkowicie przezroczyste i ukryte.
step2.xml
<!-- TODO apply starting constraints -->
<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/left_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/right_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
W tym ConstraintSet określasz Constraint dla każdej gwiazdki. Każde ograniczenie zostanie zastosowane przez MotionLayout na początku animacji.
Każdy widok gwiazdy jest wyśrodkowany u dołu ekranu za pomocą ograniczeń początku, końca i dołu. Obie gwiazdki @id/left_star i @id/right_star mają dodatkową wartość alfa, która sprawia, że są niewidoczne i będzie stosowana na początku animacji.
Zbiory ograniczeń start i end określają początek i koniec animacji. Ograniczenie początku, np. motion:layout_constraintStart_toStartOf, spowoduje, że początek widoku będzie ograniczony do początku innego widoku. Może to być początkowo mylące, ponieważ nazwa start jest używana zarówno w przypadku , jak i, a oba te pojęcia są używane w kontekście ograniczeń. Aby ułatwić rozróżnienie, symbol start w layout_constraintStart odnosi się do „początku” widoku, czyli lewej strony w językach pisanych od lewej do prawej i prawej strony w językach pisanych od prawej do lewej. Zestaw ograniczeń start odnosi się do początku animacji.
Określ ConstraintSet końcowy
- Zdefiniuj ograniczenie końcowe, aby użyć łańcucha do umieszczenia wszystkich 3 gwiazdek razem pod
@id/credits. Dodatkowo ustawi wartość końcowąalphalewej i prawej gwiazdki na1.0.
step2.xml
<!-- TODO apply ending constraints -->
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/left_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="1.0"
motion:layout_constraintHorizontal_chainStyle="packed"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toStartOf="@id/red_star"
motion:layout_constraintTop_toBottomOf="@id/credits" />
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toEndOf="@id/left_star"
motion:layout_constraintEnd_toStartOf="@id/right_star"
motion:layout_constraintTop_toBottomOf="@id/credits" />
<Constraint
android:id="@+id/right_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="1.0"
motion:layout_constraintStart_toEndOf="@id/red_star"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>
W efekcie wyświetlenia będą się rozprzestrzeniać od środka w górę i na zewnątrz podczas animacji.
Dodatkowo, ponieważ właściwość alpha jest ustawiona na @id/right_start i @id/left_star w obu ConstraintSets, oba widoki będą się pojawiać stopniowo w miarę postępu animacji.
Animowanie na podstawie przesunięcia użytkownika
MotionLayout może śledzić zdarzenia przeciągania lub przesunięcia, aby utworzyć animację „rzucania” opartą na fizyce. Oznacza to, że widoki będą się przesuwać, jeśli użytkownik je przesunie, i zwalniać jak fizyczny obiekt toczący się po powierzchni. Ten typ animacji możesz dodać za pomocą tagu OnSwipe w Transition.
- Zastąp komentarz TODO dotyczący dodawania tagu
OnSwipekodem<OnSwipe motion:touchAnchorId="@id/red_star" />.
step2.xml
<!-- TODO add OnSwipe tag -->
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end">
<!-- MotionLayout will track swipes relative to this view -->
<OnSwipe motion:touchAnchorId="@id/red_star" />
</Transition>
OnSwipe zawiera kilka atrybutów, z których najważniejszy jest touchAnchorId.
touchAnchorIdto śledzony widok, który przesuwa się w odpowiedzi na dotyk.MotionLayoutzachowa tę samą odległość od palca, którym przesuwasz.touchAnchorSideokreśla, która strona widoku powinna być śledzona. Jest to ważne w przypadku widoków, które zmieniają rozmiar, poruszają się po złożonych ścieżkach lub mają jedną stronę, która porusza się szybciej niż druga.dragDirectionokreśla, który kierunek ma znaczenie w przypadku tej animacji (w górę, w dół, w lewo lub w prawo).
Gdy MotionLayout nasłuchuje zdarzeń przeciągnięcia, detektor zostanie zarejestrowany w widoku MotionLayout, a nie w widoku określonym przez touchAnchorId. Gdy użytkownik rozpocznie gest w dowolnym miejscu na ekranie, MotionLayout zachowa stałą odległość między palcem a touchAnchorSide widoku touchAnchorId. Jeśli na przykład dotkną ekranu w odległości 100 dp od strony kotwicy, element MotionLayout zachowa tę odległość przez cały czas trwania animacji.
Wypróbuj
- Uruchom ponownie aplikację i otwórz ekran Krok 2. Zobaczysz animację.
- Spróbuj „rzucić” lub puścić palec w połowie animacji, aby zobaczyć, jak
MotionLayoutwyświetla animacje oparte na fizyce płynów.

MotionLayout może animować przejścia między bardzo różnymi projektami, korzystając z funkcji ConstraintLayout, aby tworzyć bogate efekty.
W tej animacji wszystkie 3 widoki są początkowo umieszczone względem elementu nadrzędnego u dołu ekranu. Na końcu 3 widoki są ustawione względem @id/credits w łańcuchu.
Mimo tych bardzo różnych układów MotionLayout utworzy płynną animację między początkiem a końcem.
5. Modyfikowanie ścieżki
W tym kroku utworzysz animację, która będzie się poruszać po złożonej ścieżce i animować napisy podczas ruchu. MotionLayout może modyfikować ścieżkę, którą widok będzie pokonywać między początkiem a końcem, za pomocą KeyPosition.
Krok 1. Zapoznaj się z dotychczasowym kodem
- Otwórz
layout/activity_step3.xmlixml/step3.xml, aby zobaczyć istniejący układ i scenę ruchu.ImageViewiTextViewwyświetlają księżyc i tekst z informacjami o autorach. - Otwórz plik sceny ruchomej (
xml/step3.xml). Zobaczysz, że zdefiniowanoTransitionod@id/startdo@id/end. Animacja przesuwa obraz księżyca z lewego dolnego rogu ekranu do prawego dolnego rogu za pomocą 2ConstraintSets. Tekst napisów początkowych pojawia się stopniowo, odalpha="0.0"doalpha="1.0", w miarę przesuwania się księżyca. - Uruchom teraz aplikację i kliknij Krok 3. Gdy klikniesz księżyc, zobaczysz, że porusza się on po linii prostej od początku do końca.
Krok 2. Włącz debugowanie ścieżki
Zanim dodasz łuk do ruchu księżyca, warto włączyć debugowanie ścieżki w MotionLayout.
Aby ułatwić tworzenie złożonych animacji za pomocą MotionLayout, możesz narysować ścieżkę animacji każdego widoku. Jest to przydatne, gdy chcesz wizualizować animację i dopracowywać drobne szczegóły ruchu.
- Aby włączyć ścieżki debugowania, otwórz
layout/activity_step3.xmli dodajmotion:motionDebug="SHOW_PATH"do taguMotionLayout.
activity_step3.xml
<!-- Add motion:motionDebug="SHOW_PATH" -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:motionDebug="SHOW_PATH" >
Po włączeniu debugowania ścieżki i ponownym uruchomieniu aplikacji zobaczysz ścieżki wszystkich widoków wizualizowane za pomocą linii kropkowanej.

- Kółka oznaczają początek lub koniec jednego widoku.
- Linie reprezentują ścieżkę jednego widoku.
- Diamenty reprezentują
KeyPosition, które modyfikują ścieżkę.
Na przykład w tej animacji środkowe kółko to pozycja tekstu z napisami.
Krok 3. Zmodyfikuj ścieżkę
Wszystkie animacje w MotionLayout są zdefiniowane przez początek i koniec ConstraintSet, które określają, jak wygląda ekran przed rozpoczęciem animacji i po jej zakończeniu. Domyślnie funkcja MotionLayout wyznacza liniową ścieżkę (linię prostą) między pozycją początkową i końcową każdego widoku, którego położenie się zmienia.
Aby utworzyć złożone ścieżki, takie jak łuk księżyca w tym przykładzie, MotionLayout używa KeyPosition, aby zmodyfikować ścieżkę, którą widok pokonuje między początkiem a końcem.
- Otwórz
xml/step3.xmli dodajKeyPositiondo sceny. TagKeyPositionjest umieszczony w taguTransition.

step3.xml
<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
KeyFrameSet jest elementem podrzędnym Transition i jest zbiorem wszystkich KeyFrames, takich jak KeyPosition, które powinny być stosowane podczas przejścia.
Gdy MotionLayout oblicza ścieżkę księżyca między początkiem a końcem, modyfikuje ją na podstawie KeyPosition określonego w KeyFrameSet. Możesz sprawdzić, jak to wpłynie na ścieżkę, ponownie uruchamiając aplikację.
KeyPosition ma kilka atrybutów, które opisują, jak modyfikuje ścieżkę. Najważniejsze z nich to:
framePositionto liczba z zakresu od 0 do 100. Określa, kiedy w animacji należy zastosować tenKeyPosition. Wartość 1 oznacza 1% animacji, a 99 – 99% animacji. Jeśli wartość wynosi 50, zastosuj ją dokładnie pośrodku.motionTargetto widok, w którymKeyPositionmodyfikuje ścieżkę.keyPositionTypeto sposób, w jakiKeyPositionmodyfikuje ścieżkę. Może to byćparentRelative,pathRelativelubdeltaRelative(jak wyjaśniono w następnym kroku).percentX | percentYto wartość, o którą należy zmodyfikować ścieżkę w punkcieframePosition(wartości od 0,0 do 1,0, przy czym dopuszczalne są wartości ujemne i większe niż 1).
Możesz to sobie wyobrazić tak: „W framePosition zmodyfikuj ścieżkę motionTarget przez przesunięcie jej o percentX lub percentY zgodnie ze współrzędnymi określonymi przez keyPositionType”.
Domyślnie MotionLayout zaokrągla wszystkie rogi powstałe w wyniku modyfikacji ścieżki. Jeśli przyjrzysz się utworzonej animacji, zobaczysz, że w miejscu zakrętu księżyc porusza się po zakrzywionej ścieżce. W przypadku większości animacji jest to pożądane zachowanie. Jeśli nie, możesz dostosować je za pomocą atrybutu curveFit.
Wypróbuj
Jeśli ponownie uruchomisz aplikację, zobaczysz animację tego kroku.

Księżyc porusza się po łuku, ponieważ przechodzi przez KeyPosition określony w Transition.
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
Możesz to KeyPosition odczytać jako: „W momencie framePosition 50 (w połowie animacji) zmień ścieżkę motionTarget @id/moon, przesuwając ją o 50% Y (w połowie ekranu) zgodnie ze współrzędnymi określonymi przez parentRelative (cały MotionLayout)”.
W połowie animacji księżyc musi znajdować się w KeyPosition, które jest o 50% niżej na ekranie. Ten KeyPosition nie modyfikuje ruchu w osi X, więc księżyc nadal będzie przesuwać się poziomo od początku do końca. MotionLayout wyznaczy płynną ścieżkę, która przechodzi przez ten KeyPosition podczas przemieszczania się między punktem początkowym a końcowym.
Jeśli przyjrzysz się uważnie, zobaczysz, że tekst napisów jest ograniczony przez położenie księżyca. Dlaczego nie porusza się też w pionie?

<Constraint
android:id="@id/credits"
...
motion:layout_constraintBottom_toBottomOf="@id/moon"
motion:layout_constraintTop_toTopOf="@id/moon"
/>
Okazuje się, że mimo modyfikacji ścieżki, po której porusza się księżyc, jego początkowa i końcowa pozycja nie zmieniają się w pionie. Symbol KeyPosition nie modyfikuje pozycji początkowej ani końcowej, więc tekst napisów jest ograniczony do końcowej pozycji księżyca.
Jeśli chcesz, aby napisy przesuwały się wraz z księżycem, możesz dodać do nich symbol KeyPosition lub zmodyfikować ograniczenia początku w przypadku symbolu @id/credits.
W następnej sekcji dowiesz się więcej o różnych typach keyPositionType w MotionLayout.
6. Informacje o parametrze keyPositionType
W ostatnim kroku użyto typu keyPosition parentRelative, aby przesunąć ścieżkę o 50% ekranu. Atrybut keyPositionType określa sposób, w jaki MotionLayout będzie modyfikować ścieżkę zgodnie z wartościami percentX lub percentY.
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
Dostępne są 3 rodzaje keyPosition: parentRelative, pathRelative i deltaRelative. Określenie typu spowoduje zmianę układu współrzędnych, według którego obliczane są wartości percentX i percentY.
Co to jest układ współrzędnych?
Układ współrzędnych umożliwia określenie punktu w przestrzeni. Przydają się też do opisywania pozycji na ekranie.
MotionLayout systemy współrzędnych to kartezjański układ współrzędnych. Oznacza to, że mają one oś X i oś Y zdefiniowane przez 2 prostopadłe linie. Główna różnica między nimi polega na tym, gdzie na ekranie przebiega oś X (oś Y jest zawsze prostopadła do osi X).
Wszystkie układy współrzędnych w MotionLayout używają wartości z zakresu od 0.0 do 1.0 na osiach X i Y. Dozwolone są wartości ujemne i większe niż 1.0. Na przykład wartość percentX równa -2.0 oznacza, że należy dwukrotnie przesunąć się w przeciwnym kierunku osi X.
Jeśli to wszystko brzmi trochę jak lekcja algebry, spójrz na zdjęcia poniżej.
parentRelative coordinates

keyPositionType parentRelative korzysta z tego samego układu współrzędnych co ekran. Określa (0, 0) w lewym górnym rogu całego MotionLayout, a (1, 1) w prawym dolnym rogu.
Możesz używać parentRelative, gdy chcesz utworzyć animację, która przesuwa się po całym MotionLayout, np. łuk księżyca w tym przykładzie.
Jeśli jednak chcesz zmodyfikować ścieżkę względem ruchu, np. nieco ją zakrzywić, lepszym wyborem będą pozostałe 2 układy współrzędnych.
współrzędne deltaRelative

Delta to termin matematyczny oznaczający zmianę, więc deltaRelative oznacza „zmianę względną”. Współrzędne deltaRelative to pozycja początkowa widoku, a (1,1) to pozycja końcowa.(0,0) Osie X i Y są wyrównane do ekranu.
Oś X jest zawsze pozioma na ekranie, a oś Y jest zawsze pionowa na ekranie. W porównaniu z parentRelative główna różnica polega na tym, że współrzędne opisują tylko tę część ekranu, w której będzie się przesuwać widok.
deltaRelative to świetny układ współrzędnych do kontrolowania ruchu w poziomie lub w pionie w izolacji. Możesz na przykład utworzyć animację, która w 50% zakończy ruch w pionie (oś Y) i będzie kontynuować animację w poziomie (oś X).
pathRelative coordinates

Ostatni układ współrzędnych w MotionLayout to pathRelative. Różni się on od pozostałych dwóch, ponieważ oś X podąża za ścieżką animacji od początku do końca. (0,0) to pozycja początkowa, a (1,0) to pozycja końcowa.
Dlaczego warto to zrobić? Na pierwszy rzut oka jest to dość zaskakujące, zwłaszcza że ten układ współrzędnych nie jest nawet dopasowany do układu współrzędnych ekranu.
Okazuje się, że pathRelative jest przydatny w kilku sytuacjach.
- Przyspieszanie, spowalnianie lub zatrzymywanie widoku w trakcie animacji. Wymiar X zawsze będzie dokładnie odpowiadać ścieżce, którą pokonuje widok, więc możesz użyć
pathRelativeKeyPosition, aby zmienićframePosition, w którym osiągany jest dany punkt na tej ścieżce. WartośćKeyPositionprzyframePosition="50"z wartościąpercentX="0.1"spowoduje, że animacja będzie potrzebować 50% czasu na pokonanie pierwszych 10% ruchu. - Dodawanie do ścieżki subtelnego łuku. Wymiar Y jest zawsze prostopadły do ruchu, więc jego zmiana spowoduje zmianę ścieżki do krzywej względem ogólnego ruchu.
- Dodanie drugiego wymiaru, gdy
deltaRelativenie będzie działać. W przypadku ruchu całkowicie poziomego lub pionowego funkcjadeltaRelativeutworzy tylko jeden przydatny wymiar. JednakpathRelativezawsze utworzy współrzędne X i Y, które można wykorzystać.
W następnym kroku dowiesz się, jak tworzyć jeszcze bardziej złożone ścieżki przy użyciu więcej niż jednego symbolu KeyPosition.
7. Tworzenie złożonych ścieżek
Animacja utworzona w ostatnim kroku ma płynną krzywą, ale kształt może być bardziej „księżycowy”.
Modyfikowanie ścieżki z wieloma elementami KeyPosition
MotionLayout może dalej modyfikować ścieżkę, definiując dowolną liczbę KeyPosition, aby uzyskać dowolny ruch. W tej animacji utworzysz łuk, ale jeśli chcesz, możesz sprawić, że księżyc będzie skakać w górę i w dół na środku ekranu.
- Otwórz pokój
xml/step4.xml. Widzisz, że ma taką samą liczbę wyświetleń iKeyFramedodane w ostatnim kroku. - Aby zaokrąglić górną część krzywej, dodaj jeszcze 2 punkty
KeyPositionsdo ścieżki@id/moon– jeden tuż przed osiągnięciem szczytu, a drugi po nim.

step4.xml
<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
motion:framePosition="25"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
/>
<KeyPosition
motion:framePosition="75"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
/>
Te KeyPositions zostaną zastosowane w 25% i 75% animacji, a @id/moon będzie się poruszać po ścieżce, która znajduje się 60% od góry ekranu. W połączeniu z dotychczasowym KeyPosition na poziomie 50% tworzy to płynny łuk, po którym porusza się księżyc.
W MotionLayout możesz dodać dowolną liczbę KeyPositions, aby uzyskać odpowiednią ścieżkę animacji. MotionLayout zastosuje każdy KeyPosition w określonym framePosition i ustali, jak utworzyć płynny ruch przechodzący przez wszystkie KeyPositions.
Wypróbuj
- Uruchom aplikację ponownie. Przejdź do kroku 4, aby zobaczyć animację w działaniu. Gdy klikniesz księżyc, będzie on podążać ścieżką od początku do końca, przechodząc przez każdy element
KeyPositionokreślony w elemencieKeyFrameSet.
Odkrywaj samodzielnie
Zanim przejdziesz do innych typów KeyFrame, spróbuj dodać więcej KeyPositions do KeyFrameSet, aby zobaczyć, jakie efekty możesz uzyskać, używając tylko KeyPosition.
Oto przykład pokazujący, jak utworzyć złożoną ścieżkę, która podczas animacji porusza się w przód i w tył.

step4.xml
<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
<KeyPosition
motion:framePosition="25"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
motion:percentX="0.1"
/>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
motion:percentX="0.3"
/>
<KeyPosition
motion:framePosition="75"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
motion:percentX="0.1"
/>
</KeyFrameSet>
Gdy skończysz, w następnym kroku przejdziesz do innych typów KeyFrames.KeyPosition
8. Zmiana atrybutów podczas ruchu
Tworzenie animacji dynamicznych często wiąże się ze zmianą size, rotation lub alpha widoków w trakcie animacji. MotionLayout obsługuje animowanie wielu atrybutów w dowolnym widoku za pomocą KeyAttribute.
W tym kroku użyjesz KeyAttribute, aby zmienić rozmiar i obracać księżyc. Użyjesz też KeyAttribute, aby opóźnić pojawienie się tekstu, aż księżyc prawie zakończy swoją podróż.
Krok 1. Zmień rozmiar i obróć za pomocą atrybutu KeyAttribute
- Otwórz plik
xml/step5.xml, który zawiera tę samą animację, którą utworzono w ostatnim kroku. Dla urozmaicenia na tym ekranie jako tło wykorzystano inne zdjęcie kosmosu. - Aby powiększyć i obrócić księżyc, dodaj 2 tagi
KeyAttributew taguKeyFrameSetw miejscachkeyFrame="50"ikeyFrame="100".

step5.xml
<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->
<KeyAttribute
motion:framePosition="50"
motion:motionTarget="@id/moon"
android:scaleY="2.0"
android:scaleX="2.0"
android:rotation="-360"
/>
<KeyAttribute
motion:framePosition="100"
motion:motionTarget="@id/moon"
android:rotation="-720"
/>
Te KeyAttributes są stosowane w 50% i 100% animacji. Pierwszy punkt KeyAttribute przy 50% pojawi się na szczycie łuku i spowoduje podwojenie rozmiaru widoku oraz obrócenie go o -360 stopni (czyli o pełne koło). Drugi KeyAttribute zakończy drugi obrót do -720 stopni (dwa pełne okręgi) i zmniejszy rozmiar do normalnego, ponieważ wartości scaleX i scaleY są domyślnie ustawione na 1,0.
Podobnie jak KeyPosition, KeyAttribute używa elementów framePosition i motionTarget, aby określić, kiedy zastosować KeyFrame i który widok zmodyfikować. MotionLayout będzie interpolować między KeyPositions, aby tworzyć płynne animacje.
KeyAttributes obsługuje atrybuty, które można zastosować do wszystkich widoków; Umożliwiają one zmianę podstawowych atrybutów, takich jak visibility, alpha lub elevation. Możesz też zmienić obrót, tak jak tutaj, obracać w 3 wymiarach za pomocą klawiszy rotateX i rotateY, skalować rozmiar za pomocą klawiszy scaleX i scaleY lub przesuwać widok w osiach X, Y lub Z.
Krok 2. Opóźnij wyświetlanie napisów
Jednym z celów tego kroku jest zaktualizowanie animacji, tak aby tekst z napisami końcowymi pojawiał się dopiero po zakończeniu większości animacji.
- Aby opóźnić pojawienie się napisów, zdefiniuj jeszcze jeden warunek
KeyAttribute, który zapewni, że wartośćalphapozostanie równa 0 do momentu, gdykeyPosition="85".MotionLayoutnadal będzie płynnie przechodzić od 0 do 100 alfa, ale będzie to robić w ostatnich 15% animacji.
step5.xml
<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->
<KeyAttribute
motion:framePosition="85"
motion:motionTarget="@id/credits"
android:alpha="0.0"
/>
Ten KeyAttribute utrzymuje alpha @id/credits na poziomie 0,0 przez pierwsze 85% animacji. Ponieważ zaczyna się od wartości alfa 0, oznacza to, że przez pierwsze 85% animacji będzie niewidoczny.
Efektem końcowym tego KeyAttribute jest to, że napisy pojawiają się pod koniec animacji. Daje to wrażenie, że są one skoordynowane z księżycem, który znajduje się w prawym rogu ekranu.
Opóźniając animacje w jednym widoku, gdy inny widok porusza się w ten sposób, możesz tworzyć imponujące animacje, które wydają się dynamiczne dla użytkownika.
Wypróbuj
- Uruchom ponownie aplikację i przejdź do kroku 5, aby zobaczyć animację w działaniu. Gdy klikniesz księżyc, będzie on podążać ścieżką od początku do końca, przechodząc przez każdy punkt
KeyAttributeokreślony wKeyFrameSet.

Ponieważ obracasz księżyc o 2 pełne okręgi, wykona on podwójne salto w tył, a napisy końcowe pojawią się dopiero pod koniec animacji.
Odkrywaj samodzielnie
Zanim przejdziesz do ostatniego typu KeyFrame, spróbuj zmodyfikować inne standardowe atrybuty w KeyAttributes. Spróbuj na przykład zmienić rotation na rotationX, aby zobaczyć, jaką animację to wywoła.
Oto lista atrybutów standardowych, których możesz użyć:
android:visibilityandroid:alphaandroid:elevationandroid:rotationandroid:rotationXandroid:rotationYandroid:scaleXandroid:scaleYandroid:translationXandroid:translationYandroid:translationZ
9. Zmiana atrybutów niestandardowych
Animacje zaawansowane polegają na zmianie koloru lub innych atrybutów widoku. Element MotionLayout może używać elementu KeyAttribute do zmiany dowolnego atrybutu standardowego wymienionego w poprzednim zadaniu, a element CustomAttribute służy do określania dowolnego innego atrybutu.
Za pomocą CustomAttribute można ustawić dowolną wartość, która ma funkcję ustawiającą. Możesz na przykład ustawić backgroundColor w obiekcie View za pomocą CustomAttribute. MotionLayout użyje odbicia, aby znaleźć funkcję ustawiającą, a następnie będzie ją wielokrotnie wywoływać, aby animować widok.
W tym kroku użyjesz CustomAttribute, aby ustawić atrybut colorFilter na księżycu i utworzyć animację pokazaną poniżej.

Definiowanie atrybutów niestandardowych
- Na początek otwórz
xml/step6.xml, który zawiera tę samą animację, którą utworzyliśmy w ostatnim kroku. - Aby księżyc zmieniał kolory, dodaj 2 znaki
KeyAttributez symbolemCustomAttributewKeyFrameSetw miejscachkeyFrame="0",keyFrame="50"ikeyFrame="100"..

step6.xml
<!-- TODO: Add Custom attributes here -->
<KeyAttribute
motion:framePosition="0"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFFFFF"
/>
</KeyAttribute>
<KeyAttribute
motion:framePosition="50"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFB612"
/>
</KeyAttribute>
<KeyAttribute
motion:framePosition="100"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFFFFF"
/>
</KeyAttribute>
Dodajesz CustomAttribute w KeyAttribute. CustomAttribute zostanie zastosowane w framePosition określonym przez KeyAttribute.
W obrębie znacznika CustomAttribute musisz określić attributeName i jedną wartość do ustawienia.
motion:attributeNameto nazwa funkcji ustawiającej, która będzie wywoływana przez ten atrybut niestandardowy. W tym przykładzie wywołana zostanie funkcjasetColorFilternaDrawable.motion:custom*Valueto wartość niestandardowa typu podanego w nazwie. W tym przykładzie jest to określony kolor.
Wartości niestandardowe mogą mieć jeden z tych typów:
- Kolor
- Liczba całkowita
- Liczba zmiennoprzecinkowa
- Ciąg znaków
- Wymiar
- Wartość logiczna
Za pomocą tego interfejsu API MotionLayout może animować wszystko, co udostępnia funkcję ustawiającą w dowolnym widoku.
Wypróbuj
- Uruchom ponownie aplikację i przejdź do kroku 6, aby zobaczyć animację w działaniu. Gdy klikniesz księżyc, będzie on podążać ścieżką od początku do końca, przechodząc przez każdy punkt
KeyAttributeokreślony wKeyFrameSet.

Gdy dodasz więcej KeyFrames, MotionLayout zmieni ścieżkę księżyca z linii prostej na złożoną krzywą, dodając podwójne salto w tył, zmianę rozmiaru i zmianę koloru w połowie animacji.
W prawdziwych animacjach często animujesz kilka widoków jednocześnie, kontrolując ich ruch po różnych ścieżkach i z różnymi prędkościami. Określając różne wartości KeyFrame dla każdego widoku, możesz tworzyć złożone animacje, które animują wiele widoków za pomocą MotionLayout.
10. Wydarzenia przeciągania i złożone ścieżki
W tym kroku dowiesz się, jak używać OnSwipe ze złożonymi ścieżkami. Do tej pory animacja księżyca była wywoływana przez odbiornik OnClick i trwała przez określony czas.
Sterowanie animacjami o skomplikowanych ścieżkach za pomocą OnSwipe, takimi jak animacja księżyca utworzona w kilku ostatnich krokach, wymaga zrozumienia, jak działa OnSwipe.
Krok 1. Poznaj działanie funkcji OnSwipe
- Otwórz
xml/step7.xmli znajdź istniejącą deklaracjęOnSwipe.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
/>
- Uruchom aplikację na urządzeniu i przejdź do kroku 7. Sprawdź, czy możesz uzyskać płynną animację, przeciągając księżyc wzdłuż łuku.
Gdy uruchomisz tę animację, nie będzie ona wyglądać zbyt dobrze. Gdy księżyc osiągnie szczyt łuku, zacznie skakać.

Aby zrozumieć ten błąd, zastanów się, co się stanie, gdy użytkownik dotknie ekranu tuż pod górną częścią łuku. Ponieważ tag OnSwipe ma wartość motion:touchAnchorSide="bottom", MotionLayout będzie próbować utrzymać stałą odległość między palcem a dołem widoku przez cały czas trwania animacji.
Jednak dolna część księżyca nie zawsze porusza się w tym samym kierunku – najpierw idzie w górę, a potem w dół. Dlatego MotionLayout nie wie, co zrobić, gdy użytkownik minie już szczyt łuku. Aby to uwzględnić, ponieważ śledzisz dolną część księżyca, gdzie powinna się ona znajdować, gdy użytkownik dotyka tego miejsca?

Krok 2. Używaj prawej strony
Aby uniknąć takich błędów, zawsze wybieraj touchAnchorId i touchAnchorSide, które przez cały czas trwania animacji zawsze zmieniają się w jednym kierunku.
W tej animacji zarówno strona right, jak i strona left Księżyca będą przesuwać się po ekranie w jednym kierunku.
Jednak zarówno bottom, jak i top zmienią kierunek. Gdy OnSwipe spróbuje je śledzić, zmiana kierunku spowoduje, że urządzenie się pogubi.
- Aby animacja reagowała na zdarzenia dotknięcia, zmień
touchAnchorSidenaright.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="right"
/>
Krok 3. Użyj dragDirection
Możesz też połączyć dragDirection z touchAnchorSide, aby zmienić kierunek, w którym zwykle porusza się boczny tor. Ważne jest, aby touchAnchorSide postępował tylko w jednym kierunku, ale możesz określić, w którym kierunku ma się poruszać.MotionLayout Możesz na przykład zachować touchAnchorSide="bottom", ale dodać dragDirection="dragRight". Spowoduje to śledzenie pozycji dolnej części widoku przez MotionLayout, ale będzie uwzględniać tylko jej położenie podczas przesuwania w prawo (ignoruje ruch pionowy). Dzięki temu, mimo że dolna część będzie się przesuwać w górę i w dół, animacja z OnSwipe będzie nadal działać prawidłowo.
- Aktualizacja
OnSwipe, aby prawidłowo śledzić ruch Księżyca.
step7.xml
<!-- Using dragDirection to control the direction of drag tracking →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
motion:dragDirection="dragRight"
/>
Wypróbuj
- Uruchom ponownie aplikację i spróbuj przeciągnąć księżyc po całej ścieżce. Mimo że ma złożony łuk,
MotionLayoutbędzie w stanie rozwijać animację w odpowiedzi na zdarzenia przesuwania.

11. Uruchamianie ruchu za pomocą kodu
MotionLayout w połączeniu z CoordinatorLayout umożliwia tworzenie zaawansowanych animacji. W tym kroku utworzysz zwijany nagłówek za pomocą elementu MotionLayout.
Krok 1. Zapoznaj się z dotychczasowym kodem
- Aby rozpocząć, otwórz
layout/activity_step8.xml. - W
layout/activity_step8.xmlwidać, że działająceCoordinatorLayoutiAppBarLayoutsą już utworzone.
activity_step8.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_layout"
android:layout_width="match_parent"
android:layout_height="180dp">
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motion_layout"
... >
...
</androidx.constraintlayout.motion.widget.MotionLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
...
motion:layout_behavior="@string/appbar_scrolling_view_behavior" >
...
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Ten układ wykorzystuje CoordinatorLayout do udostępniania informacji o przewijaniu między NestedScrollView a AppBarLayout. Gdy więc NestedScrollView przewinie się w górę, poinformuje o tym AppBarLayout. W ten sposób możesz zaimplementować na Androidzie zwijany pasek narzędzi, taki jak ten – przewijanie tekstu będzie „skoordynowane” ze zwijanym nagłówkiem.
Scena ruchu, na którą wskazuje @id/motion_layout, jest podobna do sceny ruchu z ostatniego kroku. Deklaracja OnSwipe została jednak usunięta, aby umożliwić jej działanie z CoordinatorLayout.
- Uruchom aplikację i przejdź do kroku 8. Gdy przewijasz tekst, księżyc się nie porusza.
Krok 2. Ustawianie przewijania w MotionLayout
- Aby widok
MotionLayoutprzewijał się od razu po przewinięciu widokuNestedScrollView, dodaj do widokuMotionLayoutelementymotion:minHeightimotion:layout_scrollFlags.
activity_step8.xml
<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motion_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
motion:layoutDescription="@xml/step8"
motion:motionDebug="SHOW_PATH"
android:minHeight="80dp"
motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed" >
- Uruchom ponownie aplikację i przejdź do kroku 8. Podczas przewijania w górę widzisz, że
MotionLayoutsię zwija. Animacja nie jest jeszcze jednak powiązana z przewijaniem.
Krok 3. Przenieś ruch za pomocą kodu
- Otwórz
Step8Activity.kt. Edytuj funkcjęcoordinateMotion(), aby informowaćMotionLayouto zmianach pozycji przewijania.
Step8Activity.kt
// TODO: set progress of MotionLayout based on an AppBarLayout.OnOffsetChangedListener
private fun coordinateMotion() {
val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
val motionLayout: MotionLayout = findViewById(R.id.motion_layout)
val listener = AppBarLayout.OnOffsetChangedListener { unused, verticalOffset ->
val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
motionLayout.progress = seekPosition
}
appBarLayout.addOnOffsetChangedListener(listener)
}
Ten kod zarejestruje OnOffsetChangedListener, który będzie wywoływany za każdym razem, gdy użytkownik przewinie stronę z bieżącym przesunięciem przewijania.
MotionLayout obsługuje wyszukiwanie przejścia przez ustawienie właściwości postępu. Aby przeliczyć verticalOffset na postęp w procentach, podziel go przez całkowity zakres przewijania.
Wypróbuj
- Ponownie wdróż aplikację i uruchom animację kroku 8. Widzisz, że
MotionLayoutprzesuwa animację na podstawie pozycji przewijania.

Za pomocą MotionLayout możesz tworzyć niestandardowe animacje dynamicznego zwijania paska narzędzi. Używając sekwencji KeyFrames, możesz uzyskać bardzo wyraziste efekty.
12. Gratulacje
W tym laboratorium kodowania omówiliśmy podstawowy interfejs API MotionLayout.
Więcej przykładów użycia MotionLayout znajdziesz w oficjalnym przykładzie. Zapoznaj się też z dokumentacją.
Więcej informacji
MotionLayout obsługuje jeszcze więcej funkcji, które nie zostały omówione w tym laboratorium, np. KeyCycle,, która umożliwia sterowanie ścieżkami lub atrybutami za pomocą powtarzających się cykli, oraz KeyTimeCycle,, która pozwala animować na podstawie czasu zegarowego. Przykłady znajdziesz w próbkach.
Linki do innych ćwiczeń w Codelabs w tym kursie znajdziesz na stronie docelowej ćwiczeń w Codelabs z zaawansowanego Androida w Kotlinie.