Android nâng cao trong Kotlin 03.2: Ảnh động với MotionLayout

1. Trước khi bắt đầu

Lớp học lập trình này trong khoá học Kiến thức nâng cao về cách tạo ứng dụng Android bằng Kotlin. Bạn sẽ nhận được nhiều giá trị nhất từ khoá học này nếu thực hiện các lớp học lập trình theo trình tự (nhưng không bắt buộc). Tất cả lớp học lập trình của khoá học có trên trang đích của lớp học lập trình Android bằng Kotlin nâng cao.

MotionLayout là một thư viện cho phép bạn thêm chuyển động đa dạng thức vào ứng dụng Android. API này dựa trên ConstraintLayout, và cho phép bạn tạo ảnh động cho mọi nội dung mà bạn có thể tạo bằng ConstraintLayout.

Bạn có thể sử dụng MotionLayout để tạo ảnh động cho vị trí, kích thước, chế độ hiển thị, độ đậm nhạt, màu sắc, độ nâng, xoay và các thuộc tính khác của nhiều khung hiển thị cùng một lúc. Khi sử dụng XML khai báo, bạn có thể tạo các ảnh động phối hợp, bao gồm nhiều thành phần hiển thị mà khó có được trong mã.

Ảnh động là một cách hay để nâng cao trải nghiệm trong ứng dụng. Bạn có thể sử dụng ảnh động để:

  • Show changes (Hiện các thay đổi) – việc tạo ảnh động giữa các trạng thái cho phép người dùng theo dõi các thay đổi trong giao diện người dùng của bạn một cách tự nhiên.
  • Thu hút sự chú ý – sử dụng ảnh động để thu hút sự chú ý đến các phần tử quan trọng trên giao diện người dùng.
  • Xây dựng thiết kế đẹp mắt—chuyển động hiệu quả trong thiết kế giúp ứng dụng trông đẹp mắt.

Điều kiện tiên quyết

Lớp học lập trình này được thiết kế dành cho các nhà phát triển có một số kinh nghiệm phát triển Android. Trước khi cố gắng hoàn tất lớp học lập trình này, bạn nên:

  • Biết cách tạo ứng dụng có hoạt động, bố cục cơ bản và chạy ứng dụng đó trên thiết bị hoặc trình mô phỏng bằng Android Studio. Làm quen với ConstraintLayout. Hãy đọc qua lớp học lập trình Bố cục ràng buộc để tìm hiểu thêm về ConstraintLayout.

Bạn sẽ thực hiện

  • Xác định ảnh động bằng ConstraintSetsMotionLayout
  • Tạo ảnh động dựa trên sự kiện kéo
  • Thay đổi ảnh động bằng KeyPosition
  • Thay đổi thuộc tính bằng KeyAttribute
  • Chạy ảnh động bằng mã
  • Tạo ảnh động cho tiêu đề có thể thu gọn bằng MotionLayout

Bạn cần có

  • Android Studio 4.0 (Trình chỉnh sửa MotionLayout chỉ hoạt động với phiên bản Android Studio này.)

2. Bắt đầu

Để tải ứng dụng mẫu xuống, bạn có thể:

... hoặc sao chép kho lưu trữ GitHub qua dòng lệnh bằng cách dùng lệnh sau:

$ git clone https://github.com/googlecodelabs/motionlayout.git

3. Tạo ảnh động bằng MotionLayout

Trước tiên, bạn sẽ tạo ảnh động di chuyển khung hiển thị từ đầu màn hình xuống cuối màn hình để phản hồi nhấp chuột của người dùng.

Để tạo ảnh động bằng mã khởi đầu, bạn cần có những phần chính sau:

  • MotionLayout, là lớp con của ConstraintLayout. Bạn chỉ định tất cả các khung hiển thị phải được tạo ảnh động bên trong thẻ MotionLayout.
  • MotionScene, là một tệp XML mô tả ảnh động cho MotionLayout.
  • Transition, nằm trong MotionScene chỉ định thời lượng ảnh động, điều kiện kích hoạt và cách di chuyển khung hiển thị.
  • ConstraintSet chỉ định cả giới hạn bắt đầukết thúc của quá trình chuyển đổi.

Hãy lần lượt xem xét từng yếu tố, bắt đầu với MotionLayout.

Bước 1: Tìm hiểu mã hiện có

MotionLayout là lớp con của ConstraintLayout nên hỗ trợ tất cả các tính năng tương tự khi thêm ảnh động. Để sử dụng MotionLayout, hãy thêm khung hiển thị MotionLayout để dùng ConstraintLayout.

  1. Trong res/layout, mở activity_step1.xml. Ở đây, bạn có ConstraintLayout với một ImageView ngôi sao duy nhất, với một sắc thái màu được áp dụng bên trong.

activity_step1.xml

<!-- initial code -->
<androidx.constraintlayout.widget.ConstraintLayout
       ...
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       >

   <ImageView
           android:id="@+id/red_star"
           ...
   />

</androidx.constraintlayout.motion.widget.MotionLayout>

ConstraintLayout này không có bất kỳ hạn chế nào nên nếu chạy ứng dụng ngay bây giờ, bạn sẽ thấy màn hình sao không bị giới hạn, tức là chúng sẽ được đặt ở một vị trí không xác định. Android Studio sẽ cung cấp cho bạn cảnh báo về việc thiếu các quy tắc ràng buộc.

Bước 2: Chuyển đổi sang bố cục chuyển động

Để tạo ảnh động bằng MotionLayout,, bạn phải chuyển đổi ConstraintLayout thành MotionLayout.

Để bố cục sử dụng cảnh chuyển động, bố cục phải trỏ vào cảnh đó.

  1. Để thực hiện việc này, hãy mở giao diện thiết kế. Trong Android Studio 4.0, bạn mở giao diện thiết kế bằng cách sử dụng biểu tượng phân tách hoặc thiết kế ở trên cùng bên phải khi xem tệp XML bố cục.

a2beea710c2decb7.png

  1. Sau khi mở giao diện thiết kế, hãy nhấp chuột phải vào bản xem trước rồi chọn Convert to MotionLayout (Chuyển đổi sang MotionLayout).

4fa936a98a8393b9.pngS

Thao tác này sẽ thay thế thẻ ConstraintLayout bằng thẻ MotionLayout và thêm motion:layoutDescription vào thẻ MotionLayout trỏ đến @xml/activity_step1_scene.

activity_step1**.xml**

<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/activity_step1_scene">

Cảnh chuyển động là một tệp XML mô tả ảnh động trong MotionLayout.

Ngay khi bạn chuyển đổi sang MotionLayout, giao diện thiết kế sẽ hiển thị Trình chỉnh sửa chuyển động

66d0e80d5ab4daf8.pngS

Có ba thành phần mới trên giao diện người dùng trong Trình chỉnh sửa chuyển động:

  1. Tổng quan – Đây là tuỳ chọn phương thức cho phép bạn chọn các phần khác nhau của ảnh động. Trong hình này, start ConstraintSet được chọn. Bạn cũng có thể chọn hiệu ứng chuyển đổi giữa startend bằng cách nhấp vào mũi tên ở giữa.
  2. Mục – Bên dưới phần tổng quan là một cửa sổ mục sẽ thay đổi dựa trên mục tổng quan hiện được chọn. Trong hình ảnh này, thông tin start ConstraintSet được hiển thị trong cửa sổ chọn.
  3. Thuộc tính – Bảng thuộc tính hiển thị và cho phép bạn chỉnh sửa các thuộc tính của mục được chọn hiện tại từ cửa sổ tổng quan hoặc cửa sổ lựa chọn. Trong hình này, nó cho thấy các thuộc tính của start ConstraintSet.

Bước 3: Xác định các điều kiện ràng buộc bắt đầu và kết thúc

Tất cả ảnh động đều có thể được xác định theo điểm bắt đầu và kết thúc. Phần bắt đầu mô tả giao diện của màn hình trước ảnh động, còn phần cuối mô tả giao diện của màn hình sau khi ảnh động hoàn tất. MotionLayout chịu trách nhiệm tìm hiểu cách tạo ảnh động giữa trạng thái bắt đầu và trạng thái kết thúc (theo thời gian).

MotionScene dùng thẻ ConstraintSet để xác định trạng thái bắt đầu và trạng thái kết thúc. ConstraintSet là một tập hợp các điều kiện ràng buộc có thể áp dụng cho các khung hiển thị. Các quy tắc này bao gồm các điều kiện ràng buộc đối với chiều rộng, chiều cao và ConstraintLayout. Tệp này cũng bao gồm một số thuộc tính như alpha. Lớp này không chứa chính các khung hiển thị mà chỉ chứa các điều kiện ràng buộc đối với các khung hiển thị đó.

Mọi điều kiện ràng buộc được chỉ định trong ConstraintSet sẽ ghi đè các điều kiện ràng buộc được chỉ định trong tệp bố cục. Nếu bạn xác định các quy tắc ràng buộc trong cả bố cục và MotionScene, thì chỉ các quy tắc ràng buộc trong MotionScene được áp dụng.

Trong bước này, bạn sẽ ràng buộc khung hiển thị dấu sao bắt đầu ở đầu màn hình và kết thúc ở điểm cuối màn hình.

Bạn có thể hoàn tất bước này bằng Trình chỉnh sửa chuyển động hoặc chỉnh sửa trực tiếp văn bản của activity_step1_scene.xml.

  1. Chọn ConstraintSet start trong bảng điều khiển tổng quan

6e57661ed358b860.pngS

  1. Trong bảng selection (lựa chọn), hãy chọn red_star. Hiện tại, bảng này cho thấy Nguồn của layout – tức là không bị hạn chế trong ConstraintSet này. Sử dụng biểu tượng bút chì ở phía trên bên phải để Tạo quy tắc ràng buộc

f9564c574b86ea8.gif

  1. Xác nhận rằng red_star cho thấy Nguồn là start khi bạn chọn start ConstraintSet trong bảng điều khiển tổng quan.
  2. Trong bảng điều khiển Attributes (Thuộc tính), với red_star được chọn trong start ConstraintSet, hãy thêm một Constraint ở trên cùng và bắt đầu bằng cách nhấp vào các nút + màu xanh dương.

2fce076cd7b04bd.pngS

  1. Mở xml/activity_step1_scene.xml để xem mã mà Trình chỉnh sửa chuyển động đã tạo cho quy tắc ràng buộc này.

activity_step1_scene.xml

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

ConstraintSetid@id/start và chỉ định mọi điều kiện ràng buộc cần áp dụng cho mọi khung hiển thị trong MotionLayout. Vì MotionLayout này chỉ có một thành phần hiển thị nên chỉ cần một Constraint.

Constraint bên trong ConstraintSet chỉ định mã nhận dạng của khung hiển thị mà nó đang ràng buộc, @id/red_star được xác định trong activity_step1.xml. Điều quan trọng cần lưu ý là thẻ Constraint chỉ xác định các điều kiện ràng buộc và thông tin bố cục. Thẻ Constraint không biết rằng thẻ này đang được áp dụng cho ImageView.

Quy tắc ràng buộc này chỉ định chiều cao, chiều rộng và 2 điều kiện ràng buộc khác cần thiết để ràng buộc khung hiển thị red_star với điểm bắt đầu trên cùng của thành phần mẹ.

  1. Chọn ConstraintSet end trong bảng điều khiển tổng quan.

346e1248639b6f1e.png.

  1. Làm theo các bước tương tự như bạn đã làm trước đó để thêm Constraint cho red_star trong end ConstraintSet.
  2. Để sử dụng Trình chỉnh sửa chuyển động nhằm hoàn tất bước này, hãy thêm một điều kiện ràng buộc vào bottomend bằng cách nhấp vào nút + màu xanh dương.

fd33c779ff83c80a.png

  1. Mã trong XML có dạng như sau:

activitiy_step1_scene.xml

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

Giống như @id/start, ConstraintSet này có một Constraint duy nhất trên @id/red_star. Lần này, thành phần này ràng buộc thành phần hiển thị ở cuối màn hình.

Bạn không cần phải đặt tên cho chúng là @id/start@id/end nhưng sẽ rất tiện lợi.

Bước 4: Xác định hiệu ứng chuyển đổi

Mỗi MotionScene cũng phải có ít nhất một hiệu ứng chuyển đổi. Hiệu ứng chuyển đổi xác định mọi phần của một ảnh động, từ đầu đến cuối.

Quá trình chuyển đổi phải chỉ định điểm bắt đầu và kết thúc ConstraintSet cho quá trình chuyển đổi. Hiệu ứng chuyển đổi cũng có thể chỉ định cách sửa đổi ảnh động theo những cách khác, chẳng hạn như khoảng thời gian chạy ảnh động hoặc cách tạo ảnh động bằng cách kéo khung hiển thị.

  1. Theo mặc định, Trình chỉnh sửa chuyển động đã tạo hiệu ứng chuyển đổi cho chúng ta khi tạo tệp MotionScene. Mở activity_step1_scene.xml để xem hiệu ứng chuyển đổi đã tạo.

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
   motion:constraintSetEnd="@+id/end"
   motion:constraintSetStart="@id/start"
   motion:duration="1000">
  <KeyFrameSet>
  </KeyFrameSet>
</Transition>

Đây là tất cả những gì MotionLayout cần để tạo ảnh động. Xem từng thuộc tính:

  • constraintSetStart sẽ được áp dụng cho các thành phần hiển thị khi ảnh động bắt đầu.
  • constraintSetEnd sẽ được áp dụng cho các khung hiển thị ở cuối ảnh động.
  • duration chỉ định thời lượng ảnh động sẽ diễn ra tính bằng mili giây.

Sau đó, MotionLayout sẽ chỉ ra một đường dẫn giữa giới hạn bắt đầu và kết thúc, đồng thời tạo ảnh động cho đường dẫn đó trong khoảng thời gian đã chỉ định.

Bước 5: Xem trước ảnh động trong Trình chỉnh sửa chuyển động

dff9ecdc1f4a0740.gif

Ảnh động: Video phát bản xem trước hiệu ứng chuyển đổi trong Trình chỉnh sửa chuyển động

  1. Mở Trình chỉnh sửa chuyển động rồi chọn hiệu ứng chuyển đổi bằng cách nhấp vào mũi tên giữa startend trong bảng điều khiển tổng quan.

1dc541ae8c43b250.pngS

  1. Bảng điều khiển lựa chọn sẽ hiện các bộ điều khiển chế độ phát và thanh điều chỉnh khi chọn một hiệu ứng chuyển đổi. Nhấp vào phát hoặc kéo vị trí hiện tại để xem trước ảnh động.

a0fd2593384dfb36.png

Bước 6: Thêm một trình xử lý lượt nhấp

Bạn cần có cách để bắt đầu ảnh động. Bạn có thể thực hiện việc này bằng cách yêu cầu MotionLayout phản hồi các sự kiện nhấp chuột trên @id/red_star.

  1. Mở trình chỉnh sửa chuyển động rồi chọn hiệu ứng chuyển động bằng cách nhấp vào mũi tên giữa điểm bắt đầu và điểm kết thúc trong bảng tổng quan.

b6f94b344ce65290.png

  1. Nhấp vào 699f7ae04024ccf6.png. Tạo trình xử lý lượt nhấp hoặc vuốt trên thanh công cụ để hiển thị bảng tổng quan . Thao tác này sẽ thêm một trình xử lý sẽ bắt đầu quá trình chuyển đổi.
  2. Chọn Trình xử lý lượt nhấp trong cửa sổ bật lên

ccf92d06335105fe.png

  1. Thay đổi tuỳ chọn View To Click (Xem để nhấp) thành red_star.

b0d3f0c970604f01.png

  1. Nhấp vào Thêm, trình xử lý lượt nhấp được biểu thị bằng một dấu chấm nhỏ trên công cụ Chuyển đổi trong Trình chỉnh sửa chuyển động.

cec3913e67fb4105.png

  1. Khi chọn hiệu ứng chuyển đổi trong bảng tổng quan, hãy thêm thuộc tính clickAction toggle vào trình xử lý OnClick mà bạn vừa thêm trong bảng thuộc tính.

9af6fc60673d093d.pngS

  1. Mở activity_step1_scene.xml để xem mã mà Trình chỉnh sửa chuyển động đã tạo

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
    motion:constraintSetStart="@+id/start"
    motion:constraintSetEnd="@+id/end"
    motion:duration="1000">
    <!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
    <OnClick
        motion:targetId="@id/red_star"
        motion:clickAction="toggle" />
</Transition>

Transition yêu cầu MotionLayout chạy ảnh động để phản hồi các sự kiện nhấp chuột bằng cách sử dụng thẻ <OnClick>. Xem từng thuộc tính:

  • targetId là chế độ xem để theo dõi lượt nhấp.
  • clickAction trong số toggle sẽ chuyển đổi giữa trạng thái bắt đầu và kết thúc khi được nhấp. Bạn có thể xem các tuỳ chọn khác cho clickAction trong tài liệu này.
  1. Chạy mã của bạn, nhấp vào Bước 1, sau đó nhấp vào dấu sao màu đỏ và xem ảnh động!

Bước 5: Ảnh động trong thực tế

Chạy ứng dụng! Bạn sẽ thấy ảnh động chạy khi nhấp vào dấu sao.

7ba88af963fdfe10.gif

Tệp cảnh chuyển động hoàn chỉnh xác định một Transition trỏ đến ConstraintSet bắt đầu và kết thúc.

Ở đầu ảnh động (@id/start), biểu tượng dấu sao bị ràng buộc ở đầu màn hình. Ở cuối ảnh động (@id/end), biểu tượng dấu sao bị ràng buộc ở cuối màn hình.

<?xml version="1.0" encoding="utf-8"?>

<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:android="http://schemas.android.com/apk/res/android">
   <!-- A transition describes an animation via start and end state -->
   <Transition
           motion:constraintSetStart="@+id/start"
           motion:constraintSetEnd="@+id/end"
           motion:duration="1000">
       <!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
       <OnClick
               motion:targetId="@id/red_star"
               motion:clickAction="toggle" />
   </Transition>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/start">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintStart_toStartOf="parent"
               motion:layout_constraintTop_toTopOf="parent" />
   </ConstraintSet>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/end">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintEnd_toEndOf="parent"
               motion:layout_constraintBottom_toBottomOf="parent" />
   </ConstraintSet>
</MotionScene>

4. Tạo ảnh động dựa trên sự kiện kéo

Ở bước này, bạn sẽ tạo ảnh động phản hồi sự kiện kéo của người dùng (khi người dùng vuốt màn hình) để chạy ảnh động. MotionLayout hỗ trợ theo dõi các sự kiện chạm để di chuyển khung hiển thị, cũng như các cử chỉ hất dựa trên vật lý để chuyển động trở nên linh hoạt.

Bước 1: Kiểm tra mã ban đầu

  1. Để bắt đầu, hãy mở tệp bố cục activity_step2.xml, trong đó có MotionLayout. Hãy xem xét mã.

activity_step2.xml

<!-- initial code -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/step2" >

   <ImageView
           android:id="@+id/left_star"
           ...
   />

   <ImageView
           android:id="@+id/right_star"
           ...
   />

   <ImageView
           android:id="@+id/red_star"
           ...
   />

   <TextView
           android:id="@+id/credits"
           ...
           motion:layout_constraintTop_toTopOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

Bố cục này xác định tất cả các khung hiển thị cho ảnh động. Biểu tượng ba dấu sao không bị hạn chế trong bố cục vì chúng sẽ được làm động trong cảnh chuyển động.

Các phần ghi công của TextView có áp dụng các điều kiện ràng buộc vì nó ở cùng một vị trí cho toàn bộ ảnh động và không sửa đổi bất kỳ thuộc tính nào.

Bước 2: Tạo ảnh động cho cảnh

Cũng giống như ảnh động cuối cùng, ảnh động sẽ được xác định bằng điểm bắt đầu và kết thúc ConstraintSet,Transition.

Xác định ConstraintSet bắt đầu

  1. Mở cảnh chuyển động xml/step2.xml để xác định ảnh động.
  2. Thêm quy tắc ràng buộc cho quy tắc ràng buộc bắt đầu start. Lúc đầu, cả ba ngôi sao đều nằm ở giữa cuối màn hình. Các sao bên phải và bên trái có giá trị alpha0.0, nghĩa là chúng hoàn toàn trong suốt và bị ẩn.

step2.xml

<!-- TODO apply starting constraints -->

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

Trong ConstraintSet này, bạn chỉ định một Constraint cho mỗi dấu sao. Mỗi quy tắc ràng buộc sẽ được MotionLayout áp dụng ở đầu ảnh động.

Mỗi chế độ xem sao được căn giữa ở cuối màn hình bằng cách sử dụng các điều kiện ràng buộc bắt đầu, kết thúc và dưới cùng. Hai ngôi sao @id/left_star@id/right_star đều có thêm một giá trị alpha khiến chúng không xuất hiện và sẽ được áp dụng ở đầu ảnh động.

Các bộ quy tắc ràng buộc startend xác định điểm bắt đầu và kết thúc của ảnh động. Một quy tắc ràng buộc khi bắt đầu, chẳng hạn như motion:layout_constraintStart_toStartOf sẽ ràng buộc điểm bắt đầu của một thành phần hiển thị với điểm bắt đầu của một thành phần hiển thị khác. Ban đầu, việc này có thể gây nhầm lẫn vì tên start được dùng cho cả cả hai đều được dùng trong bối cảnh ràng buộc. Để giúp làm rõ sự khác biệt, start trong layout_constraintStart đề cập đến "bắt đầu" của chế độ xem, là từ trái sang ngôn ngữ từ trái sang phải và từ phải sang ngôn ngữ từ phải sang trái. Bộ quy tắc ràng buộc start tham chiếu đến điểm bắt đầu của ảnh động.

Xác định ConstraintSet cuối

  1. Xác định điều kiện ràng buộc cuối để sử dụng chuỗi nhằm định vị cả 3 dấu sao lại với nhau bên dưới @id/credits. Ngoài ra, thao tác này sẽ đặt giá trị cuối của alpha của dấu sao trái và phải thành 1.0.

step2.xml

<!-- TODO apply ending constraints -->

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintHorizontal_chainStyle="packed"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toStartOf="@id/red_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toEndOf="@id/left_star"
           motion:layout_constraintEnd_toStartOf="@id/right_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintStart_toEndOf="@id/red_star"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>

Kết quả cuối cùng là các khung hiển thị sẽ trải rộng ra và đi lên từ tâm khi chúng tạo hiệu ứng động.

Ngoài ra, do thuộc tính alpha được thiết lập trên @id/right_start@id/left_star trong cả hai ConstraintSets nên cả hai chế độ xem sẽ mờ dần khi ảnh động tiến hành.

Ảnh động dựa trên thao tác vuốt của người dùng

MotionLayout có thể theo dõi các sự kiện kéo hoặc vuốt của người dùng để tạo một thao tác "hất" dựa trên vật lý ảnh động. Tức là các thành phần hiển thị sẽ tiếp tục di chuyển nếu người dùng hất các thành phần hiển thị đó và sẽ giảm tốc độ như một vật thể thực tế khi lăn trên một bề mặt. Bạn có thể thêm loại ảnh động này bằng thẻ OnSwipe trong Transition.

  1. Thay thế TODO để thêm thẻ OnSwipe bằng <OnSwipe motion:touchAnchorId="@id/red_star" />.

step2.xml

<!-- TODO add OnSwipe tag -->

<!-- A transition describes an animation via start and end state -->
<Transition
       motion:constraintSetStart="@+id/start"
       motion:constraintSetEnd="@+id/end">
   <!-- MotionLayout will track swipes relative to this view -->
   <OnSwipe motion:touchAnchorId="@id/red_star" />
</Transition>

OnSwipe chứa một số thuộc tính, trong đó thuộc tính quan trọng nhất là touchAnchorId.

  • touchAnchorId là thành phần hiển thị được theo dõi và sẽ di chuyển theo phản hồi với thao tác chạm. MotionLayout sẽ giữ cho thành phần hiển thị này giữ nguyên khoảng cách từ ngón tay đang vuốt.
  • touchAnchorSide xác định phía nào của khung hiển thị cần được theo dõi. Điều này rất quan trọng đối với những chế độ xem thay đổi kích thước, đi theo các đường dẫn phức tạp hoặc có một mặt di chuyển nhanh hơn bên kia.
  • dragDirection xác định hướng quan trọng đối với ảnh động này (lên, xuống, trái hoặc phải).

Khi MotionLayout theo dõi các sự kiện kéo, trình nghe sẽ được đăng ký trên thành phần hiển thị MotionLayout chứ không phải thành phần hiển thị do touchAnchorId chỉ định. Khi người dùng bắt đầu một cử chỉ ở bất kỳ đâu trên màn hình, MotionLayout sẽ giữ khoảng cách giữa ngón tay của họ và touchAnchorSide của thành phần hiển thị touchAnchorId không đổi. Ví dụ: nếu người dùng chạm 100 dp từ cạnh neo, thì MotionLayout sẽ giữ cạnh đó cách ngón tay 100 dp cho toàn bộ ảnh động.

Dùng thử

  1. Chạy lại ứng dụng rồi mở màn hình Bước 2. Bạn sẽ thấy ảnh động.
  2. Thử "hất" hoặc thả ngón tay ra giữa ảnh động để khám phá cách MotionLayout hiển thị ảnh động dựa trên vật lý linh hoạt!

fefcdd690a0dcaec.gif

MotionLayout có thể tạo ảnh động giữa các thiết kế rất khác nhau bằng cách sử dụng các tính năng của ConstraintLayout để tạo hiệu ứng phong phú.

Trong ảnh động này, cả 3 khung hiển thị đều được định vị tương ứng với thành phần mẹ ở cuối màn hình để bắt đầu. Ở cuối, 3 khung hiển thị này được định vị tương ứng với @id/credits trong một chuỗi.

Mặc dù các bố cục rất khác nhau này, MotionLayout sẽ tạo ảnh động linh hoạt từ đầu đến cuối.

5. Sửa đổi đường dẫn

Ở bước này, bạn sẽ tạo ảnh động đi theo một đường dẫn phức tạp trong quá trình tạo ảnh động và tạo ảnh động cho các ghi công trong chuyển động. MotionLayout có thể sửa đổi đường dẫn mà một khung hiển thị sẽ di chuyển từ điểm bắt đầu đến điểm kết thúc bằng cách sử dụng KeyPosition.

Bước 1: Tìm hiểu mã hiện có

  1. Mở layout/activity_step3.xmlxml/step3.xml để xem bố cục và cảnh chuyển động hiện có. ImageViewTextView hiển thị văn bản mặt trăng và tín dụng.
  2. Mở tệp cảnh chuyển động (xml/step3.xml). Bạn thấy rằng Transition từ @id/start đến @id/end đã được định nghĩa. Ảnh động di chuyển hình ảnh mặt trăng từ góc dưới bên trái màn hình đến góc dưới cùng bên phải màn hình bằng hai ConstraintSets. Dòng chữ ghi nhận tác giả mờ dần từ alpha="0.0" đến alpha="1.0" khi mặt trăng di chuyển.
  3. Chạy ứng dụng ngay rồi chọn Bước 3. Bạn sẽ thấy mặt trăng đi theo một đường thẳng (hoặc một đường thẳng) từ điểm bắt đầu đến điểm kết thúc khi nhấp vào mặt trăng.

Bước 2: Bật tính năng gỡ lỗi đường dẫn

Trước khi thêm một vòng cung vào chuyển động của mặt trăng, bạn nên bật tính năng gỡ lỗi đường đi trong MotionLayout.

Để giúp phát triển ảnh động phức tạp bằng MotionLayout, bạn có thể vẽ đường dẫn ảnh động của mọi khung hiển thị. Điều này rất hữu ích khi bạn muốn trực quan hoá ảnh động và tinh chỉnh các chi tiết nhỏ của chuyển động.

  1. Để bật đường dẫn gỡ lỗi, hãy mở layout/activity_step3.xml và thêm motion:motionDebug="SHOW_PATH" vào thẻ MotionLayout.

activity_step3.xml

<!-- Add motion:motionDebug="SHOW_PATH" -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:motionDebug="SHOW_PATH" >

Sau khi bật tính năng gỡ lỗi về đường dẫn, khi chạy lại ứng dụng, bạn sẽ thấy đường dẫn của tất cả các chế độ xem được hiển thị bằng một đường chấm.

23bbb604f456f65c.pngS

  • Vòng tròn đại diện cho vị trí bắt đầu hoặc vị trí kết thúc của một chế độ xem.
  • Đường kẻ đại diện cho đường dẫn của một khung hiển thị.
  • Kim cương biểu thị một KeyPosition sửa đổi đường dẫn.

Ví dụ: trong ảnh động này, vòng tròn ở giữa là vị trí của văn bản ghi công.

Bước 3: Sửa đổi đường dẫn

Tất cả ảnh động trong MotionLayout đều được xác định bằng điểm bắt đầu và kết thúc ConstraintSet giúp xác định giao diện của màn hình trước và sau khi ảnh động hoàn tất. Theo mặc định, MotionLayout vẽ một đường dẫn tuyến tính (một đường thẳng) giữa vị trí bắt đầu và vị trí kết thúc của mỗi thành phần hiển thị thay đổi vị trí.

Để tạo các đường dẫn phức tạp như vòng cung mặt trăng trong ví dụ này, MotionLayout sử dụng KeyPosition để sửa đổi đường dẫn mà một khung hiển thị đi từ điểm bắt đầu đến điểm kết thúc.

  1. Mở xml/step3.xml và thêm KeyPosition vào cảnh. Thẻ KeyPosition được đặt bên trong thẻ Transition.

eae4dae9a12d0410.png

step3.xml

<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

KeyFrameSet là phần tử con của Transition và là tập hợp tất cả KeyFrames, chẳng hạn như KeyPosition, sẽ được áp dụng trong quá trình chuyển đổi.

Khi tính toán đường đi của mặt trăng từ điểm xuất phát đến điểm cuối, nên MotionLayout sẽ sửa đổi đường đi dựa trên KeyPosition được chỉ định trong KeyFrameSet. Bạn có thể xem cách thao tác này sửa đổi đường dẫn bằng cách chạy lại ứng dụng.

KeyPosition có một số thuộc tính mô tả cách sửa đổi đường dẫn. Các lý do quan trọng nhất là:

  • framePosition là một số từ 0 đến 100. Định nghĩa này xác định thời điểm áp dụng KeyPosition này trong ảnh động, với 1 là 1% thông qua ảnh động và 99 là 99% thông qua ảnh động. Vì vậy, nếu giá trị là 50, bạn sẽ áp dụng giá trị này ngay ở giữa.
  • motionTarget là khung hiển thị mà KeyPosition này sửa đổi đường dẫn.
  • keyPositionType là cách KeyPosition này sửa đổi đường dẫn. Trạng thái đó có thể là parentRelative, pathRelative hoặc deltaRelative (như giải thích trong bước tiếp theo).
  • percentX | percentY là mức cần thiết để sửa đổi đường dẫn tại framePosition (có giá trị từ 0 đến 1, có giá trị âm và giá trị lớn hơn 1 được cho phép).

Bạn có thể xem xét theo cách sau: "Tại framePosition sửa đổi đường đi của motionTarget bằng cách di chuyển nó theo percentX hoặc percentY theo toạ độ được xác định bởi keyPositionType."

Theo mặc định, MotionLayout sẽ làm tròn mọi góc được đưa ra bằng cách sửa đổi đường dẫn. Nếu nhìn vào ảnh động bạn vừa tạo, bạn có thể thấy mặt trăng đi theo một đường cong tại chỗ uốn. Đối với hầu hết ảnh động, đây là điều bạn muốn. Nếu không, bạn có thể chỉ định thuộc tính curveFit để tuỳ chỉnh nó.

Dùng thử

Nếu chạy lại ứng dụng, bạn sẽ thấy ảnh động cho bước này.

46b179c01801f19e.gif

Mặt trăng đi theo một vòng cung vì đi qua một KeyPosition được chỉ định trong Transition.

<KeyPosition
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.5"
/>

Bạn có thể đọc KeyPosition này như sau: "Vào framePosition 50 (nửa chừng ảnh động), hãy sửa đổi đường dẫn của motionTarget @id/moon bằng cách di chuyển đường dẫn này theo 50% Y (ở nửa dưới màn hình) theo toạ độ do parentRelative (toàn bộ MotionLayout) xác định."

Vì vậy, nửa chừng ảnh động, mặt trăng phải đi qua một KeyPosition với diện tích màn hình giảm 50%. KeyPosition này hoàn toàn không sửa đổi chuyển động X, nên mặt trăng sẽ vẫn đi từ đầu đến cuối theo chiều ngang. MotionLayout sẽ tìm ra một đường dẫn bằng phẳng đi qua KeyPosition này trong khi di chuyển giữa điểm bắt đầu và điểm kết thúc.

Nếu bạn nhìn kỹ, phần văn bản ghi công bị ràng buộc bởi vị trí của mặt trăng. Tại sao ảnh không di chuyển theo chiều dọc?

1c7cf779931e45cc.gif

<Constraint
       android:id="@id/credits"
       ...
       motion:layout_constraintBottom_toBottomOf="@id/moon"
       motion:layout_constraintTop_toTopOf="@id/moon"
/>

Hoá ra là mặc dù bạn đang sửa đổi đường mà mặt trăng đi nhưng vị trí bắt đầu và kết thúc của mặt trăng hoàn toàn không di chuyển theo chiều dọc. KeyPosition không sửa đổi vị trí bắt đầu hoặc kết thúc, do đó, văn bản thông tin ghi công bị ràng buộc ở vị trí kết thúc cuối cùng trên mặt trăng.

Nếu muốn các giá trị đóng góp này di chuyển theo mặt trăng, thì bạn có thể thêm KeyPosition vào giá trị đóng góp hoặc sửa đổi các giới hạn bắt đầu trên @id/credits.

Trong phần tiếp theo, bạn sẽ tìm hiểu sâu hơn về các loại keyPositionType trong MotionLayout.

6. Tìm hiểu về keyPositionType

Trong bước cuối cùng, bạn đã sử dụng loại keyPosition của parentRelative để bù trừ đường dẫn 50% màn hình. Thuộc tính keyPositionType xác định cách MotionLayout sửa đổi đường dẫn theo percentX hoặc percentY.

<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

Có thể có 3 loại keyPosition: parentRelative, pathRelativedeltaRelative. Việc chỉ định một kiểu sẽ thay đổi hệ toạ độ mà theo đó percentXpercentY được tính toán.

Hệ toạ độ là gì?

Hệ thống toạ độ đưa ra cách thức để chỉ định một điểm trong không gian. Chúng cũng hữu ích khi mô tả vị trí trên màn hình.

Hệ toạ độ MotionLayout là một hệ toạ độ Descartes. Tức là chúng có trục X và trục Y được xác định bằng 2 đường vuông góc. Sự khác biệt chính giữa chúng là vị trí mà trục X di chuyển trên màn hình (trục Y luôn vuông góc với trục X).

Tất cả hệ toạ độ trong MotionLayout sử dụng các giá trị từ 0.0 đến 1.0 trên cả trục X và Y. Giá trị này cho phép giá trị âm và giá trị lớn hơn 1.0. Ví dụ: giá trị percentX của -2.0 có nghĩa là đi theo hướng ngược lại của trục X hai lần.

Nếu bạn thấy gần giống với lớp Đại số, hãy xem các bức ảnh dưới đây!

Tọa độ tương đối gốc

a7b7568d46d9dec7.png

keyPositionType của parentRelative sử dụng cùng một hệ toạ độ với màn hình. Thuộc tính này xác định (0, 0) ở góc trên cùng bên trái của toàn bộ MotionLayout(1, 1) ở góc dưới cùng bên phải.

Bạn có thể sử dụng parentRelative mỗi khi muốn tạo ảnh động di chuyển qua toàn bộ MotionLayout – giống như cung mặt trăng trong ví dụ này.

Tuy nhiên, nếu bạn muốn sửa đổi đường đi tương ứng với chuyển động, ví dụ làm cho đường cong chỉ một chút, thì hai hệ toạ độ khác là lựa chọn tốt hơn.

Toạ độ delta Tương đối

5680bf553627416c.png.

Delta là một thuật ngữ toán học dùng để chỉ sự thay đổi, vì vậy deltaRelative là một cách nói "thay đổi tương đối". Trong deltaRelative toạ độ(0,0) là vị trí bắt đầu của khung hiển thị và (1,1) là vị trí kết thúc. Trục X và Y được căn chỉnh phù hợp với màn hình.

Trục X luôn nằm ngang trên màn hình, còn trục Y luôn là trục dọc trên màn hình. So với parentRelative, điểm khác biệt chính là các toạ độ này chỉ mô tả một phần của màn hình mà thành phần hiển thị sẽ di chuyển.

deltaRelative là một hệ toạ độ tuyệt vời để điều khiển chuyển động ngang hoặc dọc một cách riêng biệt. Ví dụ: bạn có thể tạo một ảnh động chỉ hoàn thành chuyển động theo chiều dọc (Y) ở mức 50% và tiếp tục tạo ảnh động theo chiều ngang (X).

Tọa độ tương đối

f3aaadaac8b4a93f.png

Hệ toạ độ cuối cùng trong MotionLayoutpathRelative. Trục X khác với 2 trục còn lại vì trục X đi theo đường chuyển động từ đầu đến cuối. Vì vậy, (0,0) là vị trí bắt đầu và (1,0) là vị trí kết thúc.

Tại sao bạn muốn điều này? Hơi ngạc nhiên khi mới nhìn qua, đặc biệt là vì hệ toạ độ này thậm chí còn không được căn chỉnh cho phù hợp với hệ toạ độ màn hình.

Hoá ra pathRelative rất hữu ích cho một số việc.

  • Tăng tốc, giảm tốc hoặc dừng khung hình trong một phần của ảnh động. Vì phương diện X sẽ luôn khớp chính xác với đường dẫn mà khung hiển thị đi, bạn có thể sử dụng pathRelative KeyPosition để thay đổi framePosition mà một điểm cụ thể trong đường dẫn đó tiếp cận. Vì vậy, KeyPositionframePosition="50"percentX="0.1" sẽ khiến ảnh động dành 50% thời gian để di chuyển 10% chuyển động đầu tiên.
  • Thêm một vòng cung tinh tế vào đường dẫn. Vì chiều Y luôn vuông góc với chuyển động, nên việc thay đổi Y sẽ làm thay đổi đường cong tương ứng với chuyển động tổng thể.
  • Thêm thứ nguyên thứ hai khi deltaRelative không hoạt động. Đối với chuyển động hoàn toàn theo chiều ngang và chiều dọc, deltaRelative sẽ chỉ tạo một kích thước hữu ích. Tuy nhiên, pathRelative sẽ luôn tạo toạ độ X và Y hữu dụng được.

Trong bước tiếp theo, bạn sẽ tìm hiểu cách tạo các đường dẫn phức tạp hơn nữa bằng cách sử dụng nhiều KeyPosition.

7. Xây dựng đường dẫn phức tạp

Nhìn vào ảnh động bạn đã tạo ở bước cuối cùng, ảnh động này tạo ra một đường cong mượt mà nhưng hình dạng có thể "giống mặt trăng" hơn.

Sửa đổi đường dẫn có nhiều phần tử KeyPosition

MotionLayout có thể sửa đổi đường đi hơn nữa bằng cách xác định nhiều KeyPosition (nếu cần) để nhận được bất kỳ chuyển động nào. Đối với ảnh động này, bạn sẽ tạo một vòng cung nhưng bạn có thể làm mặt trăng nhảy lên và xuống ở giữa màn hình nếu muốn.

  1. Mở xml/step4.xml. Bạn thấy lớp này có cùng thành phần hiển thị và KeyFrame mà bạn đã thêm ở bước trước.
  2. Để làm tròn phần trên cùng của đường cong, hãy thêm hai KeyPositions nữa vào đường dẫn @id/moon, một ngay trước khi lên đến đỉnh và một ở sau.

500b5ac2db48ef87.pngS

step4.xml

<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
       motion:framePosition="25"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>
<KeyPosition
       motion:framePosition="75"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>

KeyPositions này sẽ được áp dụng 25% và 75% xuyên suốt ảnh động, đồng thời khiến @id/moon di chuyển qua một đường dẫn chiếm 60% tính từ đầu màn hình. Kết hợp với KeyPosition hiện có ở mức 50%, điều này sẽ tạo ra một vòng cung mượt mà để mặt trăng di chuyển theo sau.

Trong MotionLayout, bạn có thể thêm bao nhiêu KeyPositions tuỳ ý để có được đường chuyển động mong muốn. MotionLayout sẽ áp dụng từng KeyPosition tại framePosition được chỉ định và tìm ra cách tạo chuyển động mượt mà xuyên suốt toàn bộ KeyPositions.

Dùng thử

  1. Chạy lại ứng dụng. Chuyển đến Bước 4 để xem ảnh động hoạt động. Khi bạn nhấp vào mặt trăng, mặt trăng sẽ đi theo đường dẫn từ điểm bắt đầu đến điểm kết thúc, đi qua từng KeyPosition được chỉ định trong KeyFrameSet.

Tự khám phá

Trước khi chuyển sang các loại KeyFrame khác, hãy thử thêm một số KeyPositions khác vào KeyFrameSet để xem bạn có thể tạo những loại hiệu ứng nào chỉ bằng KeyPosition.

Sau đây là một ví dụ cho thấy cách tạo một đường dẫn phức tạp di chuyển qua lại trong ảnh động.

cd9faaffde3dfef.png

step4.xml

<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="25"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
           motion:percentX="0.3"
   />
   <KeyPosition
           motion:framePosition="75"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
</KeyFrameSet>

Sau khi khám phá xong KeyPosition, trong bước tiếp theo, bạn sẽ chuyển sang các loại KeyFrames khác.

8. Thay đổi thuộc tính trong khi chuyển động

Việc tạo ảnh động động thường có nghĩa là thay đổi size, rotation hoặc alpha của thành phần hiển thị trong quá trình tạo ảnh động. MotionLayout hỗ trợ tạo ảnh động cho nhiều thuộc tính trên bất kỳ khung hiển thị nào bằng cách sử dụng KeyAttribute.

Ở bước này, bạn sẽ sử dụng KeyAttribute để xoay và đo tỷ lệ mặt trăng. Bạn cũng sẽ sử dụng KeyAttribute để trì hoãn việc xuất hiện của văn bản này cho đến khi mặt trăng sắp hoàn tất hành trình.

Bước 1: Đổi kích thước và xoay bằng KeyAttribute

  1. Mở xml/step5.xml có chứa chính ảnh động mà bạn đã tạo ở bước trước. Để đa dạng, màn hình này dùng một hình ảnh không gian khác làm nền.
  2. Để mặt trăng mở rộng kích thước và xoay, hãy thêm hai thẻ KeyAttribute vào KeyFrameSet tại keyFrame="50"keyFrame="100"

bbae524a2898569.png

step5.xml

<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->

<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       android:scaleY="2.0"
       android:scaleX="2.0"
       android:rotation="-360"
/>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon"
       android:rotation="-720"
/>

Những KeyAttributes này được áp dụng ở mức 50% và 100% ảnh động. KeyAttribute đầu tiên ở mức 50% sẽ xảy ra ở đầu vòng cung và làm cho khung hiển thị tăng gấp đôi kích thước cũng như xoay -360 độ (hoặc một vòng tròn đầy đủ). KeyAttribute thứ hai sẽ hoàn tất vòng xoay thứ hai thành -720 độ (hai vòng tròn đầy đủ) và thu nhỏ kích thước về mức bình thường vì giá trị scaleXscaleY được mặc định là 1.0.

Giống như KeyPosition, KeyAttribute sử dụng framePositionmotionTarget để chỉ định thời điểm áp dụng KeyFrame và khung hiển thị nào cần sửa đổi. MotionLayout sẽ nội suy giữa KeyPositions để tạo ảnh động linh hoạt.

KeyAttributes hỗ trợ thuộc tính có thể áp dụng cho tất cả khung hiển thị. Các API này hỗ trợ thay đổi các thuộc tính cơ bản như visibility, alpha hoặc elevation. Bạn cũng có thể thay đổi chế độ xoay như bạn đang làm ở đây, xoay theo 3 chiều bằng rotateXrotateY, điều chỉnh kích thước bằng scaleXscaleY hoặc dịch vị trí của thành phần hiển thị theo X, Y hoặc Z.

Bước 2: Hoãn hiển thị các khoản tín dụng

Một trong những mục tiêu của bước này là cập nhật ảnh động để văn bản ghi công không xuất hiện cho đến khi ảnh động gần như hoàn tất.

  1. Để trì hoãn việc hiển thị các khoản tín dụng, hãy xác định thêm một KeyAttribute để đảm bảo rằng alpha sẽ vẫn 0 cho đến keyPosition="85". MotionLayout vẫn sẽ chuyển đổi suôn sẻ từ 0 sang 100 alpha, nhưng sẽ thực hiện việc này trong 15% ảnh động cuối cùng.

step5.xml

<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->

<KeyAttribute
       motion:framePosition="85"
       motion:motionTarget="@id/credits"
       android:alpha="0.0"
/>

KeyAttribute này giữ alpha của @id/credits ở mức 0 trong 85% đầu tiên của ảnh động. Vì nó bắt đầu ở alpha 0, điều này có nghĩa là nó sẽ không hiển thị trong 85% đầu tiên của ảnh động.

Hiệu ứng cuối cùng của KeyAttribute này là các phần ghi công xuất hiện ở cuối ảnh động. Điều này khiến các thiên hà trông như đang được phối hợp với mặt trăng đang rơi xuống ở góc bên phải màn hình.

Bằng cách trì hoãn các hoạt ảnh trên một khung hiển thị trong khi một khung hiển thị khác di chuyển như thế này, bạn có thể tạo các hoạt ảnh ấn tượng mang tính động cho người dùng.

Dùng thử

  1. Chạy lại ứng dụng rồi chuyển đến Bước 5 để xem ảnh động trong thực tế. Khi bạn nhấp vào mặt trăng, mặt trăng sẽ đi theo đường dẫn từ đầu đến cuối, đi qua từng KeyAttribute được chỉ định trong KeyFrameSet.

2f4bfdd681c1fa98.gif

Vì bạn xoay hai vòng tròn trọn vẹn mặt trăng, giờ đây nó sẽ thực hiện lật ngược hai lần và các ghi nhận tác giả sẽ trì hoãn việc xuất hiện của chúng cho đến khi hoạt ảnh gần hoàn tất.

Tự khám phá

Trước khi chuyển sang loại KeyFrame cuối cùng, hãy thử sửa đổi các thuộc tính chuẩn khác trong KeyAttributes. Chẳng hạn, hãy thử thay đổi rotation thành rotationX để xem nó tạo ra ảnh động nào.

Dưới đây là danh sách các thuộc tính chuẩn mà bạn có thể thử:

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

9. Thay đổi thuộc tính tùy chỉnh

Ảnh động phong phú liên quan đến việc thay đổi màu sắc hoặc các thuộc tính khác của một thành phần hiển thị. Mặc dù MotionLayout có thể dùng KeyAttribute để thay đổi bất kỳ thuộc tính chuẩn nào được liệt kê trong nhiệm vụ trước, nhưng bạn cần sử dụng CustomAttribute để chỉ định bất kỳ thuộc tính nào khác.

Bạn có thể sử dụng CustomAttribute để thiết lập bất kỳ giá trị nào có phương thức setter. Ví dụ: bạn có thể đặt backgroundColor trên một Thành phần hiển thị bằng CustomAttribute. MotionLayout sẽ sử dụng tính năng phản chiếu để tìm phương thức setter, sau đó gọi phương thức này nhiều lần để tạo ảnh động cho thành phần hiển thị.

Ở bước này, bạn sẽ sử dụng CustomAttribute để đặt thuộc tính colorFilter trên mặt trăng nhằm tạo ảnh động như dưới đây.

5fb6792126a09fda.gif

Xác định thuộc tính tuỳ chỉnh

  1. Để bắt đầu, hãy mở xml/step6.xml có chứa chính ảnh động mà bạn đã tạo ở bước trước.
  2. Để làm cho mặt trăng đổi màu, hãy thêm hai KeyAttribute cùng với một CustomAttribute trong KeyFrameSet tại keyFrame="0", keyFrame="50"keyFrame="100".

214699d5fdd956da.pngS

step6.xml

<!-- TODO: Add Custom attributes here -->
<KeyAttribute
       motion:framePosition="0"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFB612"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>

Bạn thêm CustomAttribute bên trong KeyAttribute. CustomAttribute sẽ được áp dụng ở framePosition do KeyAttribute chỉ định.

Bên trong CustomAttribute, bạn phải chỉ định attributeName và một giá trị cần đặt.

  • motion:attributeName là tên của phương thức setter sẽ được thuộc tính tuỳ chỉnh này gọi. Trong ví dụ này, setColorFilter trên Drawable sẽ được gọi.
  • motion:custom*Value là giá trị tuỳ chỉnh thuộc loại được ghi chú trong tên, trong ví dụ này, giá trị tuỳ chỉnh là một màu được chỉ định.

Giá trị tuỳ chỉnh có thể thuộc bất kỳ loại nào sau đây:

  • Màu
  • Số nguyên
  • Số thực dấu phẩy động
  • Chuỗi
  • Phương diện
  • Boolean

Khi sử dụng API này, MotionLayout có thể tạo ảnh động cho mọi nội dung cung cấp phương thức setter trên bất kỳ khung hiển thị nào.

Dùng thử

  1. Chạy lại ứng dụng và chuyển đến Bước 6 để xem ảnh động trong thực tế. Khi bạn nhấp vào mặt trăng, mặt trăng sẽ đi theo đường dẫn từ đầu đến cuối, đi qua từng KeyAttribute được chỉ định trong KeyFrameSet.

5fb6792126a09fda.gif

Khi bạn thêm KeyFrames khác, MotionLayout sẽ thay đổi đường đi của mặt trăng từ một đường thẳng thành một đường cong phức tạp, thêm một thao tác lật hai lần, đổi kích thước và thay đổi màu sắc ở giữa ảnh động.

Trong hoạt ảnh thực, bạn sẽ thường tạo ảnh động cho một vài khung hiển thị cùng lúc điều khiển chuyển động của các khung hiển thị đó dọc theo các đường dẫn và tốc độ khác nhau. Bằng cách chỉ định một KeyFrame khác cho mỗi thành phần hiển thị, bạn có thể biên đạo hoá các ảnh động đa dạng thức giúp tạo ảnh động cho nhiều thành phần hiển thị bằng MotionLayout.

10. Kéo các sự kiện và đường dẫn phức tạp

Trong bước này, bạn sẽ khám phá cách sử dụng OnSwipe với các đường dẫn phức tạp. Cho đến nay, ảnh động của mặt trăng đã được trình nghe OnClick kích hoạt và chạy trong một khoảng thời gian cố định.

Để kiểm soát ảnh động có đường dẫn phức tạp bằng OnSwipe, chẳng hạn như ảnh động mặt trăng bạn đã tạo ở vài bước trước, bạn phải hiểu cách hoạt động của OnSwipe.

Bước 1: Khám phá hành vi OnSwipe

  1. Mở xml/step7.xml rồi tìm nội dung khai báo OnSwipe hiện có.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. Chạy ứng dụng trên thiết bị rồi chuyển đến Bước 7. Xem liệu bạn có thể tạo ảnh động mượt mà bằng cách kéo mặt trăng dọc theo đường cung không.

Khi bạn chạy ảnh động này, nó trông không đẹp lắm. Sau khi lên đến đầu vòng cung, mặt trăng sẽ bắt đầu nhảy xung quanh.

ed96e3674854a548.gif

Để tìm hiểu về lỗi này, hãy xem xét điều gì xảy ra khi người dùng chạm vào ngay bên dưới đầu vòng cung. Vì thẻ OnSwipe có một motion:touchAnchorSide="bottom" MotionLayout sẽ cố gắng giữ cho khoảng cách giữa ngón tay và phần dưới cùng của khung hiển thị không đổi xuyên suốt ảnh động.

Tuy nhiên, vì phần đáy của mặt trăng không phải lúc nào cũng đi theo cùng một hướng nên nó di chuyển lên rồi lại quay lại, MotionLayout không biết việc cần làm khi người dùng vừa mới vượt qua phần trên cùng của vòng cung. Do đó, vì bạn đang theo dõi đáy của mặt trăng, nên vị trí sẽ được đặt ở đâu khi người dùng chạm vào đây?

56cd575c5c77eddd.png.

Bước 2: Sử dụng phía bên phải

Để tránh các lỗi như thế này, quan trọng là bạn phải luôn chọn touchAnchorIdtouchAnchorSide luôn tiến triển theo một hướng trong suốt thời gian của toàn bộ ảnh động.

Trong ảnh động này, cả phía right và phía left của mặt trăng sẽ di chuyển qua màn hình theo một hướng.

Tuy nhiên, cả bottomtop sẽ đảo ngược hướng. Khi cố gắng theo dõi người này, OnSwipe sẽ bị nhầm lẫn khi hướng của các thiết bị đó thay đổi.

  1. Để tạo ảnh động này theo các sự kiện chạm, hãy thay đổi touchAnchorSide thành right.

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="right"
/>

Bước 3: Sử dụng DragDirection

Bạn cũng có thể kết hợp dragDirection với touchAnchorSide để tạo thành một đường phụ theo một hướng khác với thông thường. Quan trọng là touchAnchorSide chỉ tiến triển theo một hướng, nhưng bạn có thể cho MotionLayout biết cần theo dõi hướng nào. Ví dụ: bạn có thể giữ lại touchAnchorSide="bottom" nhưng thêm dragDirection="dragRight". Điều này sẽ khiến MotionLayout theo dõi vị trí của phần dưới cùng của thành phần hiển thị, nhưng chỉ xem xét vị trí của thành phần hiển thị đó khi bạn di chuyển sang phải (sẽ bỏ qua chuyển động dọc). Vì vậy, mặc dù phần dưới cùng di chuyển lên và xuống, nó vẫn sẽ tạo ảnh động chính xác bằng OnSwipe.

  1. Cập nhật OnSwipe để theo dõi chuyển động của mặt trăng một cách chính xác.

step7.xml

<!-- Using dragDirection to control the direction of drag tracking 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
       motion:dragDirection="dragRight"
/>

Dùng thử

  1. Chạy lại ứng dụng và thử kéo mặt trăng qua toàn bộ đường dẫn. Mặc dù tuân theo một vòng cung phức tạp, nhưng MotionLayout vẫn có thể tiến hành ảnh động để phản hồi các sự kiện vuốt.

5458dff382261427.gif

11. Chuyển động chạy bằng mã

Bạn có thể dùng MotionLayout để tạo ảnh động phong phú khi dùng cùng CoordinatorLayout. Ở bước này, bạn sẽ tạo một tiêu đề có thể thu gọn bằng cách sử dụng MotionLayout.

Bước 1: Tìm hiểu mã hiện có

  1. Để bắt đầu, hãy mở layout/activity_step8.xml.
  2. Trong layout/activity_step8.xml, bạn sẽ thấy một CoordinatorLayoutAppBarLayout đang hoạt động đã được tạo.

activity_step8.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout
       ...>
   <com.google.android.material.appbar.AppBarLayout
           android:id="@+id/appbar_layout"
           android:layout_width="match_parent"
           android:layout_height="180dp">
       <androidx.constraintlayout.motion.widget.MotionLayout
               android:id="@+id/motion_layout"
               ... >
           ...
       </androidx.constraintlayout.motion.widget.MotionLayout>
   </com.google.android.material.appbar.AppBarLayout>
  
   <androidx.core.widget.NestedScrollView
           ...
           motion:layout_behavior="@string/appbar_scrolling_view_behavior" >
           ...
   </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

Bố cục này sử dụng CoordinatorLayout để chia sẻ thông tin cuộn giữa NestedScrollViewAppBarLayout. Vì vậy, khi NestedScrollView cuộn lên, lớp này sẽ cho AppBarLayout biết về sự thay đổi. Đó là cách bạn triển khai thanh công cụ thu gọn như thế này trên Android – việc cuộn văn bản sẽ được "phối hợp" với tiêu đề thu gọn.

Cảnh chuyển động mà @id/motion_layout trỏ đến cũng tương tự như cảnh chuyển động ở bước cuối. Tuy nhiên, hệ thống đã xoá phần khai báo OnSwipe để cho phép hoạt động với CoordinatorLayout.

  1. Chạy ứng dụng rồi chuyển đến Bước 8. Bạn thấy rằng khi cuộn văn bản, mặt trăng không di chuyển.

Bước 2: Làm cho MotionLayout cuộn

  1. Để khung hiển thị MotionLayout cuộn ngay khi NestedScrollView cuộn, hãy thêm motion:minHeightmotion:layout_scrollFlags vào MotionLayout.

activity_step8.xml

<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->

<androidx.constraintlayout.motion.widget.MotionLayout
       android:id="@+id/motion_layout"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       motion:layoutDescription="@xml/step8"
       motion:motionDebug="SHOW_PATH"
       android:minHeight="80dp"
       motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed"  >
  1. Chạy lại ứng dụng rồi chuyển đến Bước 8. Bạn sẽ thấy MotionLayout thu gọn khi cuộn lên. Tuy nhiên, ảnh động chưa phát triển dựa trên hành vi cuộn.

Bước 3: Di chuyển chuyển động bằng mã

  1. Mở Step8Activity.kt . Chỉnh sửa hàm coordinateMotion() để cho MotionLayout biết về những thay đổi đối với vị trí cuộn.

Step8Activity.kt

// TODO: set progress of MotionLayout based on an AppBarLayout.OnOffsetChangedListener

private fun coordinateMotion() {
    val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
    val motionLayout: MotionLayout = findViewById(R.id.motion_layout)

    val listener = AppBarLayout.OnOffsetChangedListener { unused, verticalOffset ->
        val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
        motionLayout.progress = seekPosition
    }

    appBarLayout.addOnOffsetChangedListener(listener)
}

Mã này sẽ đăng ký một OnOffsetChangedListener và được gọi mỗi khi người dùng cuộn với độ lệch cuộn hiện tại.

MotionLayout hỗ trợ tìm kiếm hiệu ứng chuyển đổi bằng cách đặt thuộc tính tiến trình. Để chuyển đổi giữa tiến trình verticalOffset và tiến trình theo tỷ lệ phần trăm, hãy chia cho tổng phạm vi cuộn.

Dùng thử

  1. Triển khai lại ứng dụng rồi chạy ảnh động Bước 8. Bạn thấy rằng MotionLayout sẽ tiến trình ảnh động dựa trên vị trí cuộn.

ee5ce4d9e33a59ca.gif

Bạn có thể tạo ảnh động tuỳ chỉnh cho việc thu gọn thanh công cụ bằng cách sử dụng MotionLayout. Bằng cách sử dụng một chuỗi KeyFrames, bạn có thể có được những hiệu ứng rất đậm.

12. Xin chúc mừng

Lớp học lập trình này đã đề cập đến API cơ bản của MotionLayout.

Để xem thêm ví dụ về MotionLayout trong thực tế, hãy xem mẫu chính thức. Hãy nhớ xem tài liệu này nhé!

Tìm hiểu thêm

MotionLayout hỗ trợ nhiều tính năng khác không có trong lớp học lập trình này, chẳng hạn như KeyCycle, cho phép bạn điều khiển các đường dẫn hoặc thuộc tính có chu kỳ lặp lại, còn KeyTimeCycle, cho phép bạn tạo ảnh động dựa trên thời gian trên đồng hồ. Hãy xem các mẫu để biết từng trường hợp.

Để xem đường liên kết đến các lớp học lập trình khác trong khoá học này, hãy xem trang đích của lớp học lập trình Android bằng Kotlin nâng cao.