Zaawansowany Android w Kotlin 03.2: animacja z MotionLayout

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 o ConstraintLayout znajdziesz w samouczku Constraint Layout.

Co musisz zrobić

  • Zdefiniuj animację za pomocą ConstraintSetsMotionLayout
  • 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

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 tagu MotionLayout.
  • MotionScene,, czyli plik XML opisujący animację dla MotionLayout..
  • Transition,, która jest częścią MotionScene i 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..

  1. res/layout otwórz activity_step1.xml.. Znajduje się tu ConstraintLayout z jedną ImageView gwiazdą, 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ć ConstraintLayoutMotionLayout.

Aby układ korzystał ze sceny ruchu, musi na nią wskazywać.

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

a2beea710c2decb7.png

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

4fa936a98a8393b9.png

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.

66d0e80d5ab4daf8.png

W Motion Editorze są 3 nowe elementy interfejsu:

  1. Przegląd – to okno wyboru, które umożliwia wybieranie różnych części animacji. Na tym obrazie wybrano start ConstraintSet. Możesz też wybrać przejście między startend, klikając strzałkę między nimi.
  2. 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 start ConstraintSet.
  3. 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 start ConstraintSet.

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.

  1. W panelu przeglądu wybierz start ConstraintSet.

6e57661ed358b860.png

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

f9564c574b86ea8.gif

  1. Sprawdź, czy po wybraniu w panelu przeglądu opcji start ConstraintSet w sekcji red_star wyświetla się Źródło start.
  2. W panelu Atrybuty, gdy w sekcji start ConstraintSet jest zaznaczony element red_star, dodaj u góry ograniczenie i zacznij od kliknięcia niebieskich przycisków +.

2fce076cd7b04bd.png

  1. 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 ConstraintConstraintSet 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.

  1. W panelu przeglądu wybierz end ConstraintSet.

346e1248639b6f1e.png

  1. Wykonaj te same czynności co wcześniej, aby dodać Constraint dla red_star w end ConstraintSet.
  2. Aby wykonać ten krok za pomocą Edytora ruchu, dodaj ograniczenie do elementów bottomend, klikając niebieskie przyciski +.

fd33c779ff83c80a.png

  1. 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@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.

  1. 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:

  • constraintSetStart zostanie zastosowany do wyświetleń po rozpoczęciu animacji.
  • constraintSetEnd zostanie zastosowane do wyświetleń na końcu animacji.
  • duration okreś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

dff9ecdc1f4a0740.gif

Animacja: film przedstawiający podgląd przejścia w Motion Editor

  1. Otwórz Motion Editor i wybierz przejście, klikając strzałkę między ikonami startend w panelu podglądu.

1dc541ae8c43b250.png

  1. 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ę.

a0fd2593384dfb36.png

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.

  1. Otwórz edytor ruchu i wybierz przejście, klikając strzałkę między początkiem a końcem w panelu przeglądu.

b6f94b344ce65290.png

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

ccf92d06335105fe.png

  1. Zmień Widok do kliknięcia na red_star.

b0d3f0c970604f01.png

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

cec3913e67fb4105.png

  1. Gdy w panelu przeglądu wybrane jest przejście, dodaj atrybut clickAction o wartości toggle do obsługi zdarzenia OnClick, którą właśnie dodano w panelu atrybutów.

9af6fc60673d093d.png

  1. 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:

  • targetId to widok, w którym możesz śledzić kliknięcia.
  • clickActiontoggle będzie przełączać się między stanem początkowym a końcowym po kliknięciu. Więcej opcji clickAction znajdziesz w dokumentacji.
  1. 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.

7ba88af963fdfe10.gif

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

  1. Aby rozpocząć, otwórz plik układu activity_step2.xml, który zawiera istniejący element MotionLayout. 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.

  1. Otwórz scenę ruchu xml/step2.xml, aby zdefiniować animację.
  2. 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ść alpha ró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@id/right_star mają dodatkową wartość alfa, która sprawia, że są niewidoczne i będzie stosowana na początku animacji.

Zbiory ograniczeń startend 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 startlayout_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

  1. Zdefiniuj ograniczenie końcowe, aby użyć łańcucha do umieszczenia wszystkich 3 gwiazdek razem pod @id/credits. Dodatkowo ustawi wartość końcową alpha lewej i prawej gwiazdki na 1.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@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 OnSwipeTransition.

  1. Zastąp komentarz TODO dotyczący dodawania tagu OnSwipe kodem <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.

  • touchAnchorId to śledzony widok, który przesuwa się w odpowiedzi na dotyk. MotionLayout zachowa tę samą odległość od palca, którym przesuwasz.
  • touchAnchorSide okreś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.
  • dragDirection okreś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

  1. Uruchom ponownie aplikację i otwórz ekran Krok 2. Zobaczysz animację.
  2. Spróbuj „rzucić” lub puścić palec w połowie animacji, aby zobaczyć, jak MotionLayout wyświetla animacje oparte na fizyce płynów.

fefcdd690a0dcaec.gif

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

  1. Otwórz layout/activity_step3.xmlxml/step3.xml, aby zobaczyć istniejący układ i scenę ruchu. ImageViewTextView wyświetlają księżyc i tekst z informacjami o autorach.
  2. Otwórz plik sceny ruchomej (xml/step3.xml). Zobaczysz, że zdefiniowano Transition od @id/start do @id/end. Animacja przesuwa obraz księżyca z lewego dolnego rogu ekranu do prawego dolnego rogu za pomocą 2 ConstraintSets. Tekst napisów początkowych pojawia się stopniowo, od alpha="0.0" do alpha="1.0", w miarę przesuwania się księżyca.
  3. 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.

  1. Aby włączyć ścieżki debugowania, otwórz layout/activity_step3.xml i dodaj motion:motionDebug="SHOW_PATH" do tagu MotionLayout.

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.

23bbb604f456f65c.png

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

  1. Otwórz xml/step3.xml i dodaj KeyPosition do sceny. Tag KeyPosition jest umieszczony w tagu Transition.

eae4dae9a12d0410.png

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:

  • framePosition to liczba z zakresu od 0 do 100. Określa, kiedy w animacji należy zastosować ten KeyPosition. Wartość 1 oznacza 1% animacji, a 99 – 99% animacji. Jeśli wartość wynosi 50, zastosuj ją dokładnie pośrodku.
  • motionTarget to widok, w którym KeyPosition modyfikuje ścieżkę.
  • keyPositionType to sposób, w jaki KeyPosition modyfikuje ścieżkę. Może to być parentRelative, pathRelative lub deltaRelative (jak wyjaśniono w następnym kroku).
  • percentX | percentY to wartość, o którą należy zmodyfikować ścieżkę w punkcie framePosition (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.

46b179c01801f19e.gif

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?

1c7cf779931e45cc.gif

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

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

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

a7b7568d46d9dec7.png

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

5680bf553627416c.png

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

f3aaadaac8b4a93f.png

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ć pathRelative KeyPosition, aby zmienić framePosition, w którym osiągany jest dany punkt na tej ścieżce. Wartość KeyPosition przy framePosition="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 deltaRelative nie będzie działać. W przypadku ruchu całkowicie poziomego lub pionowego funkcja deltaRelative utworzy tylko jeden przydatny wymiar. Jednak pathRelative zawsze 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.

  1. Otwórz pokój xml/step4.xml. Widzisz, że ma taką samą liczbę wyświetleń i KeyFrame dodane w ostatnim kroku.
  2. Aby zaokrąglić górną część krzywej, dodaj jeszcze 2 punkty KeyPositions do ścieżki @id/moon – jeden tuż przed osiągnięciem szczytu, a drugi po nim.

500b5ac2db48ef87.png

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.

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

  1. 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 KeyPosition określony w elemencie KeyFrameSet.

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

cd9faaffde3dfef.png

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

  1. 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.
  2. Aby powiększyć i obrócić księżyc, dodaj 2 tagi KeyAttribute w tagu KeyFrameSet w miejscach keyFrame="50"keyFrame="100".

bbae524a2898569.png

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 scaleXscaleY są domyślnie ustawione na 1,0.

Podobnie jak KeyPosition, KeyAttribute używa elementów framePositionmotionTarget, 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 rotateXrotateY, skalować rozmiar za pomocą klawiszy scaleXscaleY 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.

  1. Aby opóźnić pojawienie się napisów, zdefiniuj jeszcze jeden warunek KeyAttribute, który zapewni, że wartość alpha pozostanie równa 0 do momentu, gdy keyPosition="85". MotionLayout nadal 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

  1. 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 KeyAttribute określony w KeyFrameSet.

2f4bfdd681c1fa98.gif

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: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

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.

5fb6792126a09fda.gif

Definiowanie atrybutów niestandardowych

  1. Na początek otwórz xml/step6.xml, który zawiera tę samą animację, którą utworzyliśmy w ostatnim kroku.
  2. Aby księżyc zmieniał kolory, dodaj 2 znaki KeyAttribute z symbolem CustomAttributeKeyFrameSet w miejscach keyFrame="0", keyFrame="50"keyFrame="100"..

214699d5fdd956da.png

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 CustomAttributeKeyAttribute. CustomAttribute zostanie zastosowane w framePosition określonym przez KeyAttribute.

W obrębie znacznika CustomAttribute musisz określić attributeName i jedną wartość do ustawienia.

  • motion:attributeName to nazwa funkcji ustawiającej, która będzie wywoływana przez ten atrybut niestandardowy. W tym przykładzie wywołana zostanie funkcja setColorFilter na Drawable.
  • motion:custom*Value to 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

  1. 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 KeyAttribute określony w KeyFrameSet.

5fb6792126a09fda.gif

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

  1. Otwórz xml/step7.xml i znajdź istniejącą deklarację OnSwipe.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide →

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. 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ć.

ed96e3674854a548.gif

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?

56cd575c5c77eddd.png

Krok 2. Używaj prawej strony

Aby uniknąć takich błędów, zawsze wybieraj touchAnchorIdtouchAnchorSide, 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.

  1. Aby animacja reagowała na zdarzenia dotknięcia, zmień touchAnchorSide na right.

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ć dragDirectiontouchAnchorSide, 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.

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

  1. Uruchom ponownie aplikację i spróbuj przeciągnąć księżyc po całej ścieżce. Mimo że ma złożony łuk, MotionLayout będzie w stanie rozwijać animację w odpowiedzi na zdarzenia przesuwania.

5458dff382261427.gif

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

  1. Aby rozpocząć, otwórz layout/activity_step8.xml.
  2. W layout/activity_step8.xml widać, że działające CoordinatorLayout i AppBarLayout są 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 NestedScrollViewAppBarLayout. 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.

  1. Uruchom aplikację i przejdź do kroku 8. Gdy przewijasz tekst, księżyc się nie porusza.

Krok 2. Ustawianie przewijania w MotionLayout

  1. Aby widok MotionLayout przewijał się od razu po przewinięciu widoku NestedScrollView, dodaj do widoku MotionLayout elementy motion:minHeightmotion: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"  >
  1. Uruchom ponownie aplikację i przejdź do kroku 8. Podczas przewijania w górę widzisz, że MotionLayout się zwija. Animacja nie jest jeszcze jednak powiązana z przewijaniem.

Krok 3. Przenieś ruch za pomocą kodu

  1. Otwórz Step8Activity.kt . Edytuj funkcję coordinateMotion(), aby informować 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 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

  1. Ponownie wdróż aplikację i uruchom animację kroku 8. Widzisz, że MotionLayout przesuwa animację na podstawie pozycji przewijania.

ee5ce4d9e33a59ca.gif

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.