Poin Utama
Hero Animation — teknik animasi antarmuka di mana elemen bersama bergerak mulus dari layar sumber ke layar tujuan, mempertahankan identitasnya. Nama ini mencerminkan metafora "pahlawan" — elemen kunci yang melewati transisi panggung antar layar, menarik perhatian pengguna. Berbeda dengan transisi standar, Hero Animation tidak memutuskan hubungan visual: pengguna melihat lintasan kontinu elemen, yang meningkatkan pemahaman hierarki navigasi.
Konsep Hero Animation tertanam dalam Material Design sebagai rekomendasi untuk layar daftar-detail (list-detail), galeri, dan kartu. Algoritma animasi terdiri dari tiga fase: di layar sumber, elemen disorot dan diperbaiki pada posisi absolut; selama transisi, elemen diskalakan, dipindahkan, dan diubah bentuknya sesuai dengan wadah tujuan; di layar akhir, elemen diintegrasikan ke dalam tata letak baru. Menurut Material Design 3, animasi pahlawan yang diimplementasikan dengan benar mengurangi jumlah klik "kembali" yang salah sebesar 22%.
Di Flutter, Hero Animation diimplementasikan melalui widget Hero, yang menerima parameter wajib tag — pengenal unik. Flutter secara otomatis menemukan pasangan Hero dengan tag yang sama di rute sumber dan tujuan dan menjalankan animasi. Widget Hero tidak memerlukan pengelolaan animasi manual — framework menghitung sendiri lintasan dan menerapkan transformasi melalui objek internal FlightShuttle.
FlightShuttle — "pesawat ulang-alik" tak terlihat yang pada saat transisi membuat salinan overlay dari widget Hero, memposisikannya dalam sistem koordinat absolut dan menganimasikan transformasi (scale, translate, rotate) dari posisi awal ke posisi akhir. Kurva khusus dapat diteruskan ke FlightShuttle melalui properti flightShuttleBuilder, memungkinkan kontrol lintasan terbang: terbang dalam busur, memantul dari batas, atau melambat sebelum tiba. Di Flutter 3.16+, Hero mendukung animasi dengan mempertahankan clipRect dan borderRadius, memberikan efek kartu membulat yang berubah menjadi layar persegi panjang.
Di SwiftUI, padanan Hero Animation adalah modifier matchedGeometryEffect, diperkenalkan di iOS 14. Ini menghubungkan dua elemen melalui pengenal (id) dan ruang nama (namespace). Berbeda dengan Flutter di mana Hero secara otomatis mencegat transisi, SwiftUI memerlukan deklarasi eksplisit namespace dan penerapan modifier ke kedua bagian adegan — sumber dan tujuan — dalam hierarki yang sama atau melalui GeometryReader.
Animasi matchedGeometryEffect mendukung transformasi posisi, ukuran, rotasi, dan radius sudut (cornerRadius). Untuk mencapai kehalusan, Animation diatur dalam withAnimation. SwiftUI secara otomatis menginterpolasi keadaan antara berdasarkan kurva default easeInOut. Di iOS 17+, dukungan untuk matchedTransitionSource ditambahkan untuk TabView dan NavigationStack — analog sistem Hero Animation dengan lintasan yang telah ditentukan untuk transisi standar.
Di Android, Hero Animation disebut Shared Element Transition dan diimplementasikan melalui ActivityOptions.makeSceneTransitionAnimation atau FragmentTransaction.addSharedElement. Setiap elemen bersama ditandai dengan atribut transitionName di XML atau melalui kode, dan Android bertanggung jawab atas animasi antar aktivitas. Mulai Android 5.0 (API 21), transisi menjadi bagian dari framework; sebelumnya, animasi khusus harus ditulis melalui ValueAnimator.
Di Jetpack Compose, Shared Element Transition diimplementasikan melalui modifier Modifier.sharedElement dan Modifier.sharedBounds dalam kombinasi dengan AnimatedContent atau SharedTransitionLayout (tersedia sejak Compose BOM 2024.01). Berbeda dengan sistem View, Compose menggambar ulang elemen secara malas, sehingga elemen bersama di-cache di CompositionLocal. Compose juga mendukung animasi daftar ke detail (list→detail) dengan mempertahankan scrollOffset — kemampuan kunci untuk transisi Hero. Menurut Google I/O 2024, Compose Shared Element Transition bekerja 15% lebih cepat dari versi View berkat pengelolaan RenderNode secara langsung.
Contoh dasar Hero Animation di Flutter: thumbnail di layar daftar dan gambar layar penuh di layar detail. Tag image-hero menghubungkan dua widget, dan FlightShuttle secara otomatis menganimasikan transisi dari kartu ke layar penuh.
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,
),
),
),
);
}
}
Poin kunci — tag yang sama (image-hero) di kedua layar. Flutter mencocokkan widget Hero berdasarkan tag selama transisi rute. Jika tag berbeda, animasi tidak akan dimulai — elemen hanya akan muncul di layar baru tanpa transisi. Jenis apa pun dapat digunakan untuk tag, tetapi string adalah opsi yang paling dapat diandalkan.
Contoh Hero Animation di SwiftUI dengan NavigationStack. Modifier matchedGeometryEffect menghubungkan gambar di daftar dan layar detail melalui namespace, dan withAnimation memulai transisi mulus.
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 membuat ruang nama untuk pengenal matchedGeometryEffect. Kedua elemen menggunakan id yang sama (hero) dan namespace bersama. Saat isDetailShown diubah, SwiftUI menganimasikan transformasi ukuran, posisi, dan cornerRadius antara dua tampilan, menciptakan efek pahlawan. Untuk NavigationLink standar dengan iOS 16+, gunakan navigationTransition(.hero).
Contoh Shared Element Transition di Jetpack Compose dengan SharedTransitionLayout. Modifier sharedElement menganimasikan elemen bersama antar layar selama navigasi.
@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+) membungkus hierarki, dan modifier sharedElement dengan kunci hero menghubungkan elemen awal dan akhir. AnimatedContent mengubah keadaan, dan Compose secara otomatis menganimasikan ukuran, posisi, dan bentuk gambar. Keuntungan utama — dukungan untuk animasi bounds dan clipBounds, yang memberikan efek pembulatan kartu selama transisi.
Pertanyaan yang Sering Diajukan
Ya, Hero Animation tersedia di kedua platform. Di iOS digunakan matchedGeometryEffect di SwiftUI atau UINavigationController.transitioningDelegate. Di Android — transisi ActivityOptions.makeSceneTransitionAnimation atau Shared Element Transition di Jetpack Compose dengan Modifier.sharedElement. Flutter mengimplementasikan Hero Animation melalui widget Hero yang bekerja identik di kedua platform.
Ya, Hero Animation mendukung banyak elemen bersama. Di Flutter, tentukan beberapa Hero dengan tag berbeda. Di SwiftUI, gunakan matchedGeometryEffect dengan pengenal berbeda pada setiap elemen. Semua animasi dijalankan secara paralel — setiap elemen bepergian di lintasannya sendiri.
Kinerja tergantung pada implementasi. Flutter FlightShuttle memproses gambar melalui RenderObject, memberikan 60fps. SwiftUI matchedGeometryEffect telah divektorisasi. Untuk gambar besar, gunakan precacheImage di Flutter atau prefetch di SwiftUI — ini menghilangkan penundaan pada transisi pertama. Android Compose memerlukan pemuatan malas melalui AsyncImage.
Transisi push standar memindahkan seluruh layar, menciptakan ilusi tumpukan kartu. Hero Animation memusatkan perhatian visual pada satu elemen — pengguna melihat gerakan kontinunya, yang meningkatkan pemahaman hubungan antar layar. Push baik untuk navigasi hierarkis, Hero — untuk menonjolkan konten.
Tidak, Hero Animation diimplementasikan dengan alat bawaan. Di Flutter, ini adalah widget Hero dari paket flutter/material.dart. Di SwiftUI — modifier matchedGeometryEffect. Di Android Jetpack Compose — SharedTransitionLayout dan Modifier.sharedElement dari Compose standar. Pustaka tambahan hanya diperlukan untuk lintasan non-standar atau transformasi kompleks.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga