Hlavní body
Hero Animation — technika animace rozhraní, při které se sdílený prvek plynule přesouvá z zdrojové obrazovky na cílovou, přičemž si zachovává svou identitu. Název odráží metaforu „hrdiny" — klíčového prvku, který prochází scénickým přechodem mezi obrazovkami a přitahuje pozornost uživatele. Na rozdíl od standardních přechodů Hero Animation nepřerušuje vizuální spojení: uživatel vidí spojitou trajektorii prvku, což zlepšuje porozumění navigační hierarchii.
Koncepce Hero Animation je zakotvena v Material Design jako doporučení pro obrazovky seznam-detail (list-detail), galerie a karty. Algoritmus animace se skládá ze tří fází: na zdrojové obrazovce je prvek zvýrazněn a fixován v absolutní poloze; během přechodu je škálován, přesouván a mění tvar podle cílového kontejneru; na konečné obrazovce je prvek integrován do nového rozvržení. Podle Material Design 3 správně implementovaná animace hrdiny snižuje počet chybných kliknutí „zpět" o 22 %.
Ve Flutter je Hero Animation implementována pomocí widgetu Hero, který přijímá povinný parametr tag — jedinečný identifikátor. Flutter automaticky najde pár Hero se stejným tagem na zdrojové a cílové trase a spustí animaci. Widget Hero nevyžaduje ruční správu animace — framework si sám vypočítá trajektorii a aplikuje transformaci pomocí vnitřního objektu FlightShuttle.
FlightShuttle — neviditelný „raketoplán", který v okamžiku přechodu vytvoří překryvnou kopii widgetu Hero, umístí ji v absolutním souřadnicovém systému a animuje transformaci (scale, translate, rotate) z počáteční do konečné polohy. Do FlightShuttle lze předat vlastní křivky pomocí vlastnosti flightShuttleBuilder, což umožňuje řídit trajektorii letu: letět po oblouku, odrážet se od okrajů nebo zpomalovat před příletem. Ve Flutter 3.16+ Hero podporuje animaci se zachováním clipRect a borderRadius, což dává efekt zaoblených karet transformujících se na obdélníkové obrazovky.
Ve SwiftUI je ekvivalentem Hero Animation modifikátor matchedGeometryEffect, představený v iOS 14. Ten spojuje dva prvky pomocí identifikátoru (id) a jmenného prostoru (namespace). Na rozdíl od Flutter, kde Hero automaticky zachycuje přechod, SwiftUI vyžaduje explicitní deklaraci namespace a aplikaci modifikátoru na obě části scény — zdrojovou a cílovou — ve stejné hierarchii nebo pomocí GeometryReader.
Animace matchedGeometryEffect podporuje transformaci pozice, velikosti, rotace a poloměru rohů (cornerRadius). Pro dosažení plynulosti se Animation nastavuje v withAnimation. SwiftUI automaticky interpoluje mezistavy na základě výchozí křivky easeInOut. V iOS 17+ byla přidána podpora pro matchedTransitionSource pro TabView a NavigationStack — systémový analog Hero Animation s předdefinovanou trajektorií pro standardní přechody.
V Androidu se Hero Animation nazývá Shared Element Transition a je implementována pomocí ActivityOptions.makeSceneTransitionAnimation nebo FragmentTransaction.addSharedElement. Každý sdílený prvek je označen atributem transitionName v XML nebo pomocí kódu a Android je zodpovědný za animaci mezi aktivitami. Od Androidu 5.0 (API 21) se přechody staly součástí frameworku; předtím bylo nutné psát vlastní animace pomocí ValueAnimator.
V Jetpack Compose je Shared Element Transition implementován pomocí modifikátoru Modifier.sharedElement a Modifier.sharedBounds v kombinaci s AnimatedContent nebo SharedTransitionLayout (dostupný od Compose BOM 2024.01). Na rozdíl od systému View Compose líně překresluje prvky, takže sdílený prvek je ukládán do cache v CompositionLocal. Compose také podporuje animaci seznamu do detailu (list→detail) se zachováním scrollOffset — klíčová schopnost pro Hero přechody. Podle Google I/O 2024 funguje Compose Shared Element Transition o 15 % rychleji než verze View díky přímému řízení RenderNode.
Základní příklad Hero Animation ve Flutter: miniatura na obrazovce seznamu a obrázek přes celou obrazovku na obrazovce detailů. Tag image-hero spojuje dva widgety a FlightShuttle automaticky animuje přechod z karty na celou obrazovku.
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,
),
),
),
);
}
}
Klíčový bod — stejný tag (image-hero) na obou obrazovkách. Flutter přiřazuje widgety Hero podle tagu během přechodu trasy. Pokud se tagy liší, animace se nespustí — prvek se jednoduše objeví na nové obrazovce bez přechodu. Pro tag lze použít jakýkoli typ, ale řetězce jsou nejspolehlivější volbou.
Příklad Hero Animation ve SwiftUI s NavigationStack. Modifikátor matchedGeometryEffect spojuje obrázek v seznamu a detailní obrazovce pomocí namespace a withAnimation spouští plynulý přechod.
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 vytváří jmenný prostor pro identifikátor matchedGeometryEffect. Oba prvky používají stejné id (hero) a sdílený namespace. Při přepnutí isDetailShown SwiftUI animuje transformaci velikosti, pozice a cornerRadius mezi dvěma pohledy a vytváří efekt hrdiny. Pro standardní NavigationLink s iOS 16+ použijte navigationTransition(.hero).
Příklad Shared Element Transition v Jetpack Compose s SharedTransitionLayout. Modifikátor sharedElement animuje sdílený prvek mezi obrazovkami během navigace.
@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+) obaluje hierarchii a modifikátor sharedElement s klíčem hero spojuje počáteční a koncové prvky. AnimatedContent přepíná stav a Compose automaticky animuje velikost, pozici a tvar obrázku. Klíčovou výhodou je podpora animace bounds a clipBounds, což dává efekt zaoblení karty během přechodu.
Často kladené otázky
Ano, Hero Animation je k dispozici na obou platformách. V iOS se používá matchedGeometryEffect ve SwiftUI nebo UINavigationController.transitioningDelegate. V Androidu — přechod ActivityOptions.makeSceneTransitionAnimation nebo Shared Element Transition v Jetpack Compose s Modifier.sharedElement. Flutter implementuje Hero Animation pomocí widgetu Hero, který funguje na obou platformách identicky.
Ano, Hero Animation podporuje více sdílených prvků. Ve Flutter zadejte několik Hero s různými tagy. Ve SwiftUI použijte matchedGeometryEffect s různými identifikátory na každém prvku. Všechny animace se provádějí paralelně — každý prvek cestuje po své vlastní trajektorii.
Výkon závisí na implementaci. Flutter FlightShuttle zpracovává obrázky přes RenderObject, což poskytuje 60fps. SwiftUI matchedGeometryEffect je vektorizovaný. Pro velké obrázky použijte precacheImage ve Flutter nebo prefetch ve SwiftUI — to eliminuje zpoždění při prvním přechodu. Android Compose vyžaduje líné načítání přes AsyncImage.
Standardní push přechod posouvá celou obrazovku a vytváří iluzi hromádky karet. Hero Animation fixuje vizuální pozornost na jednom prvku — uživatel vidí jeho nepřetržitý pohyb, což zlepšuje porozumění vztahu mezi obrazovkami. Push je vhodný pro hierarchickou navigaci, Hero — pro zvýraznění obsahu.
Ne, Hero Animation je implementována vestavěnými prostředky. Ve Flutter je to widget Hero z balíčku flutter/material.dart. Ve SwiftUI — modifikátor matchedGeometryEffect. V Android Jetpack Compose — SharedTransitionLayout a Modifier.sharedElement ze standardního Compose. Další knihovny jsou potřeba pouze pro nestandardní trajektorie nebo složité transformace.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také