Zarządzanie ruchem i animacją widżetu za pomocą Motion Layout

MotionLayout to typ układu, który ułatwia zarządzanie ruchem i animacją widżetów w aplikacji. MotionLayout to podklasa klasy ConstraintLayout, która wykorzystuje zaawansowane możliwości układu. W ramach biblioteki ConstraintLayout biblioteka MotionLayout jest dostępna jako biblioteka pomocy.

MotionLayout wypełnia lukę między przejściami układu i złożoną obsługą ruchu, oferując połączenie platformy animacji właściwości, TransitionManager i CoordinatorLayout.

Rysunek 1. podstawowe ruchy kontrolowane dotykiem;

Oprócz opisywania przejść między układami MotionLayout umożliwia animację dowolnych właściwości układu. Ponadto obsługuje przewijanie przejść. Oznacza to, że możesz od razu pokazać dowolny punkt przejścia w zależności od pewnych warunków, np. dotyku. MotionLayout obsługuje też klatki kluczowe, co umożliwia pełne dostosowanie przejść do swoich potrzeb.

MotionLayout jest w pełni deklaratywny, co oznacza, że możesz opisywać wszystkie przejścia w XML, niezależnie od ich złożoności.

Uwagi dotyczące projektu

MotionLayout służy do przemieszczania, zmiany rozmiaru i animowania elementów interfejsu, z którymi użytkownicy mogą wchodzić w interakcje, takich jak przyciski i paski tytułowe. Nie używaj w aplikacji ruchu jako nieuzasadnionego efektu specjalnego. Użyj go, aby pomóc użytkownikom zrozumieć, co robi Twoja aplikacja. Więcej informacji o projektowaniu aplikacji z użyciem animacji znajdziesz w sekcji Poznawanie animacji w artykule o projektowaniu Material Design.

Rozpocznij

Aby zacząć używać MotionLayout w projekcie, wykonaj te czynności.

  1. Dodaj zależność ConstraintLayout: aby używać MotionLayout w projekcie, dodaj zależność ConstraintLayout 2.0 do pliku build.gradle aplikacji. Jeśli używasz AndroidX, dodaj te zależności:

    Odlotowe

    dependencies {
        implementation "androidx.constraintlayout:constraintlayout:2.2.0-beta01"
        // To use constraintlayout in compose
        implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0-beta01"
    }
    

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.0-beta01")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-beta01")
    }
    
  2. Utwórz plik MotionLayout: MotionLayout jest podklasą ConstraintLayout, więc możesz przekształcić dowolny istniejący element ConstraintLayout w element MotionLayout, zastępując nazwę klasy w pliku zasobu układu, jak pokazano w tych przykładach:

    AndroidX

    <!-- before: ConstraintLayout -->
    <androidx.constraintlayout.widget.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <androidx.constraintlayout.motion.widget.MotionLayout .../>
              

    Biblioteka pomocy

    <!-- before: ConstraintLayout -->
    <android.support.constraint.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <android.support.constraint.motion.MotionLayout .../>
              

    Oto pełny przykład pliku MotionLayout, który definiuje układ widoczny na ilustracji 1:

    AndroidX

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <androidx.constraintlayout.motion.widget.MotionLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/motionLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene_01"
        tools:showPaths="true">
    
        <View
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            android:text="Button" />
    
    </androidx.constraintlayout.motion.widget.MotionLayout>
            

    Biblioteka pomocy

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <android.support.constraint.motion.MotionLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/motionLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene_01"
        tools:showPaths="true">
    
        <View
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            android:text="Button" />
    
    </android.support.constraint.motion.MotionLayout>
            
  3. Utwórz scenę animacji: w poprzednim przykładzie MotionLayout atrybut app:layoutDescription odwołuje się do sceny animacji. Scena animacji to plik zasobu XML. Scena ruchu w swoim elemencie głównym <MotionScene> zawiera wszystkie opisy ruchu dla odpowiedniego układu. Aby oddzielić informacje o układzie od opisów animacji, każdy element MotionLayout odwołuje się do osobnej sceny animacji. Definicje w scenie ruchu mają pierwszeństwo przed podobnymi definicjami w sekcji MotionLayout.

    Oto przykładowy plik sceny ruchu, który opisuje podstawowy ruch poziomy na rysunku 1:

    <?xml version="1.0" encoding="utf-8"?>
    <MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:motion="http://schemas.android.com/apk/res-auto">
    
        <Transition
            motion:constraintSetStart="@+id/start"
            motion:constraintSetEnd="@+id/end"
            motion:duration="1000">
            <OnSwipe
                motion:touchAnchorId="@+id/button"
                motion:touchAnchorSide="right"
                motion:dragDirection="dragRight" />
        </Transition>
    
        <ConstraintSet android:id="@+id/start">
            <Constraint
                android:id="@+id/button"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_marginStart="8dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </ConstraintSet>
    
        <ConstraintSet android:id="@+id/end">
            <Constraint
                android:id="@+id/button"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_marginEnd="8dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toEndOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </ConstraintSet>
    
    </MotionScene>
        

    Uwaga:

    • <Transition> zawiera podstawową definicję pozwu.

      • motion:constraintSetStartmotion:constraintSetEnd to odniesienia do punktów końcowych ruchu. Te punkty końcowe są określone w elementach <ConstraintSet> w dalszej części sceny ruchu.

      • motion:duration określa czas (w milisekundach) potrzebny do ukończenia ruchu.

    • <OnSwipe>umożliwia utworzenie sterowania dotykowego dla ruchu.

      • motion:touchAnchorId – widok, który użytkownik może przesuwać i przeciągać.

      • motion:touchAnchorSide oznacza, że widok jest przeciągany od prawej strony.

      • motion:dragDirection oznacza kierunek postępu przeciągania. Na przykład: motion:dragDirection="dragRight" oznacza, że postęp zwiększa się, gdy widok jest przeciągany w prawo.

    • <ConstraintSet> to miejsce, w którym definiujesz różne ograniczenia opisujące ruch. W tym przykładzie dla każdego punktu końcowego animacji zdefiniowano <ConstraintSet>. Te punkty końcowe są wyrównane pionowo za pomocą znaczników app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent". W poziomie punkty końcowe znajdują się po lewej i prawej stronie ekranu.

    Więcej informacji o różnych elementach obsługiwanych przez scenę Motion znajdziesz w artykule Przykłady MotionLayout.

Atrybuty interpolowane

W pliku sceny animacji elementy ConstraintSet mogą zawierać dodatkowe atrybuty, które są interpolowane podczas przejścia. Oprócz pozycji i zakresów te atrybuty są interpolowane za pomocą wartości MotionLayout:

  • alpha
  • visibility
  • elevation
  • rotation, rotationX, rotationY
  • translationX, translationY, translationZ
  • scaleX, scaleY

Atrybuty niestandardowe

W elemencie <Constraint> możesz użyć elementu <CustomAttribute>, aby określić przejście dla atrybutów, które nie są powiązane tylko z pozycją lub atrybutami View.

<Constraint
    android:id="@+id/button" ...>
    <CustomAttribute
        motion:attributeName="backgroundColor"
        motion:customColorValue="#D81B60"/>
</Constraint>

Element <CustomAttribute> zawiera 2 atrybuty:

  • motion:attributeName jest wymagany i musi być zgodny z obiektem z metodami getter i setter. Metody getter i setter muszą być zgodne z określonym wzorcem. Obsługiwany jest na przykład backgroundColor, ponieważ widok danych zawiera metody getBackgroundColor() i setBackgroundColor().
  • Inny atrybut, który musisz podać, zależy od typu wartości. Wybierz jeden z obsługiwanych typów:
    • motion:customColorValue dla kolorów
    • motion:customIntegerValue dla liczb całkowitych
    • motion:customFloatValue dla pływaków
    • motion:customStringValue – ciągi
    • motion:customDimension w przypadku wymiarów
    • motion:customBoolean w przypadku wartości logicznych

Określając atrybut niestandardowy, określ wartości punktów końcowych zarówno w początkowym, jak i końcowym elemencie <ConstraintSet>.

Zmiana koloru tła

Kontynuując poprzedni przykład, załóżmy, że chcesz zmieniać kolory widoku w ramach jego ruchu, jak widać na rysunku 2.

Rysunek 2. Widok zmienia kolor tła podczas przemieszczania.

Dodaj element <CustomAttribute> do każdego elementu ConstraintSet, jak pokazano w tym fragmencie kodu:

<ConstraintSet android:id="@+id/start">
    <Constraint
        android:id="@+id/button"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginStart="8dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintStart_toStartOf="parent"
        motion:layout_constraintTop_toTopOf="parent">
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#D81B60" />
    </Constraint>
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/button"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginEnd="8dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintEnd_toEndOf="parent"
        motion:layout_constraintTop_toTopOf="parent">
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#9999FF" />
    </Constraint>
</ConstraintSet>

Dodatkowe atrybuty MotionLayout

Oprócz atrybutów z poprzedniego przykładu MotionLayout ma też inne atrybuty, które możesz określić:

  • app:applyMotionScene="boolean" wskazuje, czy chcesz zastosować scenę ruchu. Domyślna wartość tego atrybutu to true.
  • app:showPaths="boolean" wskazuje, czy ścieżki ruchu mają być wyświetlane podczas odtwarzania ruchu. Domyślna wartość tego atrybutu to false.
  • app:progress="float" umożliwia określenie postępu przejścia. Możesz użyć dowolnej wartości zmiennoprzecinkowej z zakresu 0 (początek przejścia) do 1 (koniec przejścia).
  • app:currentState="reference" umożliwia określenie konkretnego ConstraintSet.
  • app:motionDebug pozwala wyświetlić dodatkowe informacje na potrzeby debugowania dotyczącego animacji. Możliwe wartości to "SHOW_PROGRESS", "SHOW_PATH" i "SHOW_ALL".

Dodatkowe materiały

Więcej informacji o funkcji MotionLayout znajdziesz w tych materiałach: