Βασικά σημεία
Timeline — είναι το μοντέλο χρόνου της κίνησης που καθορίζει πότε ξεκινά η κίνηση, πόσο διαρκεί και πώς η πάροδος του χρόνου μετατρέπεται σε αλλαγή της παραμέτρου που κινείται. Σε αντίθεση με την κίνηση καρέ-προς-καρέ (frame-by-frame), όπου κάθε καρέ ορίζεται χειροκίνητα, η κίνηση timeline περιγράφεται από παραμέτρους: duration — συνολική διάρκεια, startDelay — καθυστέρηση πριν την έναρξη, repeatCount — αριθμός επαναλήψεων και timingFunction — καμπύλη ταχύτητας.
Η χρονογραμμή λειτουργεί σε κανονικοποιημένο χώρο [0, 1] — local time ή progress. Η τιμή μηδέν αντιστοιχεί στην αρχή της κίνησης, ένα — στην ολοκλήρωση. Η συνάρτηση easing μετατρέπει τον γραμμικό local time σε πραγματική πρόοδο κίνησης. Αν repeatCount > 1, το timeline επαναφέρεται κυκλικά (RESTART) ή αντιστρέφεται (REVERSE). Στο iOS, το CAMediaTiming προσθέτει παραμέτρους speed (ταχύτητα αναπαραγωγής, >1 επιταχύνει) και timeOffset (μετατόπιση χρονογραμμής). Στο Android, το Animator χρησιμοποιεί setCurrentPlayTime() για άμεσο έλεγχο της θέσης στη χρονογραμμή. Σύμφωνα με το Material Design, κινήσεις μικρότερες από 100 ms δεν γίνονται αντιληπτές ως κίνηση, ενώ μεγαλύτερες από 500 ms αρχίζουν να ενοχλούν. Η βέλτιστη διάρκεια για κινήσεις διεπαφής είναι 200–350 ms.
Στο iOS, η διαχείριση της χρονογραμμής κίνησης γίνεται μέσω του πρωτοκόλλου CAMediaTiming, το οποίο υλοποιούν τα CAAnimation και CALayer. Παρέχει τις ιδιότητες: duration (διάρκεια σε δευτερόλεπτα), beginTime (χρόνος έναρξης σε σχέση με το γονικό timeline), repeatCount (αριθμός επαναλήψεων), repeatDuration (συνολική διάρκεια επαναλήψεων), autoreverses (αυτόματη αντιστροφή), fillMode (συμπεριφορά πριν/μετά την κίνηση), speed (ταχύτητα αναπαραγωγής) και timeOffset (μετατόπιση στη χρονογραμμή).
CACurrentMediaTime() — συνάρτηση που επιστρέφει τον απόλυτο χρόνο σε δευτερόλεπτα από την τελευταία επανεκκίνηση της συσκευής. Χρησιμοποιείται ως βασική χρονική σήμανση για το συγχρονισμό πολλαπλών κινήσεων: ορίστε beginTime = CACurrentMediaTime() + offset για να ξεκινήσετε μια ομάδα κινήσεων με ακριβή καθυστέρηση. Το CAMediaTiming υποστηρίζει επίσης ιεραρχία timeline — το γονικό επίπεδο μπορεί να επιταχύνει/επιβραδύνει όλες τις θυγατρικές κινήσεις μέσω της ιδιότητας speed. Στο UIKit, το UIViewPropertyAnimator (iOS 10+) αφαιρεί την πολυπλοκότητα της χρονογραμμής μέσω των ιδιοτήτων duration, delay και των callback startAnimation/pauseAnimation/stopAnimation. Στο SwiftUI, η δομή Animation παρέχει το duration ως μέρος της προδιαγραφής κίνησης, αλλά ο άμεσος έλεγχος της χρονογραμμής είναι κρυμμένος — για προσαρμοσμένο timeline χρησιμοποιήστε το TimelineView (iOS 15+) με δικό σας scheduler.
Στο Android, η διαχείριση της χρονογραμμής είναι συγκεντρωμένη στην κλάση Animator και τις υποκλάσεις της (ValueAnimator, ObjectAnimator). Οι κύριες ιδιότητες του timeline: duration (διάρκεια σε χιλιοστά του δευτερολέπτου), startDelay (καθυστέρηση πριν την έναρξη), repeatCount (αριθμός επαναλήψεων), repeatMode (RESTART ή REVERSE). Η μέθοδος setCurrentPlayTime(long) επιτρέπει την κίνηση κατά μήκος της χρονογραμμής σε οποιαδήποτε θέση.
Το ValueAnimator προσθέτει ένα επίπεδο αφαίρεσης: δημιουργεί τιμές από 0 έως 1 (normalized time) βάσει του duration και τις μεταβιβάζει στο TimeInterpolator. Το AnimatorSet επιτρέπει το συνδυασμό πολλαπλών Animator σε μια ενιαία χρονογραμμή με σειριακή (playSequentially) ή παράλληλη (playTogether) εκτέλεση. Στο Jetpack Compose, η χρονογραμμή διαχειρίζεται μέσω AnimationSpec: tween (durationMillis, delayMillis, easing), spring (dampingRatio, stiffness) και keyframes (σημεία στη χρονογραμμή με προσαρμοσμένο easing για κάθε τμήμα). Το Compose AnimatedContent χρησιμοποιεί timeline για transitionSpec — ο προγραμματιστής περιγράφει πώς το περιεχόμενο εμφανίζεται/εξαφανίζεται με χρονικές μετατοπίσεις. Σύμφωνα με το Android Performance, η χρονογραμμή κίνησης πρέπει να είναι πολλαπλάσιο των 16 ms (κύκλος vsync) για 60fps — η λανθασμένη διάρκεια μπορεί να προκαλέσει απώλεια καρέ λόγω αναντιστοιχίας με το ρυθμό ανανέωσης της οθόνης.
Η χρονογραμμή γίνεται ιδιαίτερα σημαντική κατά τη σύνθεση πολλαπλών κινήσεων. Τρία βασικά μοτίβα: καταρράκτης — οι κινήσεις ξεκινούν σειριακά με μετατόπιση (κάθε επόμενη ξεκινά μετά από offset από την προηγούμενη), παράλληλο — όλες οι κινήσεις ξεκινούν ταυτόχρονα με την ίδια χρονογραμμή και stagger — οι κινήσεις ξεκινούν με επικάλυψη (η δεύτερη ξεκινά πριν την ολοκλήρωση της πρώτης).
Στο iOS, ο καταρράκτης υλοποιείται μέσω beginTime κάθε CAAnimation = προηγούμενο beginTime + προηγούμενο duration. Για stagger χρησιμοποιείται CAAnimationGroup με διαφορετικά beginTime στις ένθετες κινήσεις. Στο Android, το AnimatorSet υποστηρίζει playSequentially και playTogether· το stagger δημιουργείται με ρύθμιση startDelay για κάθε Animator. Στο Compose, το AnimatedVisibility με enter/exitTransition χρησιμοποιεί stagger στην παραλλαγή staggerChildren για καταρρακτώδη εμφάνιση στοιχείων λίστας. Στο WWDC και το Google I/O 2024, και οι δύο πλατφόρμες παρουσίασαν εργαλεία οπτικοποίησης χρονογραμμής: Xcode Animations Inspector και Android Studio Animations Timeline — δείχνουν την πραγματική χρονογραμμή και τα χαμένα καρέ σε πραγματικό χρόνο.
Παράδειγμα κίνησης με προσαρμοσμένη χρονογραμμή: καθυστέρηση 0.3 δευτ., διάρκεια 0.6 δευτ., autoreverse και ατελείωτη επανάληψη. Το CAMediaTiming διαχειρίζεται όλες τις χρονικές παραμέτρους.
import UIKit
func breathingAnimation(layer: CALayer) {
let animation = CABasicAnimation(keyPath: "opacity")
animation.duration = 0.6
animation.beginTime = CACurrentMediaTime() + 0.3
animation.fromValue = 1.0
animation.toValue = 0.3
animation.autoreverses = true
animation.repeatCount = .infinity
animation.speed = 1.0
animation.fillMode = .both
layer.add(animation, forKey: "breathing")
}
beginTime = CACurrentMediaTime() + 0.3 δίνει καθυστέρηση 300 ms. autoreverses = true κάνει την κίνηση να πηγαίνει προς την αντίθετη κατεύθυνση μετά από κάθε κύκλο. repeatCount = .infinity δημιουργεί έναν ατελείωτο βρόχο του εφέ «αναπνοής». speed = 1.0 — κανονική ταχύτητα; επιταχύνετε την κίνηση 2 φορές με τιμή 2.0.
Το SwiftUI TimelineView (iOS 15+) παρέχει πρόσβαση στη χρονογραμμή συστήματος για προσαρμοσμένη κίνηση. Η προβολή ενημερώνεται σε κάθε tick του scheduler.
import SwiftUI
struct TimelineViewExample: View {
var body: some View {
TimelineView(.periodic(from: .now, by: 0.1)) { context in
let seconds = context.date.timeIntervalSinceReferenceDate
let phase = sin(seconds * 2 * .pi)
Circle()
.fill(Color.blue)
.frame(width: 60 + 30 * phase)
.animation(.linear(duration: 0), value: phase)
}
}
}
Το TimelineView με .periodic scheduler δημιουργεί tick κάθε 0.1 δευτ. Στο closure, το context.date — η τρέχουσα ώρα που μετατρέπεται σε φάση ημιτονοειδούς. Το μέγεθος του κύκλου παλμών με συχνότητα 1 Hz. Το TimelineView είναι ο μόνος τρόπος δημιουργίας κίνησης με δική της χρονογραμμή στο SwiftUI χωρίς Animation API.
ValueAnimator με πλήρη έλεγχο χρονογραμμής: καθυστέρηση, διάρκεια, επανάληψη. Η μέθοδος setCurrentPlayTime επιτρέπει άλματα κατά μήκος της χρονογραμμής.
import android.animation.ValueAnimator
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.View
fun View.timelineAnimation() {
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 400
startDelay = 200
repeatCount = 2
repeatMode = ValueAnimator.REVERSE
interpolator = AccelerateDecelerateInterpolator()
addUpdateListener { animator ->
val progress = animator.animatedFraction
alpha = progress
translationX = progress * 100f
}
start()
}
}
startDelay = 200 ms — καθυστέρηση πριν την έναρξη. repeatMode = REVERSE επιστρέφει την κίνηση στην αρχική τιμή. Το animatedFraction δίνει πρόοδο από 0 έως 1 λαμβάνοντας υπόψη το repeatMode. Το addUpdateListener ενεργοποιείται κάθε καρέ (συνήθως 16 ms). Για παύση, χρησιμοποιήστε pause() / resume().
Το AnimatorSet επιτρέπει τη δημιουργία σύνθετων χρονογραμμών από πολλαπλές κινήσεις. Στο παράδειγμα — σειριακή εκτέλεση με επικάλυψη (stagger).
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.view.View
fun View.staggerAnimation() {
val fadeIn = ObjectAnimator.ofFloat(this, "alpha", 0f, 1f).apply {
duration = 300
}
val slideUp = ObjectAnimator.ofFloat(this, "translationY", 100f, 0f).apply {
duration = 400
startDelay = 100
}
val scale = ObjectAnimator.ofFloat(this, "scaleX", 0.8f, 1f).apply {
duration = 250
startDelay = 200
}
AnimatorSet().apply {
playTogether(fadeIn, slideUp, scale)
start()
}
}
playTogether ξεκινά όλες τις κινήσεις παράλληλα, αλλά η καθεμία έχει τη δική της startDelay — αυτό δημιουργεί εφέ stagger: το fadeIn ξεκινά πρώτο (0 ms), το slideUp μετά από 100 ms, το scale μετά από 200 ms. Τελική χρονογραμμή: 0 — 100 — 200 — 600 ms. Για σειριακή εκτέλεση χρησιμοποιήστε playSequentially.
Συχνές ερωτήσεις
Το Animator — αφηρημένη βασική κλάση για όλες τις κινήσεις. Το ValueAnimator είναι η υποκλάση του που κινεί αριθμητικές τιμές χωρίς σύνδεση με αντικείμενο. Το ObjectAnimator (κληρονόμος του ValueAnimator) κινεί μια συγκεκριμένη ιδιότητα αντικειμένου (π.χ. alpha ή translationY). Για timeline, χρησιμοποιήστε το ValueAnimator ως καθολικό χρονοδιακόπτη με callback μέσω addUpdateListener.
Χρησιμοποιήστε ρολόγια συστήματος (CACurrentMediaTime στο iOS, System.nanoTime στο Android) για τον υπολογισμό προόδου, όχι τον αριθμό καρέ. Για συγχρονισμό δικτύου, εφαρμόστε χρονοδιακόπτη NTP-based με διόρθωση offset. Το iOS υποστηρίζει AVAudioSession για συγχρονισμό χρονογραμμής ήχου-βίντεο με ακρίβεια χιλιοστού του δευτερολέπτου. Αποφύγετε το συγχρονισμό βάσει καρέ — ο ρυθμός καρέ κυμαίνεται από 30 έως 120 fps.
startOffset — καθυστέρηση πριν την έναρξη της κίνησης σε χιλιοστά του δευτερολέπτου. Χρησιμοποιείται για εφέ καταρράκτη και stagger. repeatCount — αριθμός επαναλήψεων: 0 = χωρίς επανάληψη, ValueAnimator.INFINITE = ατελείωτος βρόχος. Το repeatMode καθορίζει την κατεύθυνση: RESTART (επαναφορά στην αρχή) ή REVERSE (αντίστροφη κίνηση). Στο iOS, το startOffset ονομάζεται beginTime (σε συνδυασμό με CACurrentMediaTime), repeatCount — η ομώνυμη ιδιότητα του CABasicAnimation.
Το Material Design συνιστά 200–350 ms για κινήσεις διεπαφής. Κινήσεις μικρότερες από 100 ms δεν γίνονται αντιληπτές ως κίνηση, μεγαλύτερες από 500 ms — ενοχλούν. Για είσοδο/έξοδο στοιχείων: 200–300 ms. Για μεταβάσεις μεταξύ οθονών: 300–400 ms. Χρησιμοποιήστε μικρότερες διάρκειες για μικρά στοιχεία (100–200 ms) και μεγαλύτερες για μεγάλα (400–500 ms).
Το τραύλισμα (jank) προκύπτει λόγω: μεγάλων λειτουργιών στο κύριο νήμα (μπλοκάρουν την απόδοση), σύνθετης ιεραρχίας View/Layer με overdraw ή λανθασμένης χρήσης timeline (η διάρκεια δεν είναι πολλαπλάσιο vsync — 16ms για 60fps, 8ms για 120fps). Χρησιμοποιήστε Xcode Instruments Animations ή Android Studio Profiler για ανίχνευση χαμένων καρέ. Στο Compose, εφαρμόστε Modifier.drawWithCache για προσωρινή αποθήκευση σύνθετων σχεδίων.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης