Önemli noktalar
Hero Animation, ortak bir öğenin kaynak ekrandan hedef ekrana sorunsuz bir şekilde hareket ederek kimliğini koruduğu bir UI animasyon tekniğidir. İsim, ekranlar arasında senaryolu bir geçişten geçen ve kullanıcının dikkatini çeken anahtar bir öğe olan bir “kahraman” metaforunu yansıtır. Standart geçişlerin aksine, Hero Animation görsel bağlantıyı kesmez: kullanıcı öğenin sürekli bir yörüngesini görür ve gezinme hiyerarşisini daha iyi anlar.
Hero Animation kavramı, Material Design’da liste-detay ekranları, galeriler ve kartlar için bir öneri olarak yerleşmiştir. Animasyon algoritması üç aşamadan oluşur: kaynak ekranda öğe vurgulanır ve mutlak bir konuma sabitlenir; geçiş sırasında hedef kapsayıcıya göre ölçeklenir, hareket eder ve şekil değiştirir; son ekranda öğe yeni düzene gömülür. Material Design 3’e göre, doğru şekilde uygulanan bir kahraman animasyonu, hatalı “geri” düğmesine basma sayısını %22 azaltır.
Flutter’da Hero Animation, zorunlu bir parametre olan tag’i — benzersiz bir tanımlayıcı — alan Hero widget‑ı aracılığıyla uygulanır. Flutter, kaynak ve hedef yollarında aynı etikete sahip Hero widget çiftini otomatik olarak bulur ve animasyonu başlatır. Hero widget‑ı manuel animasyon yönetimi gerektirmez — framework, yörüngeleri bağımsız olarak hesaplar ve dönüşümü iç FlightShuttle nesnesi aracılığıyla uygular.
FlightShuttle, geçiş anında Hero widget‑ının bir kaplama kopyasını oluşturan, onu mutlak bir koordinat sistemine yerleştiren ve dönüşümü (boyutlandırma, öteleme, döndürme) başlangıç konumundan son konuma canlandıran görünmez bir “mekik”tir. flightShuttleBuilder özelliği aracılığıyla FlightShuttle’a özel eğriler iletilebilir, bu da uçuş yörüngesini kontrol etmeye olanak tanır. Flutter 3.16+’da Hero, clipRect ve borderRadius’u koruyarak animasyonu destekler ve yuvarlak kartların dikdörtgen ekranlara dönüşmesi efekti verir.
SwiftUI’de Hero Animation’ın eşdeğeri, iOS 14’te tanıtılan matchedGeometryEffect değiştiricisidir. İki öğeyi bir tanımlayıcı (id) ve bir ad alanı (namespace) aracılığıyla birbirine bağlar. Hero’nun geçişi otomatik olarak ele geçirdiği Flutter’ın aksine, SwiftUI, ad alanının açıkça bildirilmesini ve değiştiricinin aynı hiyerarşi içinde veya GeometryReader aracılığıyla sahnenin her iki bölümüne (kaynak ve hedef) uygulanmasını gerektirir.
matchedGeometryEffect animasyonu, konum, boyut, dönüş ve köşe yarıçapının dönüşümünü destekler. Akıcılık için withAnimation içinde bir Animation belirtilir. SwiftUI, varsayılan easeInOut eğrisine dayalı olarak ara durumları otomatik olarak enterpolasyon yapar. iOS 17+’da, TabView ve NavigationStack için matchedTransitionSource desteği eklendi — standart geçişler için önceden tanımlanmış bir yörüngeye sahip Hero Animation’ın bir sistem analogu.
Android’de Hero Animation, Shared Element Transition olarak adlandırılır ve ActivityOptions.makeSceneTransitionAnimation veya FragmentTransaction.addSharedElement aracılığıyla uygulanır. Her ortak öğe, XML’de bir transitionName özelliği veya kod aracılığıyla işaretlenir ve Android, etkinlikler arasındaki animasyonu yönetir. Android 5.0’dan (API 21) itibaren geçişler framework’ün bir parçası haline geldi; bundan önce, ValueAnimator kullanılarak özel animasyonlar yazılması gerekiyordu.
Jetpack Compose’da Shared Element Transition, Modifier.sharedElement ve Modifier.sharedBounds değiştiricileri aracılığıyla AnimatedContent veya SharedTransitionLayout (Compose BOM 2024.01’den itibaren mevcut) ile birlikte uygulanır. View sisteminin aksine, Compose öğeleri tembel bir şekilde yeniden çizer, bu nedenle ortak öğe CompositionLocal’da önbelleğe alınır. Compose ayrıca scrollOffset’u koruyarak listeden detaya animasyonu destekler — Hero geçişleri için önemli bir yetenek. Google I/O 2024’e göre, Compose Shared Element Transition, doğrudan RenderNode yönetimi sayesinde View sürümünden %15 daha hızlıdır.
Flutter’da Hero Animation’ın temel bir örneği: liste ekranında bir küçük resim ve detay ekranında tam ekran bir görüntü. image-hero etiketi iki widget‑ı birbirine bağlar ve FlightShuttle, karttan tam ekrana geçişi otomatik olarak canlandırır.
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,
),
),
),
);
}
}
Anahtar nokta, her iki ekranda da aynı etiket (image-hero) kullanılmasıdır. Flutter, rota geçişi sırasında Hero widget‑larını etikete göre eşleştirir. Etiketler farklıysa, animasyon başlamaz — öğe yeni ekranda geçiş olmadan görünür. Etiket için herhangi bir tür kullanılabilir, ancak dizeler en güvenilir seçenektir.
NavigationStack ile SwiftUI’de Hero Animation örneği. matchedGeometryEffect değiştiricisi, listedeki görseli ve detay ekranını bir ad alanı aracılığıyla birbirine bağlar ve withAnimation sorunsuz bir geçişi tetikler.
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 tanımlayıcısı için bir ad alanı oluşturur. Her iki öğe de aynı id’yi (“hero”) ve paylaşılan bir ad alanını kullanır. isDetailShown değiştiğinde, SwiftUI iki görünüm arasındaki boyut, konum ve köşe yarıçapı dönüşümünü canlandırarak kahraman efekti yaratır. iOS 16+ ile standart bir NavigationLink için navigationTransition(.hero) kullanın.
SharedTransitionLayout ile Jetpack Compose’da Shared Element Transition örneği. sharedElement değiştiricisi, gezinme sırasında ekranlar arasında ortak bir öğeyi canlandırır.
@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+) hiyerarşiyi sarar ve “hero” anahtarına sahip sharedElement değiştiricisi, başlangıç ve bitiş öğelerini birbirine bağlar. AnimatedContent durumu değiştirir ve Compose, görselin boyutunu, konumunu ve şeklini otomatik olarak canlandırır. Temel bir avantaj, geçiş sırasında kart yuvarlama efekti sağlayan bounds ve clipBounds animasyonu desteğidir.
Sıkça sorulan sorular
Evet, Hero Animation her iki platformda da kullanılabilir. iOS, SwiftUI’de matchedGeometryEffect veya UINavigationController.transitioningDelegate kullanır. Android, ActivityOptions.makeSceneTransitionAnimation veya Jetpack Compose’da Modifier.sharedElement ile Shared Element Transition kullanır. Flutter, Hero Animation’ı Hero widget‑ı aracılığıyla uygular ve her iki platformda aynı şekilde çalışır.
Evet, Hero Animation birden çok ortak öğeyi destekler. Flutter’da farklı etiketlere sahip birden çok Hero widget‑ı belirtin. SwiftUI’de her öğede farklı tanımlayıcılarla matchedGeometryEffect kullanın. Tüm animasyonlar paralel olarak çalışır — her öğe kendi yörüngesinde hareket eder.
Performans uygulamaya bağlıdır. Flutter FlightShuttle, görselleri RenderObject aracılığıyla işleyerek 60fps’ye ulaşır. SwiftUI matchedGeometryEffect vektörleştirilmiştir. Büyük görseller için Flutter’da precacheImage veya SwiftUI’de prefetch kullanın — bu, ilk geçişteki gecikmeleri ortadan kaldırır. Android Compose, AsyncImage aracılığıyla gecikmeli yükleme gerektirir.
Standart bir push geçişi tüm ekranı kaydırarak bir kart yığını yanılsaması yaratır. Hero Animation görsel dikkati tek bir öğeye sabitler — kullanıcı onun sürekli hareketini görür ve ekranlar arasındaki ilişkiyi daha iyi anlar. Push hiyerarşik gezinme için, Hero içeriği vurgulamak için uygundur.
Hayır, Hero Animation yerleşik araçlarla uygulanır. Flutter’da flutter/material.dart paketinden Hero widget‑ıdır. SwiftUI’de matchedGeometryEffect değiştiricisidir. Android Jetpack Compose’da standart Compose’dan SharedTransitionLayout ve Modifier.sharedElement’dir. Ek kitaplıklar yalnızca standart olmayan yörüngeler veya karmaşık dönüşümler için gereklidir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun