Главне тачке
Hero Animation — техника анимације интерфејса при којој се заједнички елемент глатко помера са изворног екрана на циљни, чувајући свој идентитет. Назив одражава метафору „хероја" — кључног елемента који пролази кроз сценски прелаз између екрана, привлачећи пажњу корисника. За разлику од стандардних прелаза, Hero Animation не прекида визуелну везу: корисник види континуирану путању елемента, што побољшава разумевање навигационе хијерархије.
Концепт Hero Animation је утемељен у Material Design-у као препорука за екране листа-детаљи (list-detail), галерије и картице. Алгоритам анимације се састоји од три фазе: на изворном екрану елемент се истиче и фиксира у апсолутној позицији; током прелаза се скалира, помера и мења облик у складу са циљним контејнером; на коначном екрану елемент се уграђује у нови распоред. Према Material Design 3, коректно имплементирана анимација хероја смањује број погрешних кликова „назад" за 22%.
У Flutter-у се Hero Animation реализује кроз виџет Hero, који прихвата обавезни параметар tag — јединствени идентификатор. Flutter аутоматски проналази пар Hero са истим тегом на изворној и циљној рути и покреће анимацију. Виџет Hero не захтева ручно управљање анимацијом — фрејмворк сам израчунава путању и примењује трансформацију кроз унутрашњи објекат FlightShuttle.
FlightShuttle — невидљиви „шатл" који у тренутку прелаза ствара копију Hero виџета преко целог екрана, позиционира је у систему апсолутних координата и анимира трансформацију (scale, translate, rotate) од почетне до крајње позиције. У FlightShuttle се могу проследити прилагођене криве кроз својство flightShuttleBuilder, што омогућава контролу путање лета: летење по луку, одбијање од граница или успоравање пре доласка. У Flutter 3.16+ Hero подржава анимацију са очувањем clipRect и borderRadius, што даје ефекат заобљених картица које се трансформишу у правоугаоне екране.
У SwiftUI-ју еквивалент Hero Animation-у је модификатор matchedGeometryEffect, представљен у iOS 14. Он повезује два елемента кроз идентификатор (id) и именски простор (namespace). За разлику од Flutter-а где Hero аутоматски пресреће прелаз, SwiftUI захтева експлицитно декларисање namespace-а и примену модификатора на оба дела сцене — изворни и циљни — унутар исте хијерархије или кроз GeometryReader.
Анимација matchedGeometryEffect подржава трансформацију позиције, величине, ротације и заобљења углова (cornerRadius). За постизање глаткости поставља се Animation у withAnimation. SwiftUI аутоматски интерполира међустања на основу подразумеване криве easeInOut. У iOS 17+ додата је подршка за matchedTransitionSource за TabView и NavigationStack — системски аналог Hero Animation-а са унапред дефинисаном путањом за стандардне прелазе.
У Android-у се Hero Animation назива Shared Element Transition и реализује се кроз ActivityOptions.makeSceneTransitionAnimation или FragmentTransaction.addSharedElement. Сваки заједнички елемент се означава атрибутом transitionName у XML-у или кроз код, а Android је одговоран за анимацију између активности. Од Android 5.0 (API 21) прелази су постали део фрејмворка; пре тога су се морале писати прилагођене анимације кроз ValueAnimator.
У Jetpack Compose-у Shared Element Transition је реализован кроз модификатор Modifier.sharedElement и Modifier.sharedBounds у комбинацији са AnimatedContent или SharedTransitionLayout (доступан од Compose BOM 2024.01). За разлику од View система, Compose лењо прецртава елементе, па се заједнички елемент кешира у CompositionLocal. Compose такође подржава анимацију листе у детаље (list→detail) са очувањем scrollOffset-а — кључна могућност за Hero прелазе. Према Google I/O 2024, Compose Shared Element Transition ради 15% брже од View верзије захваљујући директном управљању RenderNode-ом.
Основни пример Hero Animation у Flutter-у: минијатура на екрану листе и слика преко целог екрана на екрану детаља. Тег image-hero повезује два виџета, а FlightShuttle аутоматски анимира прелаз од картице до пуног екрана.
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,
),
),
),
);
}
}
Кључни моменат — исти тег (image-hero) на оба екрана. Flutter повезује Hero виџете по тегу током прелаза руте. Ако се тегови разликују, анимација се неће покренути — елемент ће се једноставно појавити на новом екрану без прелаза. Може се користити било који тип за тег, али низови карактера су најпоузданија опција.
Пример Hero Animation у SwiftUI-ју са NavigationStack-ом. Модификатор matchedGeometryEffect повезује слику у листи и екрану детаља кроз namespace, а withAnimation покреће глатки прелаз.
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-а. Оба елемента користе исти id (hero) и заједнички namespace. При пребацивању isDetailShown, SwiftUI анимира трансформацију величине, позиције и cornerRadius-а између два приказа, стварајући ефекат хероја. За стандардни NavigationLink са iOS 16+ користите navigationTransition(.hero).
Пример Shared Element Transition у Jetpack Compose-у са SharedTransitionLayout. Модификатор sharedElement анимира заједнички елемент између екрана током навигације.
@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+) обавија хијерархију, а модификатор sharedElement са кључем hero повезује почетни и завршни елемент. AnimatedContent мења стање, а Compose аутоматски анимира величину, позицију и облик слике. Кључна предност — подршка за анимацију bounds и clipBounds, што даје ефекат заобљења картице током прелаза.
Често постављана питања
Да, Hero Animation је доступан на обе платформе. У iOS-у се користи matchedGeometryEffect у SwiftUI-ју или UINavigationController.transitioningDelegate. У Android-у — прелаз ActivityOptions.makeSceneTransitionAnimation или Shared Element Transition у Jetpack Compose-у са Modifier.sharedElement. Flutter реализује Hero Animation кроз виџет Hero који ради идентично на обе платформе.
Да, Hero Animation подржава вишеструке заједничке елементе. У Flutter-у наведите неколико Hero са различитим теговима. У SwiftUI-ју користите matchedGeometryEffect са различитим идентификаторима на сваком елементу. Све анимације се извршавају паралелно — сваки елемент путује својом путањом.
Перформансе зависе од имплементације. Flutter FlightShuttle обрађује слике кроз RenderObject, што даје 60fps. SwiftUI matchedGeometryEffect је векторизован. За велике слике користите precacheImage у Flutter-у или prefetch у SwiftUI-ју — ово елиминише кашњења при првом прелазу. Android Compose захтева лењо учитавање кроз AsyncImage.
Стандардни push прелаз помера цео екран, стварајући илузију гомиле карата. Hero Animation фиксира визуелну пажњу на једном елементу — корисник види његово континуирано кретање, што побољшава разумевање везе између екрана. Push је добар за хијерархијску навигацију, Hero — за истицање садржаја.
Не, Hero Animation је имплементирана уграђеним средствима. У Flutter-у је то виџет Hero из пакета flutter/material.dart. У SwiftUI-ју — модификатор matchedGeometryEffect. У Android Jetpack Compose-у — SharedTransitionLayout и Modifier.sharedElement из стандардног Compose-а. Додатне библиотеке су потребне само за нестандардне путање или сложене трансформације.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође