1. Zanim zaczniesz
To ćwiczenie w Codelabs jest częścią kursu na temat Androida w Kotlinie dla zaawansowanych. Aby w pełni wykorzystać ten kurs, ukończysz moduły z programowania w sekwencji, ale nie jest to obowiązkowe. Wszystkie kursy z programowania znajdziesz na stronie docelowej kursów z programowania dla zaawansowanych w Kotlin.
MotionLayout
to biblioteka, która pozwala dodać efektowne obrazy w aplikacji na Androida. Bazuje na ConstraintLayout,
i pozwala animować wszystko, co można zbudować za pomocą ConstraintLayout
.
Za pomocą MotionLayout
możesz animować lokalizację, rozmiar, widoczność, alfa, kolor, wysokość, obrót oraz inne atrybuty wielu widoków jednocześnie. Używając deklaratywnego kodu XML, możesz tworzyć skoordynowane animacje z wieloma widokami, które trudno osiągnąć w kodzie.
Animacje to świetny sposób na uatrakcyjnienie aplikacji. Animacji możesz używać do:
- Pokaż zmiany – animowanie stanów pozwala w naturalny sposób śledzić zmiany w interfejsie.
- Przyciągnij uwagę – za pomocą animacji zwracaj uwagę na ważne elementy interfejsu.
- Twórz atrakcyjne projekty – skuteczny ruch w projekcie sprawi, że aplikacje będą wyglądać dopracowane.
Wymagania wstępne
To ćwiczenie w Codelabs zostało stworzone z myślą o deweloperach, którzy mają pewne doświadczenie w programowaniu na Androida. Zanim wykonasz to ćwiczenie z programowania, wykonaj te czynności:
- Dowiedz się, jak utworzyć aplikację o aktywności, podstawowy układ i uruchomić ją na urządzeniu lub w emulatorze przy użyciu Android Studio. Poznaj
ConstraintLayout
. Przeczytaj ćwiczenie z programowania dotyczące układu ograniczeń, aby dowiedzieć się więcej o tabeliConstraintLayout
.
Co trzeba zrobić
- Definiowanie animacji za pomocą narzędzi
ConstraintSets
iMotionLayout
- Animacja na podstawie zdarzeń przeciągania
- Zmień animację za pomocą funkcji
KeyPosition
- Zmień atrybuty za pomocą atrybutu
KeyAttribute
- Uruchamianie animacji za pomocą kodu
- Animuj zwijane nagłówki za pomocą funkcji
MotionLayout
Czego potrzebujesz
- Android Studio 4.0 (edytor
MotionLayout
działa tylko w tej wersji Android Studio).
2. Pierwsze kroki
Przykładowa aplikacja jest dostępna na 2 sposoby:
... lub skopiuj repozytorium GitHub z wiersza poleceń, używając następującego polecenia:
$ git clone https://github.com/googlecodelabs/motionlayout.git
3. Tworzenie animacji za pomocą MotionLayout
Najpierw utwórz animację, która w odpowiedzi na kliknięcia użytkownika przenosi widok z góry do dołu na końcu.
Aby utworzyć animację na podstawie kodu startowego, potrzebujesz tych głównych elementów:
MotionLayout,
, który jest podklasą klasyConstraintLayout
. Wszystkie widoki, które mają być animowane, określasz w taguMotionLayout
.MotionScene,
, który jest plikiem XML opisującym animację dlaMotionLayout.
Transition,
, który jest częścią elementuMotionScene
, który określa czas trwania animacji, regułę i sposób przesuwania wyświetleń.ConstraintSet
, który określa ograniczenie start i koniec przejścia.
Przyjrzyjmy się po kolei każdemu z tych elementów, zaczynając od MotionLayout
.
Krok 1. Zapoznaj się z obecnym kodem
MotionLayout
jest podklasą klasy ConstraintLayout
, dlatego podczas dodawania animacji obsługuje te same funkcje. Aby używać narzędzia MotionLayout
, musisz dodać widok MotionLayout
, w którym należy użyć elementu ConstraintLayout.
.
- W aplikacji
res/layout
otwórzactivity_step1.xml.
. Masz tuConstraintLayout
z pojedyncząImageView
gwiazdką, w której zastosowano zabarwienie.
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 obiekt ConstraintLayout
nie ma żadnych ograniczeń, więc jeśli uruchomisz go teraz, widok gwiazdki nie będzie zablokowany, co oznacza, że znajdowałyby się w nieznanej lokalizacji. Android Studio wyświetli ostrzeżenie o braku ograniczeń.
Krok 2. Przekonwertuj na układ ruchomy
Aby korzystać z animacji za pomocą elementu MotionLayout,
, musisz przekonwertować element ConstraintLayout
na element MotionLayout
.
Aby układ mógł użyć sceny ruchu, musi na nią być skierowana.
- W tym celu otwórz powierzchnię projektu. W Android Studio 4.0, patrząc na plik XML szablonu, otwierasz powierzchnię projektu, klikając ikonę podziału lub projektu w prawym górnym rogu.
- Po otwarciu platformy projektu kliknij podgląd prawym przyciskiem myszy i wybierz Konwertuj na układ MotionLayout.
Spowoduje to zastąpienie tagu ConstraintLayout
tagiem MotionLayout
i dodanie tagu motion:layoutDescription
do tagu MotionLayout
wskazującego adres @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 animacji to pojedynczy plik XML opisujący animację w pliku MotionLayout
.
Gdy tylko przekonwertujesz na MotionLayout
, na powierzchni projektowej pojawi się Edytor ruchu
W Edytorze ruchomych pojawiły się 3 nowe elementy interfejsu:
- Przegląd – jest to okno modalne, które pozwala wybrać różne części animacji. Na tym obrazie wybrany jest
start
ConstraintSet
. Możesz też wybrać przejście między elementamistart
iend
. Aby to zrobić, kliknij strzałkę między nimi. - Sekcja – pod przeglądem znajduje się okno sekcji, które zmienia się w zależności od wybranego elementu przeglądu. Na tym obrazie informacje o
start
ConstraintSet
są wyświetlane w oknie wyboru. - Atrybut – panel atrybutów wyświetla się i umożliwia edytowanie atrybutów aktualnie wybranego elementu z poziomu przeglądu lub okna wyboru. Ilustracja pokazująca atrybuty dla:
ConstraintSet
(start
).
Krok 3. Zdefiniuj ograniczenia początkowe i końcowe
Wszystkie animacje można zdefiniować jako początek i koniec. Początek wskazuje, jak wygląda ekran przed animacją, a koniec opisuje, jak będzie wyglądać ekran po zakończeniu animacji. MotionLayout
odpowiada za znajdowanie animacji między stanem początkowym i końcowym (w czasie).
Do określania stanu początkowego i końcowego MotionScene
używa tagu ConstraintSet
. ConstraintSet
to zestaw ograniczeń, które można zastosować do widoków. Obejmuje to ograniczenia dotyczące szerokości, wysokości i ConstraintLayout
. Zawiera również atrybuty, takie jak alpha
. Nie zawiera on samych widoków danych, a jedynie ich ograniczenia.
Wszystkie ograniczenia określone w polu ConstraintSet
zastąpią te określone w pliku układu. Jeśli zdefiniujesz ograniczenia zarówno w układzie, jak i w elemencie MotionScene
, stosowane będą tylko ograniczenia z elementu MotionScene
.
W tym kroku ograniczysz wyświetlanie widoku gwiazdki do początku u góry i u dołu ekranu.
Możesz wykonać ten krok za pomocą edytora ruchu lub bezpośrednio edytując tekst activity_step1_scene.xml
.
- W panelu przeglądu wybierz
start
ConstraintSet
- W panelu wyboru kliknij
red_star
. Obecnie pokazuje źródłolayout
– oznacza to, że ten parametr nie jest objęty ograniczeniami w tym zakresie (ConstraintSet
). Aby utworzyć ograniczenie, kliknij ikonę ołówka w prawym górnym rogu.
- Sprawdź, czy po wybraniu w panelu Przegląd opcji
ConstraintSet
start
w kolumniered_star
wyświetla się źródłostart
. - W panelu Atrybuty, po wybraniu opcji
red_star
w kolumniestart
ConstraintSet
, dodaj ograniczenie u góry i zacznij od kliknięcia niebieskich przycisków +.
- Otwórz aplikację
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>
ConstraintSet
ma wartość id
o wartości @id/start
i określa wszystkie ograniczenia obowiązujące do wszystkich widoków danych MotionLayout
. Ten element (MotionLayout
) ma tylko 1 widok danych, więc wystarczy 1 Constraint
.
Element Constraint
w elemencie ConstraintSet
określa identyfikator widoku, który ogranicza, @id/red_star
zdefiniowany w activity_step1.xml
. Pamiętaj, że tagi Constraint
określają tylko ograniczenia i informacje o układzie. Tag Constraint
nie wie, że jest stosowany do elementu ImageView
.
To ograniczenie określa wysokość i szerokość oraz dwa inne ograniczenia potrzebne do ograniczenia widoku red_star
do górnej części elementu nadrzędnego.
- W panelu przeglądu wybierz
end
ConstraintSet.
- Wykonaj te same czynności co wcześniej, aby dodać
Constraint
dlared_star
w:end
ConstraintSet
. - Aby wykonać ten krok przy użyciu Edytora ruchu, dodaj ograniczenie do elementów
bottom
iend
, klikając niebieskie przyciski +.
- Kod w pliku 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>
Tak jak @id/start
, ten ConstraintSet
ma singiel Constraint
w: @id/red_star
. Tym razem jest ona ograniczona 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 fragment jednej animacji, od początku do końca.
Przejście musi określać początek i koniec przejścia (ConstraintSet
). Przejście może też określać sposób modyfikowania animacji w inny sposób, na przykład czas trwania animacji lub sposób jej animowania przez przeciąganie widoków.
- Edytor Motion domyślnie utworzył przejście podczas tworzenia pliku MotionScene. Otwórz aplikację
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 potrzebuje MotionLayout
do utworzenia animacji. Patrząc na każdy z atrybutów:
- Ustawienie
constraintSetStart
zostanie zastosowane do widoków po rozpoczęciu animacji. - Ustawienie
constraintSetEnd
zostanie zastosowane do widoków na końcu animacji. duration
określa czas trwania animacji w milisekundach.
Następnie MotionLayout
wyznaczy ścieżkę między ograniczeniem początkowym a końcowym i będzie animować ją przez określony czas.
Krok 5. Wyświetl podgląd animacji w edytorze animacji
Animacja: film przedstawiający odtwarzanie podglądu przejścia w edytorze animacji.
- Otwórz edytor animacji i wybierz przejście, klikając strzałkę między
start
aend
w panelu Przegląd.
- Panel wyboru zawiera elementy sterujące odtwarzaniem i pasek przewijania po wybraniu przejścia. Kliknij przycisk odtwarzania lub przeciągnij bieżącą pozycję, aby wyświetlić podgląd animacji.
Krok 6. Dodaj moduł obsługi kliknięć
Potrzebujesz sposobu na rozpoczęcie animacji. Możesz to zrobić na przykład tak, aby zdarzenie MotionLayout
reagowało na zdarzenia kliknięcia w usłudze @id/red_star
.
- Otwórz edytor ruchu i wybierz przejście, klikając strzałkę między punktem początkowym i końcowym w panelu Przegląd.
- Kliknij Utwórz moduł obsługi kliknięć lub przesuwania na pasku narzędzi panelu przeglądu . Spowoduje to dodanie modułu obsługi, który rozpocznie przejście.
- W wyskakującym okienku wybierz Click Handler.
- Zmień wartość Widok na kliknięcie na
red_star
.
- Kliknij Dodaj. Moduł obsługi kliknięć jest reprezentowany przez małą kropkę na pasku przejścia w edytorze animacji.
- Po wybraniu przejścia w panelu przeglądu dodaj atrybut
clickAction
o wartościtoggle
do modułu obsługi OnClick dodanego w panelu atrybutów.
- Otwórz aplikację
activity_step1_scene.xml
, aby zobaczyć kod wygenerowany przez edytor ruchu
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>
Pole Transition
informuje element MotionLayout
, aby uruchamiać animację w odpowiedzi na zdarzenia kliknięcia za pomocą tagu <OnClick>
. Patrząc na każdy z atrybutów:
targetId
to widok, na którym należy monitorować kliknięcia.clickAction
ztoggle
przełączy się między stanem początkowym i końcowym po kliknięciu. Inne opcje dotycząceclickAction
znajdziesz w dokumentacji.
- Uruchom kod, kliknij Krok 1, a następnie kliknij czerwoną gwiazdkę i zobacz animację.
Krok 5. Animacje w praktyce
Uruchom aplikację Animacja powinna się uruchomić po kliknięciu gwiazdki.
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 gwiazdki jest ograniczona do górnej części ekranu. Na końcu animacji (@id/end
) ikona gwiazdki będzie ograniczona do dolnej części 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
Na tym etapie utworzysz animację, która będzie się wyświetlać w odpowiedzi na zdarzenie przeciągania przez użytkownika (przesuwanie palcem po ekranie). MotionLayout
obsługuje śledzenie zdarzeń dotyku w celu przesuwania widoków, a także oparte na fizyce gesty przesuwania, dzięki którym ruch jest płynny.
Krok 1. Sprawdź początkowy kod
- Aby rozpocząć, otwórz plik układu
activity_step2.xml
, który zawiera już elementMotionLayout
. Spójrz na 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 definiuje wszystkie widoki animacji. Ikony trzech gwiazdek nie są ograniczone w układzie, ponieważ będą one animowane w scenie animacji.
Kredyty TextView
mają zastosowane ograniczenia, ponieważ pozostają w tym samym miejscu przez całą animację i nie zmieniają żadnych atrybutów.
Krok 2. Animuj scenę
Podobnie jak w przypadku ostatniej animacji, animacja będzie wskazywać początek i koniec ConstraintSet,
oraz Transition
.
Określ początkowy element ConstraintSet
- Otwórz scenę ruchu
xml/step2.xml
, aby zdefiniować animację. - Dodaj ograniczenia początkowego ograniczenia
start
. Na początku wszystkie 3 gwiazdki są wyśrodkowane na dole ekranu. Gwiazdki po lewej i prawej stronie mają wartośćalpha
o wartości0.0
, co oznacza, że są w pełni 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 funkcji ConstraintSet
określasz po jednym elemencie Constraint
na każdą z gwiazdek. Każdy limit zostanie zastosowany przez zasadę MotionLayout
na początku animacji.
Każdy widok gwiazdek jest wyśrodkowany na dole ekranu za pomocą ograniczeń na początku, końcu i na dole. Dwie gwiazdki @id/left_star
i @id/right_star
mają dodatkową wartość alfa, która sprawia, że są niewidoczne, i zostaną zastosowane na początku animacji.
Zestawy ograniczeń start
i end
określają początek i koniec animacji. Ograniczenie początku, takie jak motion:layout_constraintStart_toStartOf
, ograniczy rozpoczęcie widoku do początku innego widoku. Początkowo może to być mylące, ponieważ nazwa start
jest używana zarówno w odniesieniu jak i w kontekście ograniczeń. Aby ułatwić rozróżnianie, właściwość start
w tekście layout_constraintStart
odnosi się do terminu „start”. czyli od lewej strony w języku od lewej do prawej, a od prawej do lewej. Ustawione ograniczenie start
odnosi się do początku animacji.
Definiowanie końcowego zbioru ograniczeń
- Określ ograniczenie końcowe, aby użyć łańcucha do umieszczenia wszystkich 3 gwiazdek obok siebie pod spodem
@id/credits
. Dodatkowo wartość końcowaalpha
lewej i prawej gwiazdki zostanie ustawiona 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>
Efektem jest to, że w miarę animacji obraz będzie rozciągać się i w górę od środka.
Właściwość alpha
jest ustawiona na @id/right_start
i @id/left_star
w obu usługach ConstraintSets
, dlatego oba widoki zanikają w miarę postępu animacji.
Animacja oparta na przesuwaniu przez użytkownika
MotionLayout
może śledzić zdarzenia przeciągania elementów przez użytkownika, aby tworzyć oparte na fizyce „przeloty” animację. Oznacza to, że jeśli użytkownik je przesuwa, obraz będzie zatrzymywany i zwolniony tak, jakby to był obiekt fizyczny podczas przesuwania się po powierzchni. Możesz dodać tego typu animację za pomocą tagu OnSwipe
w Transition
.
- Zastąp zadanie TODO dodanie tagu
OnSwipe
tagiem<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 to touchAnchorId
.
touchAnchorId
to śledzony widok, który zmienia się w odpowiedzi na dotyk.MotionLayout
będzie utrzymywać ten widok w takiej samej odległości od przesuwanego palca.touchAnchorSide
określa, którą stronę widoku chcesz śledzić. Jest to ważne w przypadku widoków, które zmieniają rozmiar, przechodzą złożone ścieżki lub w których jedna ze stron porusza się szybciej niż druga.dragDirection
określa kierunek animacji (w górę, w dół, w lewo lub w prawo).
Gdy MotionLayout
nasłuchuje zdarzeń przeciągania, detektor zostanie zarejestrowany w widoku MotionLayout
, a nie w widoku określonym przez zasadę touchAnchorId
. Gdy użytkownik rozpocznie gest w dowolnym miejscu na ekranie, MotionLayout
zachowa stałą odległość między palcem od obszaru touchAnchorSide
widoku touchAnchorId
. Jeśli na przykład użytkownik dotknie obszaru o wartości 100 dp od strony zakotwiczonej, przez cały czas trwania animacji MotionLayout
będzie utrzymywać pozycję tej strony od palca na poziomie 100 dp.
Wypróbuj
- Ponownie uruchom aplikację i otwórz ekran kroku 2. Zobaczysz animację.
- Powiedz na przykład „rzucanie” lub puść palec w połowie animacji, by sprawdzić, jak
MotionLayout
wyświetla animacje oparte na fizyce płynów.
MotionLayout
może tworzyć animacje między bardzo różnymi projektami, korzystając z funkcji ConstraintLayout
, aby tworzyć efekty multimedialne.
W tej animacji wszystkie 3 widoki są na początku umieszczane względem ich elementów nadrzędnych na dole ekranu. Na końcu te 3 widoki są ustalane względem @id/credits
w łańcuchu.
Pomimo tych zupełnie różnych układów MotionLayout
tworzy płynną animację między rozpoczęciem i końcem.
5. Modyfikowanie ścieżki
W tym kroku utworzysz animację, która podczas animacji podąża po złożonej ścieżce, i animuje napisy końcowe. MotionLayout
może za pomocą elementu KeyPosition
zmodyfikować ścieżkę, jaką pokona widok między początkiem a końcem.
Krok 1. Zapoznaj się z obecnym kodem
- Otwórz
layout/activity_step3.xml
ixml/step3.xml
, aby zobaczyć obecny układ i scenę ruchu.ImageView
iTextView
wyświetlają informacje o księżycu i informacjach o autorach. - Otwórz plik sceny ruchu (
xml/step3.xml
). Widać, że zdefiniowano zakresTransition
od@id/start
do@id/end
. Animacja przesuwa obraz księżyca z lewego dolnego rogu ekranu do prawego dolnego rogu za pomocą 2 elementówConstraintSets
. Tekst informacji o twórcach i wykonawcach zmienia się odalpha="0.0"
doalpha="1.0"
, gdy księżyc się porusza. - Uruchom aplikację teraz i wybierz Krok 3. Gdy klikniesz Księżyc, zobaczysz, że od początku do końca krąży on liniowy (lub linią prostą).
Krok 2. Włącz debugowanie ścieżek
Zanim dodasz łuk do ruchu Księżyca, warto włączyć debugowanie ścieżki w MotionLayout
.
Aby łatwiej tworzyć złożone animacje w narzędziu MotionLayout
, możesz narysować ścieżkę animacji w każdym widoku. Jest to przydatne, gdy chcesz zwizualizować animację lub doprecyzować szczegóły ruchu.
- Aby włączyć ścieżki debugowania, otwórz
layout/activity_step3.xml
i 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żek, po ponownym uruchomieniu aplikacji zobaczysz ścieżki wszystkich widoków oznaczonych kreskowaną linią.
- Kręgi oznaczają początkową lub końcową pozycję jednego widoku.
- Linie reprezentują ścieżkę jednego widoku.
- Diamenty oznaczają
KeyPosition
, które modyfikują ścieżkę.
W tej animacji na przykład środkowe koło to pozycja tekstu napisów.
Krok 3. Zmodyfikuj ścieżkę
Wszystkie animacje w elemencie MotionLayout
mają początek i koniec (ConstraintSet
), który określa wygląd ekranu przed rozpoczęciem animacji i po jej zakończeniu. Domyślnie MotionLayout
rysuje ścieżkę liniową (linię prostą) między pozycją początkową a końcową każdego widoku, która zmienia pozycję.
Aby utworzyć złożone ścieżki, takie jak łuk Księżyca, w tym przykładzie, MotionLayout
korzysta z elementu KeyPosition
, by zmodyfikować ścieżkę, którą ogląda widok między punktem początkowym i końcowym.
- Otwórz aplikację
xml/step3.xml
i dodaj do sceny elementKeyPosition
. TagKeyPosition
znajduje się wewnątrz 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>
Element KeyFrameSet
jest elementem podrzędnym elementu Transition
i stanowi zestaw wszystkich elementów KeyFrames
, np. KeyPosition
, które powinny być stosowane podczas przejścia.
Ponieważ funkcja MotionLayout
oblicza ścieżkę dla Księżyca między początkiem a końcem, zmienia ją na podstawie elementu KeyPosition
określonego w tabeli KeyFrameSet
. Aby zobaczyć, jak powoduje to zmianę ścieżki, uruchom aplikację ponownie.
Element KeyPosition
ma kilka atrybutów opisujących sposób modyfikacji ścieżki. Najważniejsze z nich to:
framePosition
jest liczbą od 0 do 100. Określa, kiedy w animacji ma zostać zastosowany elementKeyPosition
– 1 to 1%, a 99 to 99% animacji. Jeśli więc wartość wynosi 50, zostanie ona zastosowana w środku.motionTarget
to widok, w którym elementKeyPosition
modyfikuje ścieżkę.keyPositionType
określa, jakKeyPosition
modyfikuje ścieżkę. Może to byćparentRelative
,pathRelative
lubdeltaRelative
(jak wyjaśniliśmy w następnym kroku).percentX | percentY
określa, o ile zmienić ścieżkę wframePosition
(wartości od 0,0 do 1,0, przy czym dozwolone wartości to >1).
Możesz to zapisać w następujący sposób: „W miejscu framePosition
zmień ścieżkę motionTarget
, przesuwając ją o percentX
lub percentY
w zależności od współrzędnych określonych przez keyPositionType
.
Domyślnie MotionLayout
zaokrągla wszystkie narożniki wprowadzone w wyniku modyfikacji ścieżki. Gdy spojrzysz na utworzoną przed chwilą animację, możesz zobaczyć, że Księżyc porusza się po zakrzywionej ścieżce. W przypadku większości animacji jest to oczekiwany sposób. Jeśli nie, możesz określić atrybut curveFit
, aby go dostosować.
Wypróbuj
Po ponownym uruchomieniu aplikacji zobaczysz animację dla tego kroku.
Księżyc porusza się po łuku, ponieważ przechodzi przez obszar KeyPosition
określony w Transition
.
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
Możesz odczytać ten element KeyPosition
w taki sposób: „W framePosition 50
(w połowie animacji) zmień ścieżkę obiektu motionTarget
@id/moon
, przesuwając ją o 50% Y
(do połowy ekranu) zgodnie ze współrzędnymi wyznaczonymi przez parentRelative
(cały element MotionLayout
).
Dlatego w połowie animacji księżyc musi przejść przez element KeyPosition
widoczny w 50% na ekranie. Ten obiekt KeyPosition
w ogóle nie zmienia ruchu osi X, więc księżyc będzie nadal wyświetlany w poziomie od początku do końca. MotionLayout
wytypuje gładką ścieżkę, która będzie przebiegać przez ten obiekt (KeyPosition
) w trakcie przechodzenia między początkiem a końcem.
Jeśli przyjrzeć się bliżej, tekst napisów jest ograniczony przez położenie księżyca. Dlaczego nie porusza się również w pionie?
<Constraint
android:id="@id/credits"
...
motion:layout_constraintBottom_toBottomOf="@id/moon"
motion:layout_constraintTop_toTopOf="@id/moon"
/>
Okazuje się jednak, że nawet jeśli modyfikujesz trasę, którą pokonuje Księżyc, jego początkowa i końcowa pozycja w ogóle nie porusza się w pionie. Element KeyPosition
nie zmienia pozycji początkowej ani końcowej, więc tekst napisów jest ograniczony do końcowej pozycji księżyca.
Jeśli chcesz, aby środki przemieszczały się wraz z księżycem, możesz dodać do nich KeyPosition
lub zmodyfikować ograniczenia dotyczące rozpoczęcia @id/credits
.
W następnej sekcji dowiesz się więcej o różnych typach keyPositionType
w MotionLayout
.
6. Objaśnienie typu keyPositionType
W ostatnim kroku użyliśmy typu keyPosition
(parentRelative
), aby przesunąć ścieżkę o 50% ekranu. Atrybut keyPositionType
określa, w jaki sposób MotionLayout będzie modyfikować ścieżkę zgodnie z wartością percentX
lub percentY
.
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
Istnieją 3 różne typy keyPosition
: parentRelative
, pathRelative
i deltaRelative
. Określenie typu spowoduje zmianę układu współrzędnych, według którego obliczane są elementy percentX
i percentY
.
Co to jest układ współrzędnych?
Układ współrzędnych umożliwia określenie punktu w przestrzeni. Są też przydatne do opisania pozycji na ekranie.
Układy współrzędnych MotionLayout
to kartezjański układ współrzędnych. Oznacza to, że mają one oś X i Y zdefiniowaną przez dwie prostopadłe linie. Główna różnica między nimi polega na tym, w którym miejscu na ekranie znajduje się oś X (oś Y jest zawsze prostopadła do osi X).
Wszystkie układy współrzędnych w: MotionLayout
używają wartości od 0.0
do 1.0
na osi X i Y. Mogą zawierać wartości ujemne i wartości większe niż 1.0
. Na przykład wartość percentX
o wartości -2.0
oznaczałaby, że dwukrotnie przejdziesz w przeciwnym kierunku na osi X.
Jeśli to brzmi aż za bardzo jak na lekcjach algebry, spójrz na poniższe zdjęcia.
współrzędne względne
Funkcja keyPositionType
w zakresie parentRelative
korzysta z tego samego układu współrzędnych co ekran. Definiuje (0, 0)
w lewym górnym rogu całego obiektu MotionLayout
i (1, 1)
w prawym dolnym rogu.
Typu parentRelative
możesz użyć za każdym razem, gdy chcesz utworzyć animację przedstawiającą cały obiekt MotionLayout
– np. łuk księżycowy w tym przykładzie.
Jeśli jednak chcesz zmienić ścieżkę w odniesieniu do ruchu, na przykład nieco ją krzywą, lepsze będą dwa pozostałe układy współrzędnych.
Współrzędne względne
Delta to termin matematyczny oznaczający zmianę, więc deltaRelative
to sposób na powiedzenie „zmień krewnego”. We deltaRelative
współrzędnych(0,0)
to pozycja początkowa widoku, a (1,1)
to pozycja końcowa. Osie X i Y są wyrównane względem ekranu.
Oś X jest zawsze pozioma na ekranie, a oś Y zawsze pionowo. W porównaniu z urządzeniem parentRelative
główna różnica polega na tym, że współrzędne opisują tylko tę część ekranu, którą będzie przesuwać widok.
deltaRelative
to świetny układ współrzędnych do niezależnego kontrolowania ruchu w poziomie i w pionie. Na przykład możesz utworzyć animację, której ruch w pionie (Y) kończy się w 50% i kontynuuje animowanie w poziomie (X).
współrzędne względne.
Ostatni układ współrzędnych w tym miesiącu (MotionLayout
) to pathRelative
. Różni się więc od dwóch pozostałych, ponieważ oś X podąża za ścieżką animacji od początku do końca. (0,0)
jest pozycją początkową, a (1,0)
– pozycją końcową.
Dlaczego warto? Na pierwszy rzut oka może to dość zaskakujące, zwłaszcza że taki układ współrzędnych nie jest nawet dopasowany do układu współrzędnych ekranu.
Okazuje się, że usługa pathRelative
jest naprawdę przydatna w kilku sprawach.
- Przyspieszenie, spowolnienie lub zatrzymanie wyświetlenia w trakcie części animacji. Wymiar X zawsze będzie dokładnie pasować do ścieżki wyświetlanej w widoku, więc za pomocą funkcji
KeyPosition
wpathRelative
możesz zmienić, do którego punktuframePosition
dojdzie do określonego punktu tej ścieżki. Dlatego parametrKeyPosition
w punkcieframePosition="50"
z wartościąpercentX="0.1"
spowoduje, że przesuwanie animacji w 50% przypadków przemieści się w pierwszych 10% ruchu. - Dodawanie subtelnego łuku do ścieżki. Ponieważ wymiar Y jest zawsze prostopadły do ruchu, zmiana Y spowoduje zmianę ścieżki w krzywą względem całego ruchu.
- Dodanie drugiego wymiaru, gdy wymiar
deltaRelative
nie będzie działać. Aby uzyskać widok całkowicie w poziomie i w pionie, funkcjadeltaRelative
utworzy tylko 1 przydatny wymiar. JednakpathRelative
zawsze utworzy użyteczne współrzędne X i Y.
Z następnego kroku dowiesz się, jak tworzyć jeszcze bardziej złożone ścieżki przy użyciu więcej niż jednego KeyPosition
.
7. Tworzenie złożonych ścieżek
Animacja utworzona w ostatnim kroku tworzy gładką krzywą, ale kształt może być bardziej przypominający księżyc.
Modyfikowanie ścieżki z większą liczbą elementów KeyPosition
MotionLayout
może jeszcze bardziej zmodyfikować ścieżkę, definiując tyle elementów KeyPosition
, ile potrzeba do uzyskania dowolnego ruchu. Na potrzeby tej animacji utworzysz łuk, ale jeśli chcesz, możesz sprawić, by księżyc podskoczył w górę i w dół na środku ekranu.
- Otwórz pokój
xml/step4.xml
. Zobaczysz, że ma te same widoki danych iKeyFrame
dodane w ostatnim kroku. - Aby zaokrąglić górną część krzywej, dodaj jeszcze 2 elementy
KeyPositions
do ścieżki@id/moon
, jeden tuż przed jej szczytem, a drugi za 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 elementy typu KeyPositions
będą stosowane w 25% i 75% animacji, a element @id/moon
będzie przechodzić przez ścieżkę znajdującą się w 60% od góry ekranu. W połączeniu z dotychczasowym poziomem KeyPosition
(50%) tworzy to gładki łuk, za którym podąża księżyc.
W programie MotionLayout
możesz dodać tyle elementów KeyPositions
, ile potrzebujesz, aby uzyskać żądaną ścieżkę animacji. Funkcja MotionLayout
zastosuje każdy element KeyPosition
w określonym miejscu (framePosition
) i dowiedz się, jak utworzyć płynny ruch, który obejmuje wszystkie elementy KeyPositions
.
Wypróbuj
- Ponownie uruchom aplikację. Przejdź do kroku 4, aby zobaczyć, jak działa animacja. Gdy klikniesz Księżyc, wędruje on ścieżką od początku do końca, przechodząc przez każdy obiekt
KeyPosition
określony wKeyFrameSet
.
Odkrywaj samodzielnie
Zanim przejdziesz do innych typów KeyFrame
, spróbuj dodać więcej KeyPositions
do KeyFrameSet
, aby zobaczyć, jakie efekty możesz uzyskać za pomocą KeyPosition
.
Oto przykład tworzenia złożonej ścieżki, która porusza się tam i z powrotem podczas animacji.
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 przeglądać KeyPosition
, w następnym kroku przejdziesz do innych typów KeyFrames
.
8. Zmiana atrybutów podczas ruchu
Tworzenie dynamicznych animacji często wiąże się z zmienianiem wartości size
, rotation
lub alpha
widoków w miarę postępu animacji. MotionLayout
umożliwia animowanie wielu atrybutów w dowolnym widoku za pomocą komponentu KeyAttribute
.
W tym kroku użyjesz programu KeyAttribute
, aby ustawić skalę i obracanie księżyca. Użyjesz również słowa kluczowego KeyAttribute
, aby opóźnić wyświetlanie tekstu do momentu, gdy księżyc będzie prawie całą podróż.
Krok 1. Zmień rozmiar i wykonaj rotację za pomocą atrybutu KeyAttribute
- Otwórz plik
xml/step5.xml
, który zawiera tę samą animację utworzoną w ostatnim kroku. Aby zwiększyć różnorodność, w tle wykorzystano ten ekran z innym obrazem z kosmosu. - Aby księżyc się powiększał i obracał, dodaj dwa tagi
KeyAttribute
w elementachKeyFrameSet
w 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 elementy typu KeyAttributes
są stosowane w 50% i 100% animacji. Pierwsze KeyAttribute
przy 50% pojawi się na górze łuku i spowoduje podwojenie widoku oraz podwojenie widoku o -360 stopni (lub jedno pełne koło). Drugie pole KeyAttribute
zakończy drugi obrót do -720 stopni (dwa pełne okręgi) i zmniejszy rozmiar z powrotem do zwykłego, ponieważ wartości scaleX
i scaleY
wynoszą domyślnie 1,0.
Podobnie jak w przypadku KeyPosition
, element KeyAttribute
używa właściwości framePosition
i motionTarget
, aby określić, kiedy zastosować KeyFrame
, a który widok ma być modyfikowany. Funkcja MotionLayout
będzie interpolować między zmienną KeyPositions
, by utworzyć płynne animacje.
KeyAttributes
obsługują atrybuty, które można zastosować do wszystkich widoków. Umożliwiają zmianę podstawowych atrybutów, takich jak visibility
, alpha
lub elevation
. Możesz również zmienić obrót tak jak tutaj, obrócić widok w 3 wymiarach za pomocą funkcji rotateX
i rotateY
, skalować rozmiar za pomocą narzędzi scaleX
i scaleY
oraz przesunąć pozycję widoku w wymiarach X, Y lub Z.
Krok 2. Opóźnij pojawienie się środków
Jednym z celów tego kroku jest zaktualizowanie animacji w taki sposób, aby tekst napisów nie pojawiał się, dopóki animacja nie zostanie w większości ukończona.
- Aby opóźnić pojawienie się informacji o twórcach i wykonawcach, zdefiniuj jeszcze 1 element
KeyAttribute
, który sprawi, że wartośćalpha
będzie wynosić 0 dokeyPosition="85"
.MotionLayout
będzie płynnie przejść od wartości 0 do 100 alfa, ale zrobi to przez ostatnie 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 komponent KeyAttribute
zachowuje wartość alpha
z @id/credits
na poziomie 0,0 przez pierwsze 85% animacji. Początek ma wartość alfa 0, co oznacza, że przez pierwsze 85% animacji będzie niewidoczne.
Efektem końcowym tego elementu (KeyAttribute
) jest to, że napisy pojawią się pod koniec animacji. Nadaje to wyglądowi koordynacji z księżycem osadzonym w prawym rogu ekranu.
Opóźniając wyświetlanie animacji w jednym widoku w czasie, gdy inny widok wyświetla się w ten sposób, możesz stworzyć imponujące animacje, które spodobają się użytkownikowi.
Wypróbuj
- Uruchom aplikację jeszcze raz i przejdź do kroku 5, aby zobaczyć, jak działa animacja. Gdy klikniesz Księżyc, będzie on podążać ścieżką od początku do końca, przechodząc przez każdy obiekt
KeyAttribute
określony wKeyFrameSet
.
Obracanie księżyca o 2 pełne okręgi jest teraz wykonane podwójnie, a informacje o autorze będą opóźniać pojawianie się animacji do momentu, gdy będą prawie gotowe.
Odkrywaj samodzielnie
Zanim przejdziesz do ostatecznego typu atrybutu KeyFrame
, spróbuj zmodyfikować inne atrybuty w pliku KeyAttributes
. Spróbuj na przykład zmienić rotation
na rotationX
, aby zobaczyć, jaką animację generuje.
Oto lista atrybutów standardowych, które możesz wypróbować:
android:visibility
android:alpha
android:elevation
android:rotation
android:rotationX
android:rotationY
android:scaleX
android:scaleY
android:translationX
android:translationY
android:translationZ
9. Zmiana atrybutów niestandardowych
Bogate animacje polegają na zmianie koloru lub innych atrybutów widoku. MotionLayout
może użyć elementu KeyAttribute
, aby zmienić dowolny z atrybutów standardowych wymienionych w poprzednim zadaniu, a CustomAttribute
do określenia 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 widoku za pomocą CustomAttribute
. Funkcja MotionLayout
użyje funkcji refleksji, aby znaleźć metodę ustawiania, a następnie wielokrotnie ją wywoła, aby animować widok.
W tym kroku użyjesz CustomAttribute
, aby ustawić atrybut colorFilter
dla księżyca i utworzyć widoczną poniżej animację.
Definiowanie atrybutów niestandardowych
- Aby rozpocząć, otwórz plik
xml/step6.xml
, który zawiera tę samą animację utworzoną w ostatnim kroku. - Aby zmienić kolor księżyca, dodaj dwa obiekty
KeyAttribute
zCustomAttribute
wKeyFrameSet
w punktachkeyFrame="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 elemencie KeyAttribute
. Pole CustomAttribute
zostanie zastosowane w framePosition
określonym przez KeyAttribute
.
W elemencie CustomAttribute
musisz określić attributeName
i 1 wartość do ustawienia.
motion:attributeName
to nazwa metody ustawiającej, która zostanie wywołana przez ten atrybut niestandardowy. W tym przykładzie funkcjasetColorFilter
w usłudzeDrawable
zostanie wywołana.motion:custom*Value
to wartość niestandardowa typu podanego w nazwie. W tym przykładzie wartość niestandardowa to określony kolor.
Wartości niestandardowe mogą mieć te typy:
- 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 mechanizm ustawiający w dowolnym widoku.
Wypróbuj
- Uruchom aplikację jeszcze raz i przejdź do kroku 6, aby zobaczyć, jak działa animacja. Gdy klikniesz Księżyc, będzie on podążać ścieżką od początku do końca, przechodząc przez każdy obiekt
KeyAttribute
określony wKeyFrameSet
.
Po dodaniu kolejnych elementów KeyFrames
element MotionLayout
zmienia ścieżkę księżyca z linii prostej w złożoną, dodając podwójne odwrócenie, zmianę rozmiaru i zmianę koloru w połowie animacji.
W prawdziwych animacjach często animowane jest kilka widoków jednocześnie, kontrolując ich ruch na różnych ścieżkach i z różną prędkością. Określając inny parametr KeyFrame
dla każdego wyświetlenia, możliwe jest utworzenie choreografii do bogatych animacji, które animują wiele wyświetleń za pomocą funkcji MotionLayout
.
10. Przeciągnij zdarzenia i złożone ścieżki
W tym kroku pokażemy, jak używać usługi OnSwipe
ze złożonymi ścieżkami. Jak dotąd animacja księżyca została uruchomiona przez detektor OnClick
i trwała przez określony czas.
Sterowanie animacjami ze złożonymi ścieżkami za pomocą narzędzia OnSwipe
(takimi jak animacja księżyca utworzona w ostatnich kilku krokach) wymaga zrozumienia, jak działa OnSwipe
.
Krok 1. Poznaj działanie OnSwipe
- Otwórz aplikację
xml/step7.xml
i 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 jesteś w stanie uzyskać płynną animację, przeciągając księżyc wzdłuż ścieżki łuku.
Po uruchomieniu tej animacji nie wygląda ona zbyt dobrze. Gdy księżyc znajdzie się na szczycie łuku, zaczyna skakać.
Aby zrozumieć ten błąd, zastanów się, co się dzieje, gdy użytkownik dotknie ekranu nieco poniżej górnej krawędzi łuku. Ponieważ w tagu OnSwipe
występuje element motion:touchAnchorSide="bottom"
MotionLayout
, odległość między palcem a dolną krawędzią widoku będzie stała przez cały czas trwania animacji.
Dn księżyca nie zawsze zmierza w tym samym kierunku, więc idzie w górę, a potem z powrotem w dół, dlatego MotionLayout
nie wie, co zrobić, gdy użytkownik właśnie mija początek łuku. Skoro śledzisz dno Księżyca, to w którym miejscu powinien się znajdować, gdy użytkownik dotyka tego miejsca?
Krok 2. Użyj prawej strony
Aby uniknąć takich błędów, ważne jest, aby przez cały czas trwania animacji zawsze wybierać takie elementy jak touchAnchorId
i touchAnchorSide
.
W tej animacji zarówno strona right
, jak i strona księżyca (left
) będą przesuwać się w jednym kierunku po ekranie.
Zarówno bottom
, jak i top
będą jednak działać w odwrotnym kierunku. Gdy OnSwipe
spróbuje je śledzić, może się zdezorientować, kiedy zmieni się kierunek jazdy.
- Aby ta animacja była sterowana dotykiem, zmień
touchAnchorSide
naright
.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="right"
/>
Krok 3. Użyj kierunku przeciągania
Możesz też połączyć parametry dragDirection
z parametrem touchAnchorSide
, aby ustawić kierunek ścieżki bocznej w inny sposób niż zwykle. Ważne jest, aby ścieżka touchAnchorSide
rozwijała się tylko w jednym kierunku, ale możesz wskazać usłudze MotionLayout
, w którym kierunku ma śledzić ruch. Na przykład możesz zachować touchAnchorSide="bottom"
, ale dodać dragDirection="dragRight"
. Dzięki temu MotionLayout
będzie śledzić położenie u dołu widoku, ale gdy przesunie się w prawo, będzie uwzględniać jego położenie (ignoruje ruch w pionie). Nawet jeśli dolna część przesuwa się w górę i w dół, w przypadku funkcji OnSwipe
nadal jest poprawnie animowany.
- Zaktualizuj aplikację
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
- Ponownie uruchom aplikację i spróbuj przeciągnąć księżyc przez całą ścieżkę. Mimo że migracja przebiega po złożonym łuku,
MotionLayout
może przesuwać animację w odpowiedzi na zdarzenia przesuwania.
11. Ruch ruchomy z kodem
MotionLayout
umożliwia tworzenie atrakcyjnych animacji w połączeniu z zasadą CoordinatorLayout
. W tym kroku utworzysz nagłówek zwijany za pomocą właściwości MotionLayout
.
Krok 1. Zapoznaj się z obecnym kodem
- Aby rozpocząć, otwórz
layout/activity_step8.xml
. - W narzędziu
layout/activity_step8.xml
widać, że działająca instancjaCoordinatorLayout
iAppBarLayout
została już utworzona.
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 korzysta z elementu CoordinatorLayout
do udostępniania przewijanych informacji między elementami NestedScrollView
i AppBarLayout
. Gdy więc przycisk NestedScrollView
przewinie się w górę, AppBarLayout
poinformuje o zmianie. Tak właśnie implementuje się zwijany pasek narzędzi na Androidzie – przewijanie tekstu będzie „skoordynowane”. z nagłówkiem zwijającym.
Scena ruchu, na którą wskazuje @id/motion_layout
, jest podobna do sceny ruchu w ostatnim kroku. Jednak deklaracja OnSwipe
została usunięta, aby umożliwić współdziałanie z elementami CoordinatorLayout
.
- Uruchom aplikację i przejdź do kroku 8. Widać, że podczas przewijania tekstu księżyc się nie porusza.
Krok 2. Zadbaj o przewijanie elementu MotionLayout
- Aby widok
MotionLayout
był przewijany od razu po przewinięciu elementuNestedScrollView
, dodajmotion:minHeight
imotion:layout_scrollFlags
do elementuMotionLayout
.
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 aplikację ponownie i przejdź do kroku 8. Widać, że
MotionLayout
zwija się w miarę przewijania w górę. Animacja nie jest jednak jeszcze uwzględniana w zależności od sposobu przewijania.
Krok 3. Przenieś ruch za pomocą kodu
- Otwórz
Step8Activity.kt
. Edytuj funkcjęcoordinateMotion()
, aby poinformować usługęMotionLayout
o 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 pole OnOffsetChangedListener
, które będzie wywoływane za każdym razem, gdy użytkownik przewinie stronę z bieżącym przesunięciem przewijania.
Funkcja MotionLayout
obsługuje przenoszenie danych za pomocą właściwości postępu. Aby przeliczyć postęp verticalOffset
na procentowy, podziel go przez całkowity zakres przewijania.
Wypróbuj
- Wdróż aplikację ponownie i uruchom animację z kroku 8. Widać, że
MotionLayout
przewija animację zgodnie z pozycją przewijania.
Za pomocą MotionLayout
można tworzyć niestandardowe animacje zwijanego paska narzędzi. Używając sekwencji KeyFrames
, można uzyskać bardzo wyraźne efekty.
12. Gratulacje
W ramach tego ćwiczenia w programie omówiliśmy podstawowy interfejs API w MotionLayout
.
Aby zobaczyć więcej przykładów zastosowania MotionLayout
w praktyce, zapoznaj się z oficjalną przykładem. Koniecznie zapoznaj się z dokumentacją.
Więcej informacji
MotionLayout
obsługuje jeszcze więcej funkcji, których nie obejmuje to ćwiczenie z programowania, na przykład KeyCycle,
(pozwala kontrolować ścieżki lub atrybuty z powtarzającymi się cyklami) oraz KeyTimeCycle,
, który umożliwia animację na podstawie czasu zegara. Zapoznaj się z przykładami każdej z nich.
Linki do innych modułów z programowania w tym kursie znajdziesz na stronie docelowej kursów z programowania dla zaawansowanych w programie Kotlin (w języku angielskim).