Hero Animation: vad är det, animation av delade element i Flutter

Författare: IT Sectr Publicerad: 2026-03-02 Lästid: 8 min
Hero Animation — ett animationsmönster där ett gränssnittselement smidigt flyttar sig och transformeras mellan skärmar, vilket skapar en effekt av en "hjälte" som reser från ett sammanhang till ett annat. Till skillnad från den vanliga push/pop-övergången bevarar hjälteanimationen visuell kontinuitet: användaren ser hur en miniatyr förvandlas till en helskärmsbild eller hur en cellrubrik flyttas till skärmrubriken. Enligt Google Material Design (2025) minskar Hero Animation kognitiv belastning med 30 % vid navigering mellan lista och detaljer — användaren tappar inte fokus tack vare att elementets position memoreras. I Flutter implementeras mönstret via Hero-widgeten, i SwiftUI via matchedGeometryEffect-modifieraren, i Jetpack Compose via Modifier.sharedElement.

Huvudpunkter

  • Hero Animation — smidig övergång av ett delat element mellan skärmar, med bibehållen visuell kontext för användaren.
  • Flutter Hero — widget som automatiskt hittar ett par baserat på tagg och startar FlightShuttle-animationen mellan skärmar.
  • matchedGeometryEffect — SwiftUI-modifierare som kopplar samman element via en identifierare i namespace för en animerad övergång.
  • Shared Element Transition — Android-mekanism, implementerad via ActivityOptions eller Modifier.sharedElement i Jetpack Compose.
  • FlightShuttle — intern Flutter-mekanism som hanterar banan och transformationen av Hero-widgeten under övergången.

Vad är Hero Animation?

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 %.

Hero i Flutter: taggar och FlightShuttle

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.

matchedGeometryEffect i SwiftUI

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.

Shared Element Transition i Android

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.

Kodexempel

Flutter: Hero med bild

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.

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

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.

SwiftUI: matchedGeometryEffect mellan skärmar

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.

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 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).

Android: Shared Element Transition med Jetpack Compose

Exempel på Shared Element Transition i Jetpack Compose med SharedTransitionLayout. Modifieraren sharedElement animerar det delade elementet mellan skärmar under navigering.

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+) 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

Fungerar Hero Animation på iOS och Android?

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.

Kan flera element animeras samtidigt?

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.

Bliir Hero Animation långsam på stora bilder?

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.

Hur skiljer sig Hero från en vanlig push-övergång?

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.

Behövs extra bibliotek för Hero Animation?

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

  • Hero Animation — animation av delade element mellan skärmar, med bibehållen visuell kontinuitet och minskad kognitiv belastning.
  • Flutter Hero med tagg och FlightShuttle — det enklaste sättet att implementera hjälteanimation utan manuell animationshantering.
  • SwiftUI matchedGeometryEffect med @Namespace.id animerar elementets position, storlek och cornerRadius mellan två tillstånd.
  • Android SharedTransitionLayout med Modifier.sharedElement i Jetpack Compose ger övergång med RenderNode-cachning.
  • Alla tre ramverken stöder flera delade element med parallell animering.
  • FlightShuttle möjliggör anpassning av flygbanan via flightShuttleBuilder i Flutter.
  • Hero Animation rekommenderas av Material Design för list-detail-skärmar, gallerier och kort.

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.

Diskutera projektet

Läs också