نکات کلیدی
Hero Animation — تکنیک انیمیشن رابط کاربری است که در آن یک عنصر مشترک به آرامی از صفحه مبدأ به صفحه مقصد حرکت کرده و هویت خود را حفظ میکند. این نام استعاره «قهرمان» را منعکس میکند — عنصر کلیدی که از یک گذر صحنهای بین صفحهها عبور میکند و توجه کاربر را جلب میکند. برخلاف انتقالهای استاندارد، Hero Animation پیوند بصری را نمیشکند: کاربر مسیر پیوسته عنصر را میبیند که درک سلسلهمراتب ناوبری را بهبود میبخشد.
مفهوم Hero Animation در Material Design به عنوان توصیهای برای صفحههای لیست-جزئیات (list-detail)، گالریها و کارتها تثبیت شده است. الگوریتم انیمیشن از سه مرحله تشکیل شده است: در صفحه مبدأ، عنصر برجسته شده و در موقعیت مطلق ثابت میشود؛ در طول انتقال، مطابق با ظرف مقصد مقیاسبندی، جابجا و تغییر شکل میدهد؛ در صفحه نهایی، عنصر در چیدمان جدید ادغام میشود. طبق Material Design 3، انیمیشن قهرمان به درستی پیادهسازی شده تعداد کلیکهای اشتباه «بازگشت» را ۲۲٪ کاهش میدهد.
در Flutter، Hero Animation از طریق ویجت Hero پیادهسازی میشود که پارامتر اجباری tag — یک شناسه یکتا — را میپذیرد. Flutter به طور خودکار جفت Hero با تگ یکسان را در مسیرهای مبدأ و مقصد پیدا کرده و انیمیشن را اجرا میکند. ویجت Hero نیازی به مدیریت دستی انیمیشن ندارد — فریمورک خود مسیر را محاسبه کرده و از طریق شی داخلی FlightShuttle تغییر شکل را اعمال میکند.
FlightShuttle — یک «شاتل» نامرئی است که در لحظه انتقال یک کپی پوششی از ویجت Hero ایجاد کرده، آن را در سیستم مختصات مطلق قرار میدهد و transform (scale, translate, rotate) را از موقعیت اولیه تا نهایی انیمیشن میکند. میتوان منحنیهای سفارشی را از طریق ویژگی flightShuttleBuilder به FlightShuttle منتقل کرد که امکان کنترل مسیر پرواز را فراهم میکند: پرواز در امتداد قوس، جهش از مرزها یا کاهش سرعت قبل از رسیدن. در Flutter 3.16+، Hero از انیمیشن با حفظ clipRect و borderRadius پشتیبانی میکند که اثر کارتهای گرد شده تبدیلشونده به صفحههای مستطیلی را ایجاد میکند.
در 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 با مسیر از پیش تعریفشده برای انتقالهای استاندارد.
در 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 عمل میکند.
مثال پایه Hero Animation در Flutter: تصویر کوچک در صفحه لیست و تصویر تمامصفحه در صفحه جزئیات. تگ image-hero دو ویجت را به هم متصل میکند و FlightShuttle به طور خودکار انتقال از کارت به تمامصفحه را انیمیشن میکند.
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 را بر اساس تگ در طول انتقال مسیر مطابقت میدهد. اگر تگها متفاوت باشند، انیمیشن اجرا نخواهد شد — عنصر به سادگی در صفحه جدید بدون انتقال ظاهر میشود. میتوان از هر نوعی برای تگ استفاده کرد، اما رشتهها مطمئنترین گزینه هستند.
مثال Hero Animation در SwiftUI با NavigationStack. اصلاحکننده matchedGeometryEffect تصویر لیست و صفحه جزئیات را از طریق namespace به هم متصل میکند و withAnimation یک انتقال نرم را اجرا میکند.
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) استفاده کنید.
مثال Shared Element Transition در Jetpack Compose با SharedTransitionLayout. اصلاحکننده sharedElement عنصر مشترک را بین صفحهها در طول ناوبری انیمیشن میکند.
@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 از matchedGeometryEffect در SwiftUI یا UINavigationController.transitioningDelegate استفاده میشود. در Android — انتقال ActivityOptions.makeSceneTransitionAnimation یا Shared Element Transition در Jetpack Compose با Modifier.sharedElement. Flutter Hero Animation را از طریق ویجت Hero پیادهسازی میکند که در هر دو پلتفرم به طور یکسان کار میکند.
بله، Hero Animation از عناصر مشترک متعدد پشتیبانی میکند. در Flutter چندین Hero با تگهای مختلف مشخص کنید. در SwiftUI از matchedGeometryEffect با شناسههای مختلف روی هر عنصر استفاده کنید. همه انیمیشنها به صورت موازی اجرا میشوند — هر عنصر در مسیر خود حرکت میکند.
عملکرد به پیادهسازی بستگی دارد. Flutter FlightShuttle تصاویر را از طریق RenderObject پردازش میکند که ۶۰fps را تضمین میکند. SwiftUI matchedGeometryEffect برداری شده است. برای تصاویر بزرگ از precacheImage در Flutter یا prefetch در SwiftUI استفاده کنید — این کار تأخیرها را در اولین انتقال برطرف میکند. Android Compose نیاز به بارگذاری تنبل از طریق AsyncImage دارد.
انتقال استاندارد push کل صفحه را جابجا میکند و توهم دسته کارت ایجاد میکند. Hero Animation توجه بصری را روی یک عنصر متمرکز میکند — کاربر حرکت پیوسته آن را میبیند که درک ارتباط بین صفحهها را بهبود میبخشد. Push برای ناوبری سلسلهمراتبی خوب است، Hero — برای برجستهسازی محتوا.
خیر، Hero Animation با ابزارهای داخلی پیادهسازی شده است. در Flutter این ویجت Hero از بسته flutter/material.dart است. در SwiftUI — اصلاحکننده matchedGeometryEffect. در Android Jetpack Compose — SharedTransitionLayout و Modifier.sharedElement از Compose استاندارد. کتابخانههای اضافی فقط برای مسیرهای غیراستاندارد یا تبدیل پیچیده مورد نیاز هستند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید