Quản lý ảnh động chuyển động và tiện ích bằng MotionLayout

MotionLayout là một loại bố cục giúp bạn quản lý chuyển động và ảnh động tiện ích trong ứng dụng. MotionLayout là một lớp con của ConstraintLayout và dựa trên các chức năng bố cục phong phú của lớp này. Là một phần của thư viện ConstraintLayout, MotionLayout có sẵn dưới dạng thư viện hỗ trợ.

MotionLayout giúp kết nối khoảng cách giữa các hiệu ứng chuyển đổi bố cục và tính năng xử lý chuyển động phức tạp, cung cấp một tổ hợp các tính năng giữa khung ảnh động thuộc tính, TransitionManagerCoordinatorLayout.

Hình 1. Chuyển động cơ bản được điều khiển bằng thao tác chạm.

Ngoài việc mô tả các hiệu ứng chuyển đổi giữa các bố cục, MotionLayout còn cho phép bạn tạo ảnh động cho bất kỳ thuộc tính bố cục nào. Hơn nữa, tính năng này vốn hỗ trợ các hiệu ứng chuyển đổi có thể tua lại. Điều này có nghĩa là bạn có thể hiển thị tức thì bất kỳ điểm nào trong quá trình chuyển đổi dựa trên một số điều kiện, chẳng hạn như đầu vào cảm ứng. MotionLayout cũng hỗ trợ các khung hình chính, cho phép chuyển đổi tuỳ chỉnh hoàn toàn cho phù hợp với nhu cầu của bạn.

MotionLayout mang tính khai báo đầy đủ, nghĩa là bạn có thể mô tả mọi hiệu ứng chuyển đổi trong XML, bất kể phức tạp đến mức nào.

Cân nhắc về thiết kế

MotionLayout dùng để di chuyển, đổi kích thước và tạo ảnh động cho các phần tử trên giao diện người dùng mà người dùng tương tác, chẳng hạn như nút và thanh tiêu đề. Đừng sử dụng chuyển động trong ứng dụng của bạn làm hiệu ứng đặc biệt vô bổ. Sử dụng thông báo này để giúp người dùng hiểu được ứng dụng của bạn đang làm gì. Để biết thêm thông tin về cách thiết kế ứng dụng có chuyển động, hãy xem phần Tìm hiểu về chuyển động trong Material Design.

Bắt đầu

Hãy làm theo các bước sau để bắt đầu sử dụng MotionLayout trong dự án của bạn.

  1. Thêm phần phụ thuộc ConstraintLayout: để sử dụng MotionLayout trong dự án, hãy thêm phần phụ thuộc ConstraintLayout 2.0 vào tệp build.gradle của ứng dụng. Nếu bạn đang sử dụng AndroidX, hãy thêm phần phụ thuộc sau:

    Groovy

    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. Tạo tệp MotionLayout: MotionLayout là một lớp con của ConstraintLayout, vì vậy, bạn có thể chuyển đổi mọi ConstraintLayout hiện có thành MotionLayout bằng cách thay thế tên lớp trong tệp tài nguyên bố cục, như trong các ví dụ sau:

    AndroidX

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

    Thư viện hỗ trợ

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

    Dưới đây là ví dụ đầy đủ về tệp MotionLayout, xác định bố cục hiển thị trong hình 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>
            

    Thư viện hỗ trợ

    <?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. Tạo MotionScene: trong ví dụ MotionLayout trước, thuộc tính app:layoutDescription tham chiếu đến một cảnh chuyển động. Cảnh chuyển động là một tệp tài nguyên XML. Trong phần tử gốc <MotionScene>, cảnh chuyển động chứa tất cả nội dung mô tả chuyển động cho bố cục tương ứng. Để tách riêng thông tin bố cục với nội dung mô tả chuyển động, mỗi MotionLayout sẽ tham chiếu đến một cảnh chuyển động riêng biệt. Các định nghĩa trong cảnh chuyển động được ưu tiên hơn mọi định nghĩa tương tự trong MotionLayout.

    Dưới đây là tệp cảnh chuyển động mẫu mô tả chuyển động ngang cơ bản trong hình 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>
        

    Xin lưu ý những điều sau:

    • <Transition> chứa định nghĩa cơ sở của chuyển động.

      • motion:constraintSetStartmotion:constraintSetEnd là các tệp tham chiếu đến các điểm cuối của chuyển động. Các điểm cuối này được xác định trong các phần tử <ConstraintSet> sau trong cảnh chuyển động.

      • motion:duration chỉ định số mili giây cần thiết để hoàn tất chuyển động.

    • <OnSwipe> cho phép bạn tạo tính năng điều khiển cảm ứng cho chuyển động.

      • motion:touchAnchorId đề cập đến thành phần hiển thị mà người dùng có thể vuốt và kéo.

      • motion:touchAnchorSide có nghĩa là thành phần hiển thị đang được kéo từ phía bên phải.

      • motion:dragDirection cho biết hướng tiến trình của thao tác kéo. Ví dụ: motion:dragDirection="dragRight" có nghĩa là tiến trình sẽ tăng khi khung hiển thị được kéo sang phải.

    • <ConstraintSet> là nơi bạn xác định các điều kiện ràng buộc mô tả chuyển động của mình. Trong ví dụ này, một <ConstraintSet> được xác định cho mỗi điểm cuối của chuyển động. Các điểm cuối này được căn giữa theo chiều dọc bằng cách sử dụng app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent". Theo chiều ngang, các điểm cuối nằm ở phía bên trái và bên phải của màn hình.

    Để xem chi tiết hơn về các phần tử mà cảnh chuyển động hỗ trợ, hãy xem ví dụ về MotionLayout.

Thuộc tính nội suy

Trong tệp cảnh chuyển động, các phần tử ConstraintSet có thể chứa các thuộc tính bổ sung được nội suy trong quá trình chuyển đổi. Ngoài vị trí và giới hạn, MotionLayout còn nội suy các thuộc tính sau:

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

Thuộc tính tùy chỉnh

Trong <Constraint>, bạn có thể sử dụng phần tử <CustomAttribute> để chỉ định chuyển đổi cho các thuộc tính không chỉ liên quan đến vị trí hoặc thuộc tính View.

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

<CustomAttribute> chứa hai thuộc tính riêng:

  • motion:attributeName là bắt buộc và phải so khớp một đối tượng với các phương thức getter và setter. Phương thức getter và setter phải khớp với một mẫu cụ thể. Ví dụ: backgroundColor được hỗ trợ vì khung hiển thị có các phương thức getBackgroundColor()setBackgroundColor() cơ bản.
  • Thuộc tính khác mà bạn phải cung cấp dựa trên loại giá trị. Chọn trong số các loại được hỗ trợ sau:
    • motion:customColorValue cho màu
    • motion:customIntegerValue cho số nguyên
    • motion:customFloatValue cho số thực
    • motion:customStringValue cho chuỗi
    • motion:customDimension cho phương diện
    • motion:customBoolean cho boolean

Khi chỉ định một thuộc tính tuỳ chỉnh, hãy xác định các giá trị điểm cuối trong cả phần tử <ConstraintSet> đầu và cuối.

Thay đổi màu nền

Dựa trên ví dụ trước, giả sử bạn muốn màu sắc của thành phần hiển thị thay đổi trong quá trình chuyển động của thành phần hiển thị đó, như minh hoạ trong hình 2.

Hình 2. Thành phần hiển thị sẽ thay đổi màu nền khi di chuyển.

Thêm một phần tử <CustomAttribute> vào mỗi phần tử ConstraintSet, như minh hoạ trong đoạn mã sau đây:

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

Các thuộc tính MotionLayout bổ sung

Ngoài các thuộc tính trong ví dụ trước, MotionLayout có các thuộc tính khác mà bạn có thể muốn chỉ định:

  • app:applyMotionScene="boolean" cho biết liệu có áp dụng cảnh chuyển động hay không. Giá trị mặc định của thuộc tính này là true.
  • app:showPaths="boolean" cho biết liệu có hiển thị các đường dẫn chuyển động khi chuyển động đang chạy hay không. Giá trị mặc định của thuộc tính này là false.
  • app:progress="float" cho phép bạn chỉ định rõ tiến trình chuyển đổi. Bạn có thể sử dụng bất kỳ giá trị dấu phẩy động nào từ 0 (bắt đầu chuyển đổi) đến 1 (kết thúc chuyển đổi).
  • app:currentState="reference" cho phép bạn chỉ định một ConstraintSet cụ thể.
  • app:motionDebug cho phép bạn hiển thị thêm thông tin gỡ lỗi về chuyển động. Các giá trị có thể là "SHOW_PROGRESS", "SHOW_PATH" hoặc "SHOW_ALL".

Tài nguyên khác

Để biết thêm thông tin về MotionLayout, hãy xem các tài nguyên sau: