Transition: cơ bản, hoạt ảnh chuyển tiếp trong ứng dụng di động

Tác giả: IT Sectr Đã đăng: 2026-03-02 Thời gian đọc: 11 phút

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 — hoạt ảnh thay đổi nội dung giữa các màn hình, fragment hoặc trạng thái.
  • UIView.transition — API chính cho hoạt ảnh chuyển tiếp trong iOS (UIKit).
  • FragmentTransaction.setCustomAnimations — cấu hình hoạt ảnh chuyển tiếp giữa các Fragment trong Android.
  • Transition Framework (android.transition) — hệ thống hoạt ảnh khai báo của Android cho chuyển tiếp cảnh sang cảnh.
  • UIViewControllerAnimatedTransitioning — giao thức iOS cho chuyển tiếp tuỳ chỉnh giữa các bộ điều khiển.

Transition là gì?

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 hoạt ảnh chuyển tiếp

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ếpMô tảAPI iOSAPI Android
Cross-fadeMờ dần: một nội dung biến mất, nội dung khác xuất hiệnUIView.transition(with:duration:options:.transitionCrossDissolve)TransitionSet + Fade()
SlideTrượt: nội dung vào/ra theo chiều ngang hoặc dọcCATransition(type: .push, subtype: .fromLeft)Slide() / FragmentTransaction.setCustomAnimations
Scale / ZoomThu phóng: thành phần phóng to toàn màn hìnhUIViewPropertyAnimator với hoạt ảnh tỷ lệScale() + ActivityOptions.makeScaleUpAnimation
ExplodeNổ: nội dung vỡ thành nhiều mảnh khi biến mấtCATransition(type: .reveal)Explode() (API 21+)
Fade-throughMờ dần qua màn hình trắng/đenUIView.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ả).

Chuyển tiếp trong iOS (Swift)

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.

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

Chuyển tiếp trong Android (Kotlin)

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

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

Chuyển tiếp UIViewController tuỳ chỉnh trong iOS

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.

swift
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 trong Android: cảnh sang cảnh

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

kotlin
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

Sự khác biệt giữa Transition và Animation là gì?

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.

Làm thế nào để tạo chuyển tiếp mượt mà giữa các Fragment Android?

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 không tạo hoạt ảnh thay đổi chiều cao của View — phải làm sao?

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.

Làm thế nào để tạo chuyển tiếp tuỳ chỉnh trong SwiftUI?

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.

Làm thế nào để đo hiệu suất của Transition?

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

  • Transition — hoạt ảnh thay đổi nội dung giữa các màn hình, fragment hoặc trạng thái giao diện.
  • UIView.transition (iOS) — API cơ bản cho chuyển tiếp cross-dissolve, flip, curl trong một View.
  • UIViewControllerAnimatedTransitioning (iOS) — giao thức cho chuyển tiếp tuỳ chỉnh giữa các bộ điều khiển.
  • FragmentTransaction.setCustomAnimations (Android) — hoạt ảnh chuyển tiếp giữa các Fragment.
  • Transition Framework (android.transition) — hệ thống cảnh khai báo: ChangeBounds, Fade, Slide, Explode.
  • Thời lượng khuyến nghị: 300–400 ms cho di động, tối đa 600 ms cho các thành phần lớn.
  • Một loại chuyển tiếp cho mỗi ứng dụng để có ngôn ngữ trực quan thống nhất (Material Design).

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.

Thảo luận dự án

Đọc thêm