Zaawansowany Android w Kotlin 03.2: animacja z MotionLayout

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 tabeli ConstraintLayout.

Co trzeba zrobić

  • Definiowanie animacji za pomocą narzędzi ConstraintSets i MotionLayout
  • 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

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ą klasy ConstraintLayout. Wszystkie widoki, które mają być animowane, określasz w tagu MotionLayout.
  • MotionScene,, który jest plikiem XML opisującym animację dla MotionLayout.
  • Transition,, który jest częścią elementu MotionScene, 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..

  1. W aplikacji res/layout otwórz activity_step1.xml.. Masz tu ConstraintLayout 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.

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

a2beea710c2decb7.png

  1. Po otwarciu platformy projektu kliknij podgląd prawym przyciskiem myszy i wybierz Konwertuj na układ MotionLayout.

4fa936a98a8393b9.png

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

66d0e80d5ab4daf8.png

W Edytorze ruchomych pojawiły się 3 nowe elementy interfejsu:

  1. 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 elementami start i end. Aby to zrobić, kliknij strzałkę między nimi.
  2. 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.
  3. 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.

  1. W panelu przeglądu wybierz start ConstraintSet

6e57661ed358b860.png

  1. W panelu wyboru kliknij red_star. Obecnie pokazuje źródło layout – 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.

f9564c574b86ea8.gif

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

2fce076cd7b04bd.png

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

  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 przy użyciu Edytora ruchu, dodaj ograniczenie do elementów bottom i end, klikając niebieskie przyciski +.

fd33c779ff83c80a.png

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

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

dff9ecdc1f4a0740.gif

Animacja: film przedstawiający odtwarzanie podglądu przejścia w edytorze animacji.

  1. Otwórz edytor animacji i wybierz przejście, klikając strzałkę między start a end w panelu Przegląd.

1dc541ae8c43b250.png

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

a0fd2593384dfb36.png

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.

  1. Otwórz edytor ruchu i wybierz przejście, klikając strzałkę między punktem początkowym i końcowym w panelu Przegląd.

b6f94b344ce65290.png

  1. Kliknij 699f7ae04024ccf6.png 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.
  2. W wyskakującym okienku wybierz Click Handler.

ccf92d06335105fe.png

  1. Zmień wartość Widok na kliknięcie na red_star.

b0d3f0c970604f01.png

  1. Kliknij Dodaj. Moduł obsługi kliknięć jest reprezentowany przez małą kropkę na pasku przejścia w edytorze animacji.

cec3913e67fb4105.png

  1. Po wybraniu przejścia w panelu przeglądu dodaj atrybut clickAction o wartości toggle do modułu obsługi OnClick dodanego w panelu atrybutów.

9af6fc60673d093d.png

  1. 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 z toggle przełączy się między stanem początkowym i końcowym po kliknięciu. Inne opcje dotyczące clickAction znajdziesz w dokumentacji.
  1. 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.

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

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

  1. Otwórz scenę ruchu xml/step2.xml, aby zdefiniować animację.
  2. 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ści 0.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ń

  1. Określ ograniczenie końcowe, aby użyć łańcucha do umieszczenia wszystkich 3 gwiazdek obok siebie pod spodem @id/credits. Dodatkowo wartość końcowa alpha lewej i prawej gwiazdki zostanie ustawiona 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>

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.

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

  1. Ponownie uruchom aplikację i otwórz ekran kroku 2. Zobaczysz animację.
  2. 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.

fefcdd690a0dcaec.gif

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

  1. Otwórz layout/activity_step3.xml i xml/step3.xml, aby zobaczyć obecny układ i scenę ruchu. ImageView i TextView wyświetlają informacje o księżycu i informacjach o autorach.
  2. Otwórz plik sceny ruchu (xml/step3.xml). Widać, że zdefiniowano zakres Transition od @id/start do @id/end. Animacja przesuwa obraz księżyca z lewego dolnego rogu ekranu do prawego dolnego rogu za pomocą 2 elementów ConstraintSets. Tekst informacji o twórcach i wykonawcach zmienia się od alpha="0.0" do alpha="1.0", gdy księżyc się porusza.
  3. 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.

  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żek, po ponownym uruchomieniu aplikacji zobaczysz ścieżki wszystkich widoków oznaczonych kreskowaną linią.

23bbb604f456f65c.png

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

  1. Otwórz aplikację xml/step3.xml i dodaj do sceny element KeyPosition. Tag KeyPosition znajduje się wewnątrz 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>

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 element KeyPosition – 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 element KeyPosition modyfikuje ścieżkę.
  • keyPositionType określa, jak KeyPosition modyfikuje ścieżkę. Może to być parentRelative, pathRelative lub deltaRelative (jak wyjaśniliśmy w następnym kroku).
  • percentX | percentY określa, o ile zmienić ścieżkę w framePosition (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.

46b179c01801f19e.gif

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?

1c7cf779931e45cc.gif

<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

a7b7568d46d9dec7.png

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

5680bf553627416c.png

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.

f3aaadaac8b4a93f.png

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 w pathRelative możesz zmienić, do którego punktu framePosition dojdzie do określonego punktu tej ścieżki. Dlatego parametr KeyPosition w punkcie framePosition="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, funkcja deltaRelative utworzy tylko 1 przydatny wymiar. Jednak pathRelative 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.

  1. Otwórz pokój xml/step4.xml. Zobaczysz, że ma te same widoki danych i KeyFrame dodane w ostatnim kroku.
  2. 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.

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

  1. 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 w 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ć za pomocą KeyPosition.

Oto przykład tworzenia złożonej ścieżki, która porusza się tam i z powrotem podczas animacji.

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

  1. 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.
  2. Aby księżyc się powiększał i obracał, dodaj dwa tagi KeyAttribute w elementach KeyFrameSet w miejscach keyFrame="50" i 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 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.

  1. 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 do keyPosition="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

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

2f4bfdd681c1fa98.gif

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

5fb6792126a09fda.gif

Definiowanie atrybutów niestandardowych

  1. Aby rozpocząć, otwórz plik xml/step6.xml, który zawiera tę samą animację utworzoną w ostatnim kroku.
  2. Aby zmienić kolor księżyca, dodaj dwa obiekty KeyAttribute z CustomAttribute w KeyFrameSet w punktach keyFrame="0", keyFrame="50" i 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 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 funkcja setColorFilter w usłudze Drawable 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

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

5fb6792126a09fda.gif

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

  1. 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"
/>
  1. 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ć.

ed96e3674854a548.gif

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?

56cd575c5c77eddd.png

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.

  1. Aby ta animacja była sterowana dotykiem, zmień touchAnchorSide na right.

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.

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

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

5458dff382261427.gif

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

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

  1. Uruchom aplikację i przejdź do kroku 8. Widać, że podczas przewijania tekstu księżyc się nie porusza.

Krok 2. Zadbaj o przewijanie elementu MotionLayout

  1. Aby widok MotionLayout był przewijany od razu po przewinięciu elementu NestedScrollView, dodaj motion:minHeight i motion:layout_scrollFlags do elementu MotionLayout.

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

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

  1. Wdróż aplikację ponownie i uruchom animację z kroku 8. Widać, że MotionLayout przewija animację zgodnie z pozycją przewijania.

ee5ce4d9e33a59ca.gif

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