Hero Animation: چیست، انیمیشن عناصر مشترک در Flutter

نویسنده: IT Sectr منتشر شده: 2026-03-02 زمان مطالعه: 8 دقیقه
Hero Animation — الگوی انیمیشنی است که در آن عنصر رابط کاربری به آرامی بین صفحه‌ها جابجا شده و تغییر شکل می‌دهد و اثر «قهرمانی» را ایجاد می‌کند که از یک زمینه به زمینه دیگر سفر می‌کند. برخلاف انتقال استاندارد push/pop، انیمیشن قهرمان تداوم بصری را حفظ می‌کند: کاربر می‌بیند که چگونه یک تصویر کوچک به یک تصویر تمام‌صفحه تبدیل می‌شود یا چگونه عنوان یک سلول به عنوان صفحه منتقل می‌شود. طبق Google Material Design (2025)، Hero Animation بار شناختی را تا ۳۰٪ در هنگام ناوبری بین لیست و جزئیات کاهش می‌دهد — کاربر به دلیل به خاطر سپردن موقعیت عنصر، تمرکز خود را از دست نمی‌دهد. در Flutter این الگو از طریق ویجت Hero، در SwiftUI از طریق اصلاح‌کننده matchedGeometryEffect و در Jetpack Compose از طریق Modifier.sharedElement پیاده‌سازی می‌شود.

نکات کلیدی

  • Hero Animation — انتقال روان عنصر مشترک بین صفحه‌ها که زمینه بصری را برای کاربر حفظ می‌کند.
  • Flutter Hero — ویجتی که به طور خودکار جفت را بر اساس تگ پیدا کرده و انیمیشن FlightShuttle را بین صفحه‌ها اجرا می‌کند.
  • matchedGeometryEffect — اصلاح‌کننده SwiftUI که عناصر را از طریق شناسه در namespace برای انتقال انیمیشنی連結 می‌دهد.
  • Shared Element Transition — مکانیزم Android که از طریق ActivityOptions یا Modifier.sharedElement در Jetpack Compose پیاده‌سازی می‌شود.
  • FlightShuttle — مکانیزم داخلی Flutter که مسیر و تغییر شکل ویجت Hero را در طول انتقال مدیریت می‌کند.

Hero Animation چیست؟

Hero Animation — تکنیک انیمیشن رابط کاربری است که در آن یک عنصر مشترک به آرامی از صفحه مبدأ به صفحه مقصد حرکت کرده و هویت خود را حفظ می‌کند. این نام استعاره «قهرمان» را منعکس می‌کند — عنصر کلیدی که از یک گذر صحنه‌ای بین صفحه‌ها عبور می‌کند و توجه کاربر را جلب می‌کند. برخلاف انتقال‌های استاندارد، Hero Animation پیوند بصری را نمی‌شکند: کاربر مسیر پیوسته عنصر را می‌بیند که درک سلسله‌مراتب ناوبری را بهبود می‌بخشد.

مفهوم Hero Animation در Material Design به عنوان توصیه‌ای برای صفحه‌های لیست-جزئیات (list-detail)، گالری‌ها و کارت‌ها تثبیت شده است. الگوریتم انیمیشن از سه مرحله تشکیل شده است: در صفحه مبدأ، عنصر برجسته شده و در موقعیت مطلق ثابت می‌شود؛ در طول انتقال، مطابق با ظرف مقصد مقیاس‌بندی، جابجا و تغییر شکل می‌دهد؛ در صفحه نهایی، عنصر در چیدمان جدید ادغام می‌شود. طبق Material Design 3، انیمیشن قهرمان به درستی پیاده‌سازی شده تعداد کلیک‌های اشتباه «بازگشت» را ۲۲٪ کاهش می‌دهد.

Hero در Flutter: تگ‌ها و FlightShuttle

در Flutter، Hero Animation از طریق ویجت Hero پیاده‌سازی می‌شود که پارامتر اجباری tag — یک شناسه یکتا — را می‌پذیرد. Flutter به طور خودکار جفت Hero با تگ یکسان را در مسیرهای مبدأ و مقصد پیدا کرده و انیمیشن را اجرا می‌کند. ویجت Hero نیازی به مدیریت دستی انیمیشن ندارد — فریم‌ورک خود مسیر را محاسبه کرده و از طریق شی داخلی FlightShuttle تغییر شکل را اعمال می‌کند.

FlightShuttle — یک «شاتل» نامرئی است که در لحظه انتقال یک کپی پوششی از ویجت Hero ایجاد کرده، آن را در سیستم مختصات مطلق قرار می‌دهد و transform (scale, translate, rotate) را از موقعیت اولیه تا نهایی انیمیشن می‌کند. می‌توان منحنی‌های سفارشی را از طریق ویژگی flightShuttleBuilder به FlightShuttle منتقل کرد که امکان کنترل مسیر پرواز را فراهم می‌کند: پرواز در امتداد قوس، جهش از مرزها یا کاهش سرعت قبل از رسیدن. در Flutter 3.16+، Hero از انیمیشن با حفظ clipRect و borderRadius پشتیبانی می‌کند که اثر کارت‌های گرد شده تبدیل‌شونده به صفحه‌های مستطیلی را ایجاد می‌کند.

matchedGeometryEffect در SwiftUI

در SwiftUI، معادل Hero Animation اصلاح‌کننده matchedGeometryEffect است که در iOS 14 معرفی شد. این اصلاح‌کننده دو عنصر را از طریق شناسه (id) و فضای نام (namespace) به هم متصل می‌کند. برخلاف Flutter که Hero به طور خودکار انتقال را رهگیری می‌کند، SwiftUI نیاز به اعلان صریح namespace و اعمال اصلاح‌کننده به هر دو بخش صحنه — مبدأ و مقصد — در یک سلسله‌مراتب یا از طریق GeometryReader دارد.

انیمیشن matchedGeometryEffect از تغییر شکل موقعیت، اندازه، چرخش و شعاع گوشه (cornerRadius) پشتیبانی می‌کند. برای دستیابی به نرمی، Animation در withAnimation تنظیم می‌شود. SwiftUI به طور خودکار حالت‌های میانی را بر اساس منحنی پیش‌فرض easeInOut درون‌یابی می‌کند. در iOS 17+، پشتیبانی از matchedTransitionSource برای TabView و NavigationStack اضافه شده است — معادل سیستمی Hero Animation با مسیر از پیش تعریف‌شده برای انتقال‌های استاندارد.

Shared Element Transition در Android

در Android، Hero Animation Shared Element Transition نامیده می‌شود و از طریق ActivityOptions.makeSceneTransitionAnimation یا FragmentTransaction.addSharedElement پیاده‌سازی می‌شود. هر عنصر مشترک با ویژگی transitionName در XML یا از طریق کد مشخص می‌شود و Android مسئول انیمیشن بین فعالیت‌ها است. از Android 5.0 (API 21) به بعد، انتقال‌ها بخشی از فریم‌ورک شدند؛ قبل از آن باید انیمیشن‌های سفارشی از طریق ValueAnimator نوشته می‌شد.

در Jetpack Compose، Shared Element Transition از طریق اصلاح‌کننده Modifier.sharedElement و Modifier.sharedBounds در ترکیب با AnimatedContent یا SharedTransitionLayout پیاده‌سازی می‌شود (از Compose BOM 2024.01 در دسترس است). برخلاف سیستم View، Compose عناصر را تنبلانه بازترسیم می‌کند، بنابراین عنصر مشترک در CompositionLocal ذخیره می‌شود. Compose همچنین از انیمیشن لیست به جزئیات (list→detail) با حفظ scrollOffset پشتیبانی می‌کند — قابلیت کلیدی برای انتقال‌های Hero. طبق Google I/O 2024، Compose Shared Element Transition به دلیل مدیریت مستقیم RenderNode ۱۵٪ سریع‌تر از نسخه View عمل می‌کند.

مثال‌های کد

Flutter: Hero با تصویر

مثال پایه Hero Animation در Flutter: تصویر کوچک در صفحه لیست و تصویر تمام‌صفحه در صفحه جزئیات. تگ image-hero دو ویجت را به هم متصل می‌کند و FlightShuttle به طور خودکار انتقال از کارت به تمام‌صفحه را انیمیشن می‌کند.

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,
          ),
        ),
      ),
    );
  }
}

نکته کلیدی — تگ یکسان (image-hero) در هر دو صفحه. Flutter ویجت‌های Hero را بر اساس تگ در طول انتقال مسیر مطابقت می‌دهد. اگر تگ‌ها متفاوت باشند، انیمیشن اجرا نخواهد شد — عنصر به سادگی در صفحه جدید بدون انتقال ظاهر می‌شود. می‌توان از هر نوعی برای تگ استفاده کرد، اما رشته‌ها مطمئن‌ترین گزینه هستند.

SwiftUI: matchedGeometryEffect بین صفحه‌ها

مثال Hero Animation در SwiftUI با NavigationStack. اصلاح‌کننده matchedGeometryEffect تصویر لیست و صفحه جزئیات را از طریق namespace به هم متصل می‌کند و withAnimation یک انتقال نرم را اجرا می‌کند.

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 یک فضای نام برای شناسه matchedGeometryEffect ایجاد می‌کند. هر دو عنصر از id یکسان (hero) و namespace مشترک استفاده می‌کنند. هنگام تغییر isDetailShown، SwiftUI تبدیل اندازه، موقعیت و cornerRadius را بین دو نمای ایجاد می‌کند و اثر قهرمان را ایجاد می‌کند. برای NavigationLink استاندارد با iOS 16+ از navigationTransition(.hero) استفاده کنید.

Android: Shared Element Transition با Jetpack Compose

مثال Shared Element Transition در Jetpack Compose با SharedTransitionLayout. اصلاح‌کننده sharedElement عنصر مشترک را بین صفحه‌ها در طول ناوبری انیمیشن می‌کند.

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+) سلسله‌مراتب را احاطه می‌کند و اصلاح‌کننده sharedElement با کلید hero عناصر شروع و پایان را به هم متصل می‌کند. AnimatedContent حالت را تغییر می‌دهد و Compose به طور خودکار اندازه، موقعیت و شکل تصویر را انیمیشن می‌کند. مزیت کلیدی — پشتیبانی از انیمیشن bounds و clipBounds که اثر گرد شدن کارت را در طول انتقال ایجاد می‌کند.

سوالات متداول

آیا Hero Animation در iOS و Android کار می‌کند؟

بله، Hero Animation در هر دو پلتفرم در دسترس است. در iOS از matchedGeometryEffect در SwiftUI یا UINavigationController.transitioningDelegate استفاده می‌شود. در Android — انتقال ActivityOptions.makeSceneTransitionAnimation یا Shared Element Transition در Jetpack Compose با Modifier.sharedElement. Flutter Hero Animation را از طریق ویجت Hero پیاده‌سازی می‌کند که در هر دو پلتفرم به طور یکسان کار می‌کند.

آیا می‌توان چندین عنصر را همزمان انیمیشن کرد؟

بله، Hero Animation از عناصر مشترک متعدد پشتیبانی می‌کند. در Flutter چندین Hero با تگ‌های مختلف مشخص کنید. در SwiftUI از matchedGeometryEffect با شناسه‌های مختلف روی هر عنصر استفاده کنید. همه انیمیشن‌ها به صورت موازی اجرا می‌شوند — هر عنصر در مسیر خود حرکت می‌کند.

آیا Hero Animation روی تصاویر بزرگ کند می‌شود؟

عملکرد به پیاده‌سازی بستگی دارد. Flutter FlightShuttle تصاویر را از طریق RenderObject پردازش می‌کند که ۶۰fps را تضمین می‌کند. SwiftUI matchedGeometryEffect برداری شده است. برای تصاویر بزرگ از precacheImage در Flutter یا prefetch در SwiftUI استفاده کنید — این کار تأخیرها را در اولین انتقال برطرف می‌کند. Android Compose نیاز به بارگذاری تنبل از طریق AsyncImage دارد.

Hero چه تفاوتی با انتقال استاندارد push دارد؟

انتقال استاندارد push کل صفحه را جابجا می‌کند و توهم دسته کارت ایجاد می‌کند. Hero Animation توجه بصری را روی یک عنصر متمرکز می‌کند — کاربر حرکت پیوسته آن را می‌بیند که درک ارتباط بین صفحه‌ها را بهبود می‌بخشد. Push برای ناوبری سلسله‌مراتبی خوب است، Hero — برای برجسته‌سازی محتوا.

آیا کتابخانه‌های اضافی برای Hero Animation نیاز است؟

خیر، Hero Animation با ابزارهای داخلی پیاده‌سازی شده است. در Flutter این ویجت Hero از بسته flutter/material.dart است. در SwiftUI — اصلاح‌کننده matchedGeometryEffect. در Android Jetpack Compose — SharedTransitionLayout و Modifier.sharedElement از Compose استاندارد. کتابخانه‌های اضافی فقط برای مسیرهای غیراستاندارد یا تبدیل پیچیده مورد نیاز هستند.

خلاصه

  • Hero Animation — انیمیشن عنصر مشترک بین صفحه‌ها که تداوم بصری را حفظ کرده و بار شناختی را کاهش می‌دهد.
  • Flutter Hero با تگ و FlightShuttle — ساده‌ترین راه برای پیاده‌سازی انیمیشن قهرمان بدون مدیریت دستی انیمیشن.
  • SwiftUI matchedGeometryEffect با @Namespace.id موقعیت، اندازه و cornerRadius عنصر را بین دو حالت انیمیشن می‌کند.
  • Android SharedTransitionLayout با Modifier.sharedElement در Jetpack Compose انتقال را با ذخیره RenderNode فراهم می‌کند.
  • هر سه فریم‌ورک از عناصر مشترک متعدد با انیمیشن موازی پشتیبانی می‌کنند.
  • FlightShuttle امکان سفارشی‌سازی مسیر پرواز را از طریق flightShuttleBuilder در Flutter فراهم می‌کند.
  • Hero Animation توسط Material Design برای صفحه‌های لیست-جزئیات، گالری‌ها و کارت‌ها توصیه می‌شود.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید