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

Wypróbuj metodę Compose
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak używać animacji w Compose.

MotionLayout to typ układu, który pomaga zarządzać ruchem i animacją widżetów w aplikacji.MotionLayout to podklasa ConstraintLayout, która wykorzystuje jej rozbudowane możliwości układu. W ramach biblioteki ConstraintLayout dostępna jest biblioteka pomocy MotionLayout.

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

Rysunek 1. Podstawowe sterowanie ruchem za pomocą dotyku.

Oprócz opisywania przejść między układami MotionLayout umożliwia animowanie dowolnych właściwości układu. Ponadto obsługuje on przejścia, które można przewijać. Oznacza to, że możesz natychmiast wyświetlić dowolny punkt w przejściu na podstawie określonego warunku, np. dotknięcia. MotionLayout obsługuje też klatki kluczowe, co umożliwia w pełni dostosowane przejścia do Twoich potrzeb.

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

Uwagi dotyczące projektu

MotionLayout służy do przesuwania, zmiany rozmiaru i animowania elementów interfejsu, z którymi użytkownicy wchodzą w interakcję, takich jak przyciski i paski tytułu. Nie używaj ruchu w aplikacji jako niepotrzebnego efektu specjalnego. Używaj go, aby pomóc użytkownikom zrozumieć, co robi Twoja aplikacja. Więcej informacji o projektowaniu aplikacji z animacjami znajdziesz w sekcji Understanding motion (Zrozumienie animacji) w Material Design.

Rozpocznij

Aby zacząć korzystać z 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 następującą zależność:

    Odlotowe

    dependencies {
        implementation "androidx.constraintlayout:constraintlayout:2.2.1"
        // To use constraintlayout in compose
        implementation "androidx.constraintlayout:constraintlayout-compose:1.1.1"
    }

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.1")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.1")
    }
  2. Utwórz plik MotionLayout: MotionLayout jest podklasą ConstraintLayout, więc możesz przekształcić dowolny istniejący element ConstraintLayoutMotionLayout, 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 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 MotionScene: w poprzednim przykładzie MotionLayout atrybut app:layoutDescription odwołuje się do sceny ruchu. Scena ruchu to plik zasobu XML. W elemencie głównym <MotionScene> scena ruchu zawiera wszystkie opisy ruchu dla odpowiedniego układu. Aby oddzielić informacje o układzie od opisów ruchu, każdy element MotionLayout odnosi się do osobnej sceny ruchu. Definicje w scenie ruchu 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ę ruchu.

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

      • motion:duration określa liczbę milisekund potrzebnych do zakończenia ruchu.

    • <OnSwipe> umożliwia tworzenie sterowania dotykowego ruchem.

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

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

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

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

    Szczegółowe informacje o różnych elementach obsługiwanych przez scenę ruchu znajdziesz w przykładach MotionLayout.

Interpolowane atrybuty

W pliku sceny ruchu elementy ConstraintSet mogą zawierać dodatkowe atrybuty, które są interpolowane podczas przejścia. Oprócz pozycji i obszaru MotionLayout interpoluje te atrybuty:

  • 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 z pozycją lub atrybutami View.

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

Element <CustomAttribute> ma 2 atrybuty:

  • Wymagany jest parametr motion:attributeName, który musi być zgodny z obiektem z metodami pobierania i ustawiania. Funkcje pobierające i ustawiające muszą pasować do określonego wzorca. Na przykład backgroundColor jest obsługiwane, ponieważ widok ma metody bazowe getBackgroundColor()setBackgroundColor().
  • Inny atrybut, który musisz podać, zależy od typu wartości. Wybierz jeden z tych obsługiwanych typów:
    • motion:customColorValue kolorów
    • motion:customIntegerValue dla liczb całkowitych
    • motion:customFloatValue w przypadku liczb zmiennoprzecinkowych,
    • motion:customStringValue w przypadku ciągów znaków,
    • motion:customDimension w przypadku wymiarów
    • motion:customBoolean w przypadku wartości logicznych

Podczas określania atrybutu niestandardowego zdefiniuj wartości punktu końcowego w elementach <ConstraintSet> i <ConstraintSet>.

Zmiana koloru tła

W nawiązaniu do poprzedniego przykładu załóżmy, że chcesz, aby kolory widoku zmieniały się w ramach jego ruchu, jak pokazano na rysunku 2.

Rysunek 2. Podczas przesuwania widok zmienia kolor tła.

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 inne atrybuty, które możesz określić:

  • app:applyMotionScene="boolean" wskazuje, czy zastosować scenę ruchu. Domyślna wartość tego atrybutu to true.
  • app:showPaths="boolean" określa, czy ścieżki ruchu mają być wyświetlane podczas ruchu. Domyślna wartość tego atrybutu to false.
  • app:progress="float" umożliwia wyraźne określenie postępu przejścia. Możesz użyć dowolnej wartości zmiennoprzecinkowej z zakresu od 0 (początek przejścia) do 1 (koniec przejścia).
  • app:currentState="reference" umożliwia określenie konkretnego ConstraintSet.
  • app:motionDebug umożliwia wyświetlanie dodatkowych informacji na potrzeby debugowania dotyczących ruchu. Możliwe wartości to "SHOW_PROGRESS", "SHOW_PATH" lub "SHOW_ALL".

Dodatkowe materiały

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