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
ConstraintSets
vàMotionLayout
- 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ủaConstraintLayout
. 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 choMotionLayout.
Transition,
nằm trongMotionScene
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 đầu và kế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.
- Trong
res/layout
, mởactivity_step1.xml.
Ở đây, bạn cóConstraintLayout
với mộtImageView
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 đó.
- Để 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.
- 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).
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
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:
- 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ữastart
vàend
bằng cách nhấp vào mũi tên ở giữa. - 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. - 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
.
- Chọn ConstraintSet
start
trong bảng điều khiển tổng quan
- 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ủalayout
– tức là không bị hạn chế trongConstraintSet
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
- Xác nhận rằng
red_star
cho thấy Nguồn làstart
khi bạn chọnstart
ConstraintSet
trong bảng điều khiển tổng quan. - Trong bảng điều khiển Attributes (Thuộc tính), với
red_star
được chọn trongstart
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.
- 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>
ConstraintSet
có id
là @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ẹ.
- Chọn ConstraintSet
end
trong bảng điều khiển tổng quan.
- Làm theo các bước tương tự như bạn đã làm trước đó để thêm
Constraint
chored_star
trongend
ConstraintSet
. - Để 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
bottom
vàend
bằng cách nhấp vào nút + màu xanh dương.
- 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
và @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ị.
- 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
Ả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
- 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
start
vàend
trong bảng điều khiển tổng quan.
- 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.
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
.
- 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.
- Nhấp vào 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.
- Chọn Trình xử lý lượt nhấp trong cửa sổ bật lên
- Thay đổi tuỳ chọn View To Click (Xem để nhấp) thành
red_star
.
- 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.
- 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.
- 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 choclickAction
trong tài liệu này.
- 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.
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
- Để 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,
và Transition
.
Xác định ConstraintSet bắt đầu
- Mở cảnh chuyển động
xml/step2.xml
để xác định ảnh động. - 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ịalpha
là0.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
và @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 start
và end
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ả và 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
- 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ủaalpha
của dấu sao trái và phải thành1.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
và @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
.
- 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ử
- Chạy lại ứng dụng rồi mở màn hình Bước 2. Bạn sẽ thấy ảnh động.
- 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!
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ó
- Mở
layout/activity_step3.xml
vàxml/step3.xml
để xem bố cục và cảnh chuyển động hiện có.ImageView
vàTextView
hiển thị văn bản mặt trăng và tín dụng. - Mở tệp cảnh chuyển động (
xml/step3.xml
). Bạn thấy rằngTransition
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 haiConstraintSets
. Dòng chữ ghi nhận tác giả mờ dần từalpha="0.0"
đếnalpha="1.0"
khi mặt trăng di chuyển. - 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.
- Để bật đường dẫn gỡ lỗi, hãy mở
layout/activity_step3.xml
và thêmmotion: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.
- 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.
- Mở
xml/step3.xml
và thêmKeyPosition
vào cảnh. ThẻKeyPosition
được đặt bên trong thẻTransition
.
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ụngKeyPosition
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áchKeyPosition
này sửa đổi đường dẫn. Trạng thái đó có thể làparentRelative
,pathRelative
hoặcdeltaRelative
(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ạiframePosition
(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.
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?
<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
, pathRelative
và deltaRelative
. Việc chỉ định một kiểu sẽ thay đổi hệ toạ độ mà theo đó percentX
và percentY
đượ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
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
và (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
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
Hệ toạ độ cuối cùng trong MotionLayout
là pathRelative
. 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 đổiframePosition
mà một điểm cụ thể trong đường dẫn đó tiếp cận. Vì vậy,KeyPosition
ởframePosition="50"
có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.
- 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. - Để 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.
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ử
- 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 trongKeyFrameSet
.
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.
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
- 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. - Để mặt trăng mở rộng kích thước và xoay, hãy thêm hai thẻ
KeyAttribute
vàoKeyFrameSet
tạikeyFrame="50"
vàkeyFrame="100"
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ị scaleX
và scaleY
được mặc định là 1.0.
Giống như KeyPosition
, KeyAttribute
sử dụng framePosition
và motionTarget
để 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 rotateX
và rotateY
, điều chỉnh kích thước bằng scaleX
và scaleY
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.
- Để 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ằngalpha
sẽ vẫn 0 cho đếnkeyPosition="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ử
- 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 trongKeyFrameSet
.
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.
Xác định thuộc tính tuỳ chỉnh
- Để 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. - Để làm cho mặt trăng đổi màu, hãy thêm hai
KeyAttribute
cùng với mộtCustomAttribute
trongKeyFrameSet
tạikeyFrame="0"
,keyFrame="50"
vàkeyFrame="100".
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ênDrawable
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ử
- 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 trongKeyFrameSet
.
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
- Mở
xml/step7.xml
rồi tìm nội dung khai báoOnSwipe
hiện có.
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
/>
- 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.
Để 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?
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 touchAnchorId
và touchAnchorSide
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ả bottom
và top
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.
- Để tạo ảnh động này theo các sự kiện chạm, hãy thay đổi
touchAnchorSide
thànhright
.
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
.
- 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ử
- 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.
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ó
- Để bắt đầu, hãy mở
layout/activity_step8.xml
. - Trong
layout/activity_step8.xml
, bạn sẽ thấy mộtCoordinatorLayout
vàAppBarLayout
đ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 NestedScrollView
và AppBarLayout
. 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
.
- 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
- Để khung hiển thị
MotionLayout
cuộn ngay khiNestedScrollView
cuộn, hãy thêmmotion:minHeight
vàmotion:layout_scrollFlags
vàoMotionLayout
.
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" >
- 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ã
- Mở
Step8Activity.kt
. Chỉnh sửa hàmcoordinateMotion()
để choMotionLayout
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ử
- 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.
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.