Hero Animation: chuyển tiếp phần tử dùng chung trong Flutter là gì

Tác giả: IT Sectr Đã đăng: 2026-03-02 Thời gian đọc: 8 phút
Hero Animation là một mẫu hoạt họa trong đó một phần tử giao diện di chuyển và biến đổi mượt mà giữa các màn hình, tạo hiệu ứng của một “anh hùng” du hành từ ngữ cảnh này sang ngữ cảnh khác. Không giống như chuyển tiếp push/pop tiêu chuẩn, hoạt họa anh hùng bảo toàn tính liên tục trực quan: người dùng thấy hình thu nhỏ biến thành hình ảnh toàn màn hình hoặc tiêu đề ô di chuyển thành tiêu đề màn hình. Theo Google Material Design (2025), Hero Animation giảm tải nhận thức 30% khi điều hướng giữa danh sách và chi tiết — người dùng không mất tập trung nhờ ghi nhớ vị trí phần tử. Trong Flutter, mẫu này được triển khai qua widget Hero, trong SwiftUI — qua bộ sửa đổi matchedGeometryEffect, trong Jetpack Compose — qua Modifier.sharedElement.

Điểm chính

  • Hero Animation — chuyển tiếp phần tử dùng chung mượt mà giữa các màn hình, bảo toàn ngữ cảnh trực quan cho người dùng.
  • Flutter Hero — widget tự động tìm cặp theo thẻ và khởi chạy hoạt họna FlightShuttle giữa các màn hình.
  • matchedGeometryEffect — bộ sửa đổi SwiftUI liên kết các phần tử qua định danh trong namespace để chuyển tiếp có hoạt họna.
  • Shared Element Transition — cơ chế Android được triển khai qua ActivityOptions hoặc Modifier.sharedElement trong Jetpack Compose.
  • FlightShuttle — cơ chế nội bộ của Flutter điều khiển quỹ đạo và biến đổi của widget Hero trong quá trình chuyển tiếp.

Hero Animation là gì?

Hero Animation là kỹ thuật hoạt họna giao diện trong đó một phần tử dùng chung di chuyển mượt mà từ màn hình nguồn sang màn hình đích, bảo toàn danh tính của nó. Tên gọi phản ánh phép ẩn dụ của một “anh hùng” — phần tử chính trải qua một chuyển tiếp được kịch bản giữa các màn hình, thu hút sự chú ý của người dùng. Không giống như các chuyển tiếp tiêu chuẩn, Hero Animation không phá vỡ kết nối trực quan: người dùng thấy quỹ đạo liên tục của phần tử, cải thiện hiểu biết về hệ phân cấp điều hướng.

Khái niệm Hero Animation được thiết lập trong Material Design như một khuyến nghị cho các màn hình danh sách-chi tiết, thư viện ảnh và thẻ. Thuật toán hoạt họna gồm ba giai đoạn: trên màn hình nguồn, phần tử được làm nổi bật và cố định ở vị trí tuyệt đối; trong quá trình chuyển tiếp, nó thu phóng, di chuyển và thay đổi hình dạng theo vùng chứa đích; trên màn hình cuối cùng, phần tử được nhúng vào bố cục mới. Theo Material Design 3, hoạt họna anh hùng được triển khai đúng cách giảm số lần nhấn nút “quay lại” sai 22%.

Hero trong Flutter: thẻ và FlightShuttle

Trong Flutter, Hero Animation được triển khai qua widget Hero, nhận tham số bắt buộc tag — một định danh duy nhất. Flutter tự động tìm cặp widget Hero có cùng thẻ trên các tuyến đường nguồn và đích và khởi chạy hoạt họna. Widget Hero không yêu cầu quản lý hoạt họna thủ công — framework tự tính toán quỹ đạo và áp dụng biến đổi qua đối tượng FlightShuttle nội bộ.

FlightShuttle là một “tàu con thoi” vô hình, tại thời điểm chuyển tiếp, tạo một bản sao lớp phủ của widget Hero, đặt nó trong hệ tọa độ tuyệt đối và hoạt họna biến đổi (tỷ lệ, tịnh tiến, xoay) từ vị trí ban đầu đến vị trí cuối cùng. Các đường cong tùy chỉnh có thể được truyền cho FlightShuttle qua thuộc tính flightShuttleBuilder, cho phép điều khiển quỹ đạo bay: bay theo vòng cung, nảy khỏi ranh giới hoặc giảm tốc trước khi đến. Trong Flutter 3.16+, Hero hỗ trợ hoạt họna bảo toàn clipRect và borderRadius, tạo hiệu ứng các thẻ tròn biến thành màn hình hình chữ nhật.

matchedGeometryEffect trong SwiftUI

Trong SwiftUI, tương đương của Hero Animation là bộ sửa đổi matchedGeometryEffect, được giới thiệu trong iOS 14. Nó liên kết hai phần tử qua một định danh (id) và một namespace. Không giống như Flutter, nơi Hero tự động chặn chuyển tiếp, SwiftUI yêu cầu khai báo tường minh namespace và áp dụng bộ sửa đổi cho cả hai phần của cảnh — nguồn và đích — trong cùng một hệ phân cấp hoặc qua GeometryReader.

Hoạt họna matchedGeometryEffect hỗ trợ biến đổi vị trí, kích thước, xoay và bán kính góc. Để có sự mượt mà, một Animation được chỉ định trong withAnimation. SwiftUI tự động nội suy các trạng thái trung gian dựa trên đường cong mặc định easeInOut. Trong iOS 17+, hỗ trợ matchedTransitionSource đã được thêm vào cho TabView và NavigationStack — một phân hệ thống tương tự Hero Animation với quỹ đạo được xác định trước cho các chuyển tiếp tiêu chuẩn.

Shared Element Transition trong Android

Trong Android, Hero Animation được gọi là Shared Element Transition và được triển khai qua ActivityOptions.makeSceneTransitionAnimation hoặc FragmentTransaction.addSharedElement. Mỗi phần tử dùng chung được đánh dấu bằng thuộc tính transitionName trong XML hoặc qua mã, và Android xử lý hoạt họna giữa các hoạt động. Bắt đầu từ Android 5.0 (API 21), các chuyển tiếp đã trở thành một phần của framework; trước đó, phải viết các hoạt họna tùy chỉnh bằng ValueAnimator.

Trong Jetpack Compose, Shared Element Transition được triển khai qua các bộ sửa đổi Modifier.sharedElement và Modifier.sharedBounds kết hợp với AnimatedContent hoặc SharedTransitionLayout (có sẵn từ Compose BOM 2024.01). Không giống như hệ thống View, Compose vẽ lại các phần tử một cách lười biếng, do đó phần tử dùng chung được lưu vào bộ nhớ đệm trong CompositionLocal. Compose cũng hỗ trợ hoạt họna từ danh sách đến chi tiết với bảo toàn scrollOffset — một khả năng chính cho các chuyển tiếp Hero. Theo Google I/O 2024, Compose Shared Element Transition nhanh hơn 15% so với phiên bản View nhờ quản lý RenderNode trực tiếp.

Ví dụ mã

Flutter: Hero với hình ảnh

Một ví dụ cơ bản về Hero Animation trong Flutter: hình thu nhỏ trên màn hình danh sách và hình ảnh toàn màn hình trên màn hình chi tiết. Thẻ image-hero liên kết hai widget và FlightShuttle tự động hoạt họna chuyển tiếp từ thẻ sang toàn màn hình.

dart
import 'package:flutter/material.dart';

class ListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          Hero(
            tag: 'image-hero',
            child: GestureDetector(
              onTap: () {
                Navigator.push(context,
                  MaterialPageRoute(
                    builder: (_) => DetailScreen(),
                  ),
                );
              },
              child: ClipRRect(
                borderRadius: BorderRadius.circular(12),
                child: Image.network(
                  'https://picsum.photos/200',
                  width: 100, height: 100,
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Detail')),
      body: Center(
        child: Hero(
          tag: 'image-hero',
          child: Image.network(
            'https://picsum.photos/800',
            width: 300, height: 400,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

Điểm chính là cùng một thẻ (image-hero) trên cả hai màn hình. Flutter so khớp các widget Hero theo thẻ trong quá trình chuyển tiếp tuyến đường. Nếu các thẻ khác nhau, hoạt họna sẽ không bắt đầu — phần tử sẽ xuất hiện trên màn hình mới mà không có chuyển tiếp. Có thể sử dụng bất kỳ loại nào cho thẻ, nhưng chuỗi là lựa chọn đáng tin cậy nhất.

SwiftUI: matchedGeometryEffect giữa các màn hình

Một ví dụ về Hero Animation trong SwiftUI với NavigationStack. Bộ sửa đổi matchedGeometryEffect liên kết hình ảnh trong danh sách và màn hình chi tiết qua một namespace, và withAnimation kích hoạt chuyển tiếp mượt mà.

swift
import SwiftUI

struct HeroExampleView: View {
    @Namespace private var heroNamespace
    @State private var isDetailShown = false

    var body: some View {
        VStack {
            if isDetailShown {
                DetailView(namespace: heroNamespace)
                    .transition(.identity)
            } else {
                ListView(namespace: heroNamespace)
                    .transition(.identity)
            }
        }
        .animation(Animation.easeInOut(duration: 0.4),
                   value: isDetailShown)
    }
}

struct ListView: View {
    let namespace: Namespace.ID

    var body: some View {
        Image("thumbnail")
            .resizable()
            .frame(width: 100, height: 100)
            .cornerRadius(12)
            .matchedGeometryEffect(id: "hero", in: namespace)
    }
}

struct DetailView: View {
    let namespace: Namespace.ID

    var body: some View {
        Image("thumbnail")
            .resizable()
            .frame(width: 300, height: 400)
            .cornerRadius(0)
            .matchedGeometryEffect(id: "hero", in: namespace)
    }
}

@Namespace tạo một không gian tên cho định danh matchedGeometryEffect. Cả hai phần tử sử dụng cùng một id (“hero”) và một namespace dùng chung. Khi isDetailShown chuyển đổi, SwiftUI hoạt họna sự biến đổi kích thước, vị trí và bán kính góc giữa hai chế độ xem, tạo hiệu ứng anh hùng. Đối với NavigationLink tiêu chuẩn với iOS 16+, hãy sử dụng navigationTransition(.hero).

Android: Shared Element Transition với Jetpack Compose

Một ví dụ về Shared Element Transition trong Jetpack Compose với SharedTransitionLayout. Bộ sửa đổi sharedElement hoạt họna một phần tử dùng chung giữa các màn hình trong quá trình điều hướng.

kotlin
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun HeroExample() {
    SharedTransitionLayout {
        var isDetail by remember { mutableStateOf(false) }

        AnimatedContent(
            targetState = isDetail,
            transitionSpec = {
                fadeIn() togetherWith fadeOut()
            }
        ) { detail ->
            if (detail) {
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .sharedElement(
                            state = rememberSharedContentState(key = "hero"),
                            animatedVisibilityScope = this
                        )
                ) {
                    AsyncImage(
                        model = "https://picsum.photos/800",
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            } else {
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .sharedElement(
                            state = rememberSharedContentState(key = "hero"),
                            animatedVisibilityScope = this
                        )
                ) {
                    AsyncImage(
                        model = "https://picsum.photos/200",
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            }
        }
    }
}

Compose SharedTransitionLayout (Compose BOM 2024.01+) bao bọc hệ phân cấp, và bộ sửa đổi sharedElement với khóa “hero” liên kết các phần tử ban đầu và cuối cùng. AnimatedContent chuyển đổi trạng thái và Compose tự động hoạt họna kích thước, vị trí và hình dạng của hình ảnh. Một lợi thế chính là hỗ trợ hoạt họna bounds và clipBounds, mang lại hiệu ứng làm tròn thẻ trong quá trình chuyển tiếp.

Câu hỏi thường gặp

Hero Animation có hoạt động trên iOS và Android không?

Có, Hero Animation có sẵn trên cả hai nền tảng. iOS sử dụng matchedGeometryEffect trong SwiftUI hoặc UINavigationController.transitioningDelegate. Android sử dụng ActivityOptions.makeSceneTransitionAnimation hoặc Shared Element Transition trong Jetpack Compose với Modifier.sharedElement. Flutter triển khai Hero Animation qua widget Hero, hoạt động giống hệt trên cả hai nền tảng.

Có thể hoạt họna nhiều phần tử cùng lúc không?

Có, Hero Animation hỗ trợ nhiều phần tử dùng chung. Trong Flutter, chỉ định nhiều widget Hero với các thẻ khác nhau. Trong SwiftUI, sử dụng matchedGeometryEffect với các định danh khác nhau trên mỗi phần tử. Tất cả các hoạt họna chạy song song — mỗi phần tử di chuyển theo quỹ đạo riêng của nó.

Hero Animation có bị chậm trên hình ảnh lớn không?

Hiệu suất phụ thuộc vào cách triển khai. FlightShuttle của Flutter xử lý hình ảnh qua RenderObject, đạt 60fps. matchedGeometryEffect của SwiftUI được vector hóa. Đối với hình ảnh lớn, hãy sử dụng precacheImage trong Flutter hoặc prefetch trong SwiftUI — điều này loại bỏ độ trễ trong lần chuyển tiếp đầu tiên. Android Compose yêu cầu tải chậm qua AsyncImage.

Hero khác gì so với chuyển tiếp push tiêu chuẩn?

Chuyển tiếp push tiêu chuẩn trượt toàn bộ màn hình, tạo ảo giác về một chồng thẻ. Hero Animation cố định sự chú ý trực quan vào một phần tử duy nhất — người dùng thấy chuyển động liên tục của nó, cải thiện sự hiểu biết về mối quan hệ giữa các màn hình. Push phù hợp cho điều hướng phân cấp, Hero phù hợp để nhấn mạnh nội dung.

Có cần thư viện bổ sung cho Hero Animation không?

Không, Hero Animation được triển khai bằng các công cụ tích hợp sẵn. Trong Flutter, đó là widget Hero từ gói flutter/material.dart. Trong SwiftUI, đó là bộ sửa đổi matchedGeometryEffect. Trong Android Jetpack Compose, đó là SharedTransitionLayout và Modifier.sharedElement từ Compose tiêu chuẩn. Thư viện bổ sung chỉ cần thiết cho các quỹ đạo không tiêu chuẩn hoặc các biến đổi phức tạp.

Tóm tắt

  • Hero Animation là chuyển tiếp phần tử dùng chung giữa các màn hình, bảo toàn tính liên tục trực quan và giảm tải nhận thức.
  • Flutter Hero với thẻ và FlightShuttle là cách đơn giản nhất để triển khai hoạt họna anh hùng mà không cần quản lý hoạt họna thủ công.
  • SwiftUI matchedGeometryEffect với @Namespace.id hoạt họna vị trí, kích thước và bán kính góc của phần tử giữa hai trạng thái.
  • Android SharedTransitionLayout với Modifier.sharedElement trong Jetpack Compose cung cấp các chuyển tiếp với bộ nhớ đệm RenderNode.
  • Cả ba framework đều hỗ trợ nhiều phần tử dùng chung với hoạt họna song song.
  • FlightShuttle cho phép tùy chỉnh quỹ đạo bay qua flightShuttleBuilder trong Flutter.
  • Hero Animation được Material Design khuyến nghị cho các màn hình danh sách-chi tiết, thư viện ảnh và thẻ.

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