Βασικά σημεία
Hero Animation — μια τεχνική κινούμενων σχεδίων διεπαφής κατά την οποία ένα κοινό στοιχείο μετακινείται ομαλά από την οθόνη προέλευσης στην οθόνη προορισμού, διατηρώντας την ταυτότητά του. Το όνομα αντικατοπτρίζει τη μεταφορά του "ήρωα" — το βασικό στοιχείο που περνά μέσα από μια σκηνική μετάβαση μεταξύ οθονών, προσελκύοντας την προσοχή του χρήστη. Σε αντίθεση με τις τυπικές μεταβάσεις, το Hero Animation δεν σπάει την οπτική σύνδεση: ο χρήστης βλέπει τη συνεχή τροχιά του στοιχείου, βελτιώνοντας την κατανόηση της ιεραρχίας πλοήγησης.
Η ιδέα του Hero Animation είναι ριζωμένη στο Material Design ως σύσταση για οθόνες λίστας-λεπτομέρειας (list-detail), γκαλερί και κάρτες. Ο αλγόριθμος κινούμενων σχεδίων αποτελείται από τρεις φάσεις: στην οθόνη προέλευσης, το στοιχείο τονίζεται και στερεώνεται σε απόλυτη θέση· κατά τη διάρκεια της μετάβασης, κλιμακώνεται, μετακινείται και αλλάζει σχήμα σύμφωνα με το δοχείο προορισμού· στην τελική οθόνη, το στοιχείο ενσωματώνεται στη νέα διάταξη. Σύμφωνα με το Material Design 3, ένα σωστά υλοποιημένο κινούμενο σχέδιο ήρωα μειώνει τον αριθμό των λανθασμένων κλικ "πίσω" κατά 22%.
Στο Flutter, το Hero Animation υλοποιείται μέσω του widget Hero, το οποίο δέχεται την υποχρεωτική παράμετρο tag — ένα μοναδικό αναγνωριστικό. Το Flutter βρίσκει αυτόματα το ζεύγος Hero με την ίδια ετικέτα στις διαδρομές προέλευσης και προορισμού και εκκινεί το κινούμενο σχέδιο. Το widget Hero δεν απαιτεί χειροκίνητη διαχείριση κινούμενων σχεδίων — το framework υπολογίζει μόνο του την τροχιά και εφαρμόζει τον μετασχηματισμό μέσω του εσωτερικού αντικειμένου FlightShuttle.
FlightShuttle — ένα αόρατο "λεωφορείο" που τη στιγμή της μετάβασης δημιουργεί ένα αντίγραφο επικάλυψης του widget Hero, το τοποθετεί στο απόλυτο σύστημα συντεταγμένων και δημιουργεί κινούμενο σχέδιο μετασχηματισμού (scale, translate, rotate) από την αρχική θέση στην τελική. Στο FlightShuttle μπορούν να μεταδοθούν προσαρμοσμένες καμπύλες μέσω της ιδιότητας flightShuttleBuilder, επιτρέποντας τον έλεγχο της τροχιάς πτήσης: πτήση σε τόξο, αναπήδηση από όρια ή επιβράδυνση πριν από την άφιξη. Στο Flutter 3.16+, το Hero υποστηρίζει κινούμενο σχέδιο με διατήρηση του clipRect και borderRadius, δίνοντας το εφέ στρογγυλεμένων καρτών που μετατρέπονται σε ορθογώνιες οθόνες.
Στο SwiftUI, το αντίστοιχο του Hero Animation είναι ο modifier matchedGeometryEffect, που εισήχθη στο iOS 14. Συνδέει δύο στοιχεία μέσω ενός αναγνωριστικού (id) και ενός χώρου ονομάτων (namespace). Σε αντίθεση με το Flutter όπου το Hero υποκλέπτει αυτόματα τη μετάβαση, το SwiftUI απαιτεί ρητή δήλωση του namespace και εφαρμογή του modifier και στα δύο μέρη της σκηνής — προέλευσης και προορισμού — εντός της ίδιας ιεραρχίας ή μέσω 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), οι μεταβάσεις έγιναν μέρος του framework· πριν από αυτό, έπρεπε να γράφονται προσαρμοσμένα κινούμενα σχέδια μέσω ValueAnimator.
Στο Jetpack Compose, το Shared Element Transition υλοποιείται μέσω του modifier 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 συνδέει τα δύο widget, και το 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 ταιριάζει τα widget Hero βάσει ετικέτας κατά τη μετάβαση διαδρομής. Εάν οι ετικέτες διαφέρουν, το κινούμενο σχέδιο δεν θα ξεκινήσει — το στοιχείο θα εμφανιστεί απλά στη νέα οθόνη χωρίς μετάβαση. Μπορεί να χρησιμοποιηθεί οποιοσδήποτε τύπος για ετικέτα, αλλά οι συμβολοσειρές είναι η πιο αξιόπιστη επιλογή.
Παράδειγμα Hero Animation στο SwiftUI με NavigationStack. Ο modifier 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. Ο modifier 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+) περιτυλίγει την ιεραρχία, και ο modifier 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 μέσω του widget 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, είναι το widget Hero από το πακέτο flutter/material.dart. Στο SwiftUI — ο modifier matchedGeometryEffect. Στο Android Jetpack Compose — SharedTransitionLayout και Modifier.sharedElement από το τυπικό Compose. Επιπλέον βιβλιοθήκες απαιτούνται μόνο για μη τυπικές τροχιές ή σύνθετους μετασχηματισμούς.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης