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 rozbudowane możliwości układu. W ramach biblioteki ConstraintLayout MotionLayout jest dostępny jako biblioteka pomocy.

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

Rysunek 1. Podstawowy ruch sterowany dotykowo.

Oprócz opisu przejść między układami, MotionLayout umożliwia animowanie dowolnych właściwości układu. Co więcej, z założenia obsługuje przejściowe przejścia. Oznacza to, że możesz od razu pokazać dowolny punkt przejścia pod pewnymi warunkami, np. za pomocą dotyku. MotionLayout obsługuje też klatki kluczowe, dzięki czemu możesz w pełni dostosować przejścia do Twoich potrzeb.

Parametr MotionLayout jest w pełni deklaracyjny, co oznacza, że w pliku XML możesz opisywać dowolne przejścia, nawet te bardzo złożone.

Uwagi dotyczące projektu

MotionLayout służy do przesuwania i animowania elementów interfejsu, z którymi użytkownicy wchodzą w interakcje, takich jak przyciski i paski tytułu, oraz zmienianie ich rozmiaru. Nie używaj ruchu w aplikacji jako nieuzasadnionego efektu specjalnego. Używaj jej, aby pokazać użytkownikom, jak działa Twoja aplikacja. Więcej informacji na temat projektowania aplikacji z ruchem znajdziesz w sekcji Material Design Interpretowanie ruchu.

Rozpocznij

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

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

    Odlotowy

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

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.0-alpha13")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha13")
    }
    
  2. Utwórz plik MotionLayout: MotionLayout to podklasa klasy ConstraintLayout, więc możesz przekształcić dowolny istniejący obiekt ConstraintLayout w MotionLayout, zastępując nazwę klasy w pliku zasobów układu, jak 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 pokazany 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 MotionScene: w poprzednim przykładzie MotionLayout atrybut app:layoutDescription odwołuje się do sceny ruchu. Scena ruchu to plik zasobów XML. W elemencie głównym <MotionScene> scena ruchu zawiera wszystkie opisy ruchu dla danego układu. Aby oddzielić informacje o układzie od opisów ruchu, każdy element MotionLayout odwołuje się do innej sceny ruchu. Definicje zawarte w scenie ruchu mają pierwszeństwo przed podobnymi definicjami z MotionLayout.

    Oto przykładowy plik sceny ruchu opisujący podstawowy ruch w poziomie na ilustracji 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:constraintSetStart i motion:constraintSetEnd to odwołania do punktów końcowych ruchu. Te punkty końcowe są zdefiniowane w elementach <ConstraintSet> na późniejszym etapie sceny ruchu.

      • motion:duration określa czas w milisekundach, po jakim następuje ruch.

    • <OnSwipe> umożliwia skonfigurowanie sterowania dotykowego ruchu.

      • motion:touchAnchorId odnosi się do widoku, który użytkownik może przesuwać i przeciągać.

      • 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 rośnie w miarę przeciągania widoku w prawo.

    • <ConstraintSet> to miejsce, w którym definiujesz różne ograniczenia opisujące Twój ruch. W tym przykładzie każdy punkt końcowy ruchu ma jeden <ConstraintSet>. Te punkty końcowe są wyśrodkowane w pionie za pomocą właściwości app:layout_constraintTop_toTopOf="parent" i app:layout_constraintBottom_toBottomOf="parent". W poziomie znajdują się punkty końcowe po lewej i prawej stronie ekranu.

    Aby dowiedzieć się więcej o różnych elementach obsługiwanych przez scenę ruchu, zapoznaj się z przykładami Motion Layout.

Interpolowane atrybuty

Elementy ConstraintSet w pliku sceny ruchu mogą zawierać dodatkowe atrybuty, które są interpolowane podczas przejść. Oprócz pozycji i granic te atrybuty są interpolowane przez MotionLayout:

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

Atrybuty niestandardowe

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

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

Element <CustomAttribute> zawiera 2 własne atrybuty:

  • Metoda motion:attributeName jest wymagana i musi pasować do obiektu za pomocą metod pobierania i setera. Metody pobierające i ustawiające muszą pasować do konkretnego wzorca. Obsługiwany jest na przykład backgroundColor, ponieważ widok zawiera metody getBackgroundColor() i setBackgroundColor().
  • Kolejny atrybut, który musisz podać, zależy od typu wartości. Wybierz jeden z tych obsługiwanych typów:
    • motion:customColorValue – kolory
    • motion:customIntegerValue – liczby całkowite
    • motion:customFloatValue dla liczby zmiennoprzecinkowej
    • motion:customStringValue dla ciągów tekstowych
    • motion:customDimension – wymiary
    • motion:customBoolean – wartości logiczne

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

Zmiana koloru tła

Opierając się na poprzednim przykładzie, załóżmy, że kolory widoku zmieniają się wraz z ruchem, tak jak to widać na rys. 2.

Rysunek 2. Kolor tła widoku zmienia się wraz z poruszaniem się.

Dodaj element <CustomAttribute> do każdego elementu ConstraintSet w sposób podany 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 Motion Layout

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

  • app:applyMotionScene="boolean" wskazuje, czy zastosować daną scenę ruchu. Wartość domyślna tego atrybutu to true.
  • app:showPaths="boolean" wskazuje, czy pokazywać ścieżki animacji w trakcie ruchu. Wartość domyślna tego atrybutu to false.
  • app:progress="float" umożliwia jawne określanie postępu przenoszenia. Możesz użyć dowolnej wartości zmiennoprzecinkowej od 0 (początek przejścia) do 1 (koniec przejścia).
  • app:currentState="reference" umożliwia określenie konkretnego elementu ConstraintSet.
  • app:motionDebug pozwala wyświetlić dodatkowe informacje na temat debugowania ruchu. Możliwe wartości to "SHOW_PROGRESS", "SHOW_PATH" oraz "SHOW_ALL".

Dodatkowe materiały

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