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

Wypróbuj tworzenie wiadomości
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak korzystać z animacji w Compose.

MotionLayout to typ układu, który ułatwia zarządzanie animacjami i animowanymi widżetami w aplikacji. MotionLayout jest podklasą ConstraintLayout i rozszerza jego bogate 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 a zaawansowanymi funkcjami animacji, oferując połączenie funkcji ramy animacji obiektu (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 natychmiast wyświetlić dowolny punkt w ramach przejścia na podstawie określonego warunku, 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 określa układ widoczny na rysunku 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 informacje o układzie były oddzielone od opisów animacji, każda wartość MotionLayout odnosi się do osobnej sceny animacji. Definicje w scenie z animacją mają pierwszeństwo przed podobnymi definicjami w 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 końców animacji. Te punkty końcowe są zdefiniowane w elementach <ConstraintSet> w późniejszej części sceny ruchu.

      • motion:duration określa liczbę milisekund, w jakiej ma trwać animacja.

    • <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 odnosi się do kierunku przesuwania. 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". Punkty końcowe w osi poziomej znajdują się na lewej i prawej krawędzi ekranu.

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

Atrybuty interpolowane

W pliku sceny ruchu 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ą 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. Na przykład backgroundColor jest obsługiwana, ponieważ widok ma metody getBackgroundColor()setBackgroundColor().
  • Drugi 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 zmiennych typu float
    • motion:customStringValue dla ciągów tekstowych
    • motion:customDimension w przypadku wymiarów
    • motion:customBoolean w przypadku wartości logicznych

Podczas określania atrybutu niestandardowego definiuj wartości punktów końcowych zarówno w elementach start <ConstraintSet>, jak i end <ConstraintSet>.

Zmiana koloru tła

Wracając do poprzedniego przykładu, załóżmy, że chcesz zmienić kolory obrazu w ramach jego ruchu, jak pokazano 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 ma być zastosowana scena 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ące tego projektu. Możliwe wartości to "SHOW_PROGRESS", "SHOW_PATH" i "SHOW_ALL".

Dodatkowe materiały

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