Transition (hoạt ảnh chuyển tiếp) là cơ chế thay đổi trực quan thay thế nội dung này bằng nội dung khác: màn hình, fragment, view hoặc trạng thái giao diện. Một chuyển tiếp được triển khai đúng cách sẽ hướng sự chú ý của người dùng, giải thích mối quan hệ giữa các thành phần và làm cho điều hướng trở nên trực quan. Theo Material Design Guidelines (2026), các ứng dụng có chuyển tiếp được thiết kế tốt cho thấy tỷ lệ giữ chân người dùng cao hơn 22% trong phiên thứ hai. Tìm hiểu thêm về các loại hoạt ảnh khác trong hướng dẫn hoạt ảnh chung.
Những điểm chính
Transition là một hoạt ảnh xảy ra khi thay thế trạng thái giao diện trực quan này bằng trạng thái khác. Không giống như Property Animation (làm hoạt ảnh thuộc tính của một đối tượng đơn lẻ), Transition quản lý toàn bộ tập hợp các thay đổi: xuất hiện/biến mất của các thành phần, thay đổi vị trí, kích thước và thuộc tính của chúng trong một chuyển tiếp duy nhất. Transition giải quyết vấn đề thay đổi nội dung mượt mà để người dùng hiểu được cái gì đã di chuyển đi đâu.
Trên các nền tảng di động, Transition được sử dụng trong ba kịch bản chính: điều hướng giữa các màn hình (push, pop, modal present), thay đổi fragment (FragmentTransaction trong Android) và thay đổi trạng thái trong màn hình (mở rộng thẻ, chuyển tab). Mỗi kịch bản yêu cầu cách tiếp cận riêng: chuyển tiếp điều hướng — UIViewControllerAnimatedTransitioning trong iOS hoặc ActivityOptions trong Android, chuyển tiếp trong màn hình — Transition Framework hoặc UIView.transition.
Theo Apple HIG (2026), thời lượng của một chuyển tiếp tiêu chuẩn không được vượt quá 400–500 ms — hoạt ảnh dài hơn bị coi là độ trễ. Material Design khuyến nghị 300–350 ms cho chuyển tiếp di động. Đối với các thành phần lớn (thẻ, hình ảnh), thời gian có thể tăng lên 500–600 ms mà không làm giảm hiệu suất cảm nhận.
Các loại chuyển tiếp được chia thành nhiều danh mục tuỳ thuộc vào nội dung nào thay đổi và thay đổi như thế nào. Các loại cơ bản: cross-fade — một nội dung biến mất mượt mà và nội dung khác xuất hiện; slide — một màn hình trượt ra, màn hình khác trượt vào; scale/zoom — một thành phần phóng to, mở ra màn hình chi tiết. Chuyển tiếp kết hợp pha trộn nhiều hiệu ứng.
| Loại chuyển tiếp | Mô tả | API iOS | API Android |
|---|---|---|---|
| Cross-fade | Mờ dần: một nội dung biến mất, nội dung khác xuất hiện | UIView.transition(with:duration:options:.transitionCrossDissolve) | TransitionSet + Fade() |
| Slide | Trượt: nội dung vào/ra theo chiều ngang hoặc dọc | CATransition(type: .push, subtype: .fromLeft) | Slide() / FragmentTransaction.setCustomAnimations |
| Scale / Zoom | Thu phóng: thành phần phóng to toàn màn hình | UIViewPropertyAnimator với hoạt ảnh tỷ lệ | Scale() + ActivityOptions.makeScaleUpAnimation |
| Explode | Nổ: nội dung vỡ thành nhiều mảnh khi biến mất | CATransition(type: .reveal) | Explode() (API 21+) |
| Fade-through | Mờ dần qua màn hình trắng/đen | UIView.transition(with: .transitionCrossDissolve, với nền trắng) | Fade() qua ChangeBounds |
Quy tắc Material Design: sử dụng một loại chuyển tiếp trong một ứng dụng để tạo ngôn ngữ trực quan thống nhất. Khuyến nghị: slide cho điều hướng giữa các màn hình cùng cấp (master → chi tiết), cross-fade cho thay đổi nội dung trong một màn hình (tab, chuyển đổi chế độ xem), scale/zoom cho chuyển tiếp từ thẻ sang màn hình chi tiết (thẻ sản phẩm → mô tả).
Trên iOS, UIView.transition là cách đơn giản nhất để tạo hoạt ảnh thay đổi nội dung trong một View duy nhất. Nó hỗ trợ cross-dissolve, curl-up, curl-down, flip-from-left, flip-from-right. Đối với chuyển tiếp điều hướng giữa các UIViewController, hãy sử dụng UINavigationControllerDelegate với UIViewControllerAnimatedTransitioning — điều này cho phép kiểm soát hoàn toàn hoạt ảnh push/pop.
import UIKit
// UIView.transition: thay đổi hình ảnh với cross-dissolve
UIView.transition(with: imageView,
duration: 0.4,
options: [.transitionCrossDissolve, .curveEaseInOut]
) {
self.imageView.image = UIImage(named: "new_image")
} completion: { _ in
print("Transition completed")
}
// UIView.transition: hoán đổi hai View
UIView.transition(from: firstView, to: secondView,
duration: 0.5,
options: [.transitionFlipFromRight, .showHideTransitionViews]
) { _ in
// firstView bị ẩn, secondView được hiển thị
}
// CATransition cho lớp (không có Auto Layout)
let transition = CATransition()
transition.type = .push
transition.subtype = .fromRight
transition.duration = 0.35
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
view.layer.add(transition, forKey: "pushTransition")
UIViewPropertyAnimator là một giải pháp thay thế hiện đại cho chuyển tiếp tuỳ chỉnh trên iOS 10+. Không giống như UIView.transition, PropertyAnimator hỗ trợ tạm dừng, huỷ bỏ và hoạt ảnh đảo ngược, điều này rất quan trọng cho các chuyển tiếp tương tác (vuốt để quay lại). Để tạo hoạt ảnh giữa các UIViewController, hãy sử dụng UIPercentDrivenInteractiveTransition cùng với PropertyAnimator — đây là mẫu tiêu chuẩn cho điều hướng tương tác.
Trên Android, FragmentTransaction với setCustomAnimations là cách chính để tạo hoạt ảnh chuyển tiếp giữa các Fragment. Tham số: enter (hoạt ảnh vào của Fragment mới), exit (hoạt ảnh ra của Fragment cũ), popEnter và popExit — cho điều hướng quay lại. FragmentTransaction hỗ trợ tài nguyên hoạt ảnh (R.anim) và Transition Framework (R.transition).
import androidx.fragment.app.FragmentTransaction
import androidx.transition.TransitionInflater
// FragmentTransaction với hoạt ảnh tài nguyên
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.slide_in_right, // enter
R.anim.slide_out_left, // exit
R.anim.slide_in_left, // popEnter
R.anim.slide_out_right // popExit
)
.replace(R.id.container, DetailFragment())
.addToBackStack(null)
.commit()
// FragmentTransaction với Transition Framework
val detailFragment = DetailFragment()
detailFragment.enterTransition = TransitionInflater.from(this)
.inflateTransition(R.transition.slide_right)
detailFragment.exitTransition = TransitionInflater.from(this)
.inflateTransition(R.transition.fade_out)
supportFragmentManager.beginTransaction()
.replace(R.id.container, detailFragment)
.addToBackStack(null)
.commit()
// ActivityOptions: hoạt ảnh khi khởi động Activity
val options = ActivityOptions.makeScaleUpAnimation(
view, 0, 0, view.width, view.height
).toBundle()
startActivity(intent, options)
Thực hành tốt nhất Android: Slide() — để thay thế Fragment trong điều hướng (enter = Slide(Gravity.END), exit = Slide(Gravity.START)). Fade() — để hiển thị/ẩn lớp phủ và hộp thoại. Explode() — cho chuyển tiếp nhấn mạnh (xóa một mục khỏi danh sách). Đối với thiết bị cũ (API < 21), hãy sử dụng tài nguyên R.anim làm dự phòng. Transition Framework không hỗ trợ SurfaceView, TextureView, GLSurfaceView — đối với video và camera, chỉ sử dụng hoạt ảnh R.anim.
UIViewControllerAnimatedTransitioning là một giao thức iOS để tạo hoạt ảnh chuyển tiếp hoàn toàn tuỳ chỉnh giữa các bộ điều khiển. Nó cung cấp quyền truy cập vào view chứa (transitionContext.containerView), view nguồn và view đích. Kết hợp với UIPercentDrivenInteractiveTransition, cách tiếp cận này cho phép tạo các chuyển tiếp tương tác với thao tác vuốt bằng ngón tay.
import UIKit
"> Trình hoạt ảnh chuyển cảnh tùy chỉnh
class ScaleTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let isPresenting: Bool
init(presenting: Bool) { self.isPresenting = presenting }
func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.4
}
func animateTransition(using ctx: UIViewControllerContextTransitioning) {
guard let toView = ctx.view(forKey: .to),
let fromView = ctx.view(forKey: .from)
else { return }
let container = ctx.containerView
let initialScale: CGAffineTransform = CGAffineTransform(scaleX: 0.8, y: 0.8)
let finalFrame = ctx.finalFrame(for: toView)
if isPresenting {
toView.transform = initialScale
toView.alpha = 0
toView.frame = finalFrame
container.addSubview(toView)
}
UIView.animate(withDuration: transitionDuration(using: ctx),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.3) {
if self.isPresenting {
toView.transform = .identity
toView.alpha = 1
fromView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
fromView.alpha = 0
} else {
fromView.transform = initialScale
fromView.alpha = 0
}
} completion: { _ in
fromView.transform = .identity
ctx.completeTransition(!ctx.transitionWasCancelled)
}
}
}
// Ứng dụng trong UINavigationControllerDelegate
extension MainViewController: UINavigationControllerDelegate {
func navigationController(
_ nc: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from: UIViewController, to: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return ScaleTransitionAnimator(presenting: operation == .push)
}
}
Chuyển tiếp tương tác: thêm UIPanGestureRecognizer vào màn hình nguồn. Khi cử chỉ được kích hoạt, hãy tạo UIPercentDrivenInteractiveTransition và gọi update(CGFloat(percent)). Để huỷ khi không đạt đến ngưỡng (thường là 30%), hãy gọi cancel(). Mẫu này được sử dụng theo mặc định trong UINavigationController cho cử chỉ pop và trong UIScrollView với pagingEnabled. Đối với cử chỉ tuỳ chỉnh, hãy triển khai UIViewControllerInteractiveTransitioning.
Transition Framework (android.transition) là một hệ thống hoạt ảnh khai báo tự động tạo hoạt ảnh cho các thay đổi giữa hai cảnh (Scene). Một cảnh là một tập hợp các Views với trạng thái cụ thể. Khi chuyển từ Cảnh A sang Cảnh B, Transition Framework phân tích sự khác biệt và tạo hoạt ảnh: thay đổi vị trí (ChangeBounds), thay đổi kích thước (ChangeClipBounds, ChangeTransform), xuất hiện (Fade) và biến mất (Fade).
import android.transition.Scene
import android.transition.TransitionManager
import android.transition.TransitionSet
import android.transition.AutoTransition
import android.transition.ChangeBounds
import android.transition.Fade
// TransitionSet: kết hợp ChangeBounds + Fade
val transition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_TOGETHER
addTransition(ChangeBounds())
addTransition(Fade(Fade.IN))
duration = 400L
interpolator = FastOutSlowInInterpolator()
}
// Chuyển tiếp giữa các cảnh trong ViewGroup
val sceneRoot = findViewById<ViewGroup>(R.id.scene_root)
val scene1 = Scene.getSceneForLayout(sceneRoot, R.layout.scene_initial)
val scene2 = Scene.getSceneForLayout(sceneRoot, R.layout.scene_expanded)
"> AutoTransition không cần cấu hình thủ công
TransitionManager.go(scene2, AutoTransition().apply {
duration = 350L
})
// TransitionManager.beginDelayedTransition — không có cảnh
TransitionManager.beginDelayedTransition(sceneRoot, ChangeBounds())
itemView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
itemView.requestLayout() "> Thay đổi layout được hoạt ảnh tự động
ChangeBounds là chuyển tiếp được sử dụng nhiều nhất trong Android, nó tạo hoạt ảnh cho các thay đổi vị trí và kích thước của View. ChangeTransform tạo hoạt ảnh xoay và tỷ lệ. ChangeImageTransform — cho hoạt ảnh ImageView (centerCrop → fitCenter). Đối với các chuyển tiếp phức tạp có thay đổi cấu trúc phân cấp, hãy sử dụng TransitionSet với ordering = ORDERING_SEQUENTIAL (các hoạt ảnh thực thi tuần tự, không song song). Theo tài liệu Android Developers (2026), Transition Framework hỗ trợ tất cả các View tiêu chuẩn nhưng không hỗ trợ hoạt ảnh bề mặt (SurfaceView, TextureView, VideoView).
Câu hỏi thường gặp
Animation — tạo hoạt ảnh cho các thuộc tính của một đối tượng (alpha, translation, scale). Transition — tạo hoạt ảnh cho sự thay đổi của một tập hợp các đối tượng hoặc trạng thái (fragment → fragment, scene → scene). Transition quản lý nhiều thay đổi đồng thời: xuất hiện, biến mất, di chuyển, thay đổi kích thước — và tự động tạo hoạt ảnh cho sự khác biệt giữa hai trạng thái. Animation yêu cầu quản lý thủ công từng thuộc tính.
Sử dụng FragmentTransaction.setCustomAnimations với Transition Framework: fragment1.exitTransition = Fade(Fade.OUT), fragment2.enterTransition = Slide(Gravity.END). Để sắp xếp trình tự: setReorderingAllowed(true) — các fragment sẽ được sắp xếp lại để chuyển tiếp song song mượt mà. Đối với các trường hợp đơn giản, slide_in_right / slide_out_left từ R.anim là đủ. Đối với điều hướng quay lại, hãy chỉ định popEnter và popExit.
Transition Framework chỉ tạo hoạt ảnh cho các thuộc tính không kích hoạt tính toán lại bố cục. Để tạo hoạt ảnh chiều cao, hãy sử dụng ChangeBounds — nó tự động tạo hoạt ảnh cho các thay đổi ranh giới View giữa hai trạng thái bố cục. Gọi TransitionManager.beginDelayedTransition(viewGroup, ChangeBounds()) trước khi thay đổi LayoutParams. Nếu chiều cao thay đổi qua WRAP_CONTENT, hãy đảm bảo layout_height khác nhau trước và sau — ChangeBounds sẽ không hoạt động nếu cả hai trạng thái đều là match_parent.
Sử dụng bổ từ .transition() với AnyTransition: .transition(.move(edge: .trailing).combined(with: .opacity)). Đối với chuyển tiếp tuỳ chỉnh, hãy triển khai AnyTransition.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)). Để tạo hoạt ảnh trong NavigationStack, hãy sử dụng .navigationTransition(.slide) trên iOS 18+. SwiftUI không hỗ trợ UIViewControllerAnimatedTransitioning — chỉ các chuyển tiếp khai báo qua AnyTransition.
Sử dụng Profile GPU Rendering (Android) hoặc Core Animation Instrument (iOS). Đối với Android, bật Transition Profiling qua adb shell setprop debug.transition.profiling 1 — nhật ký sẽ hiển thị thời lượng của từng giai đoạn chuyển tiếp. Trên iOS, bật Slow Animations trong trình mô phỏng. Chỉ số mục tiêu: thời lượng < 400 ms, FPS >= 55, rớt khung hình — không quá 2 mỗi chuyển tiếp. Đối với các cảnh phức tạp với 15+ View được tạo hoạt ảnh đồng thời, hãy sử dụng ChangeBounds với thời lượng 300 ms.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm