Huvudpunkter
Hero Animation — en gränssnittsanimationsteknik där ett delat element smidigt flyttas från källskärmen till målskärmen och behåller sin identitet. Namnet återspeglar metaforen om "hjälten" — nyckelelementet som passerar genom en scenisk övergång mellan skärmar och fångar användarens uppmärksamhet. Till skillnad från standardövergångar bryter Hero Animation inte den visuella kopplingen: användaren ser elementets kontinuerliga bana, vilket förbättrar förståelsen av navigeringshierarkin.
Konceptet Hero Animation är förankrat i Material Design som en rekommendation för list-detail-skärmar, gallerier och kort. Animationsalgoritmen består av tre faser: på källskärmen markeras elementet och fixeras i absolut position; under övergången skalas, flyttas och ändrar det form enligt målbehållaren; på den slutliga skärmen integreras elementet i den nya layouten. Enligt Material Design 3 minskar en korrekt implementerad hjälteanimation antalet felaktiga "tillbaka"-klick med 22 %.
I Flutter implementeras Hero Animation via Hero-widgeten, som tar emot den obligatoriska parametern tag — en unik identifierare. Flutter hittar automatiskt paret Hero med samma tagg på käll- och målrutterna och startar animationen. Hero-widgeten kräver ingen manuell animationshantering — ramverket beräknar själv banan och tillämpar transformationen via det interna FlightShuttle-objektet.
FlightShuttle — en osynlig "skyttel" som vid övergångsögonblicket skapar en överlagringskopia av Hero-widgeten, placerar den i det absoluta koordinatsystemet och animerar transformationen (scale, translate, rotate) från startpositionen till slutpositionen. Anpassade kurvor kan skickas till FlightShuttle via egenskapen flightShuttleBuilder, vilket möjliggör kontroll av flygbanan: flyga i en båge, studsa mot kanter eller sakta ner före ankomst. I Flutter 3.16+ stöder Hero animering med bevarande av clipRect och borderRadius, vilket ger effekten av rundade kort som förvandlas till rektangulära skärmar.
I SwiftUI är motsvarigheten till Hero Animation modifieraren matchedGeometryEffect, introducerad i iOS 14. Den kopplar samman två element via en identifierare (id) och ett namnområde (namespace). Till skillnad från Flutter där Hero automatiskt fångar upp övergången, kräver SwiftUI en explicit deklaration av namespace och applicering av modifieraren på båda delarna av scenen — källa och mål — inom samma hierarki eller via GeometryReader.
matchedGeometryEffect-animationen stöder transformation av position, storlek, rotation och hörnradie (cornerRadius). För jämnhet ställs Animation in i withAnimation. SwiftUI interpolerar automatiskt mellanliggande tillstånd baserat på standardkurvan easeInOut. I iOS 17+ har stöd för matchedTransitionSource lagts till för TabView och NavigationStack — en systemmotsvarighet till Hero Animation med fördefinierad bana för standardövergångar.
I Android kallas Hero Animation Shared Element Transition och implementeras via ActivityOptions.makeSceneTransitionAnimation eller FragmentTransaction.addSharedElement. Varje delat element markeras med attributet transitionName i XML eller via kod, och Android ansvarar för animationen mellan aktiviteter. Från och med Android 5.0 (API 21) blev övergångar en del av ramverket; innan dess var man tvungen att skriva anpassade animationer via ValueAnimator.
I Jetpack Compose implementeras Shared Element Transition via modifieraren Modifier.sharedElement och Modifier.sharedBounds i kombination med AnimatedContent eller SharedTransitionLayout (tillgänglig från Compose BOM 2024.01). Till skillnad från View-systemet ritar Compose om element lättjefullt, så det delade elementet cachas i CompositionLocal. Compose stöder också animation från lista till detalj (list→detail) med bevarande av scrollOffset — en nyckelfunktion för Hero-övergångar. Enligt Google I/O 2024 fungerar Compose Shared Element Transition 15 % snabbare än View-versionen tack vare direkt hantering av RenderNode.
Grundläggande exempel på Hero Animation i Flutter: en miniatyr på listskärmen och en helskärmsbild på detaljskärmen. Taggen image-hero kopplar samman de två widgetarna, och FlightShuttle animerar automatiskt övergången från kort till helskärm.
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,
),
),
),
);
}
}
Nyckelpunkten — samma tagg (image-hero) på båda skärmarna. Flutter matchar Hero-widgetar efter tagg under ruttövergången. Om taggarna skiljer sig åt startar inte animationen — elementet visas helt enkelt på den nya skärmen utan övergång. Vilken typ som helst kan användas för taggen, men strängar är det mest pålitliga alternativet.
Exempel på Hero Animation i SwiftUI med NavigationStack. Modifieraren matchedGeometryEffect kopplar samman bilden i listan och detaljskärmen via namespace, och withAnimation startar en smidig övergång.
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 skapar ett namnområde för matchedGeometryEffect-identifieraren. Båda elementen använder samma id (hero) och delat namespace. När isDetailShown växlas animerar SwiftUI transformationen av storlek, position och cornerRadius mellan de två vyerna, vilket skapar hjälteeffekten. För standard NavigationLink med iOS 16+, använd navigationTransition(.hero).
Exempel på Shared Element Transition i Jetpack Compose med SharedTransitionLayout. Modifieraren sharedElement animerar det delade elementet mellan skärmar under navigering.
@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+) omsluter hierarkin, och modifieraren sharedElement med nyckeln hero kopplar samman start- och slutelementen. AnimatedContent växlar tillstånd, och Compose animerar automatiskt bildens storlek, position och form. Den viktigaste fördelen — stöd för bounds- och clipBounds-animering, vilket ger effekten av kortavrundning under övergången.
Vanliga frågor
Ja, Hero Animation är tillgänglig på båda plattformarna. I iOS används matchedGeometryEffect i SwiftUI eller UINavigationController.transitioningDelegate. I Android — övergången ActivityOptions.makeSceneTransitionAnimation eller Shared Element Transition i Jetpack Compose med Modifier.sharedElement. Flutter implementerar Hero Animation via Hero-widgeten som fungerar identiskt på båda plattformarna.
Ja, Hero Animation stöder flera delade element. I Flutter, ange flera Hero med olika taggar. I SwiftUI, använd matchedGeometryEffect med olika identifierare på varje element. Alla animationer körs parallellt — varje element reser längs sin egen bana.
Prestanda beror på implementeringen. Flutter FlightShuttle bearbetar bilder via RenderObject, vilket ger 60fps. SwiftUI matchedGeometryEffect är vektoriserad. För stora bilder, använd precacheImage i Flutter eller prefetch i SwiftUI — detta eliminerar fördröjningar vid första övergången. Android Compose kräver lättjefull laddning via AsyncImage.
En vanlig push-övergång flyttar hela skärmen och skapar en illusion av en kortstack. Hero Animation fixerar visuell uppmärksamhet på ett element — användaren ser dess kontinuerliga rörelse, vilket förbättrar förståelsen av sambandet mellan skärmar. Push är bra för hierarkisk navigering, Hero — för att framhäva innehåll.
Nej, Hero Animation är implementerad med inbyggda verktyg. I Flutter är det Hero-widgeten från paketet flutter/material.dart. I SwiftUI — modifieraren matchedGeometryEffect. I Android Jetpack Compose — SharedTransitionLayout och Modifier.sharedElement från standard Compose. Extra bibliotek behövs endast för icke-standardiserade banor eller komplexa transformationer.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också