Κύρια σημεία
Easing (συνάρτηση εξομάλυνσης) — μια μαθηματική συνάρτηση f(t) που αντιστοιχίζει τη γραμμική πορεία του χρόνου t ∈ [0,1] σε μια μη γραμμική τιμή κινούμενης εικόνας. Εάν μια κινούμενη εικόνα αλλάζει τη θέση ενός αντικειμένου από Α σε Β σε 500 ms, το easing καθορίζει πώς το αντικείμενο κινείται μεταξύ αυτών των σημείων σε κάθε χρονική στιγμή. Γραμμικό easing (f(t) = t) σημαίνει σταθερή ταχύτητα — το αντικείμενο κινείται μηχανικά. Μη γραμμικό easing — φυσικό: το αντικείμενο επιταχύνει (ease-in), επιβραδύνει (ease-out) ή έχει ομαλή αρχή και τέλος (ease-in-out).
Οι συναρτήσεις easing περιγράφονται από κυβικές καμπύλες Bézier (Cubic Bézier), όπου ο άξονας Χ είναι ο κανονικοποιημένος χρόνος και ο άξονας Υ είναι η κανονικοποιημένη πρόοδος. Δύο σημεία ελέγχου (P1, P2) καθορίζουν το σχήμα της καμπύλης. Οι τυπικές καμπύλες καθορίζονται από την προδιαγραφή CSS: ease (0.25, 0.1, 0.25, 1.0), ease-in (0.42, 0.0, 1.0, 1.0), ease-out (0.0, 0.0, 0.58, 1.0), ease-in-out (0.42, 0.0, 0.58, 1.0). Αυτές οι ίδιες τιμές χρησιμοποιούνται στο CAMediaTimingFunction στο iOS και στο PathInterpolator στο Android. Σύμφωνα με τις οδηγίες Material Design Motion, οι καμπύλες easing πρέπει να αντιστοιχούν στο πλαίσιο: για εμφάνιση — ease-out (γρήγορη έναρξη), για εξαφάνιση — ease-in (γρήγορο τέλος).
Στο iOS, η κύρια εργασία με το easing γίνεται μέσω της κλάσης CAMediaTimingFunction από το Core Animation. Δέχεται μια κυβική καμπύλη Bézier με τη μορφή δύο σημείων ελέγχου (c1x, c1y, c2x, c2y) και εφαρμόζεται στο CAAnimation (CABasicAnimation, CAKeyframeAnimation). Η CAMediaTimingFunction υποστηρίζει 5 προκαθορισμένες συναρτήσεις: linear, easeIn, easeOut, easeInEaseOut και default (ισοδύναμο του easeInEaseOut).
Η CAMediaTimingFunction λειτουργεί σε επίπεδο CALayer — η κινούμενη εικόνα εκτελείται σε μια ξεχωριστή διεργασία, τον render server, που εγγυάται 60fps ακόμη και σε σύνθετους μετασχηματισμούς. Για την κινούμενη εικόνα ιδιοτήτων UIView (frame, alpha, transform), το UIKit χρησιμοποιεί UIView.animate με την παράμετρο options: .curveEaseInOut, η οποία εσωτερικά δημιουργεί επίσης μια CAMediaTimingFunction με την αντίστοιχη καμπύλη. Στο iOS 17+, προστέθηκε το UIViewAnimationCurve.custom με BlockParameters για προσαρμοσμένη Bézier — αυτό εξαλείφει την ανάγκη γραφής δικής σας CAAnimation. Η CAMediaTimingFunction χρησιμοποιείται επίσης στο UIKit Dynamics και στο UIScrollView για επιβράδυνση (UIScrollViewDecelerationRate).
Στο SwiftUI, το easing ορίζεται μέσω της δομής Animation με τροποποιητές .easeIn, .easeOut, .easeInOut, .spring και .interpolatingSpring. Σε αντίθεση με το UIKit, όπου η κινούμενη εικόνα είναι διαδικαστική (beginAnimations/commitAnimations), το SwiftUI χρησιμοποιεί δηλωτική προσέγγιση: η κινούμενη εικόνα είναι μια ιδιότητα κατάστασης που αλλάζει και το πλαίσιο καθορίζει μόνο του πώς να κάνει κίνηση τη μετάβαση.
Ο τροποποιητής .timingCurve(c0x, c0y, c1x, c1y) δέχεται απευθείας τα σημεία ελέγχου της κυβικής Bézier — αυτό είναι το πλήρες ανάλογο της CAMediaTimingFunction. Το SwiftUI Animation υποστηρίζει προκαθορισμένες καμπύλες ως στατικές ιδιότητες: το Animation.easeInOut είναι ισοδύναμο του ease-in-out (0.42, 0.0, 0.58, 1.0). Για κινούμενη εικόνα ελατηρίου, χρησιμοποιείται το Animation.spring(response:dampingFraction:blendDuration), όπου dampingFraction < 1 δημιουργεί εφέ ελατηρίου με απόσβεση. Στο WWDC 2023, η Apple παρουσίασε το Animation.keyframes — κινούμενη εικόνα καρέ-καρέ με προσαρμοσμένο easing σε κάθε βασικό καρέ, που δίνει πλήρη έλεγχο της τροχιάς.
Στο Android, το easing υλοποιείται μέσω του PathInterpolator (API 21+) — μιας κλάσης που δέχεται ένα αντικείμενο Path με κυβική καμπύλη Bézier. Τα σημεία ελέγχου ορίζονται μέσω Path.cubicTo(x1, y1, x2, y2, x3, y3), όπου (x1,y1) είναι το πρώτο σημείο ελέγχου, (x2,y2) το δεύτερο, (x3,y3) το τελικό σημείο (πάντα 1,1). Το PathInterpolator είναι υποχρεωτικό εάν χρειάζεται προσαρμοσμένη καμπύλη· για τυπικά σενάρια, διατίθενται AccelerateDecelerateInterpolator (ease-in-out), AccelerateInterpolator (ease-in) και DecelerateInterpolator (ease-out).
Στο Jetpack Compose, το easing ορίζεται μέσω του αντικειμένου Easing από το πακέτο androidx.compose.animation. Διατίθενται FastOutSlowInEasing, LinearOutSlowInEasing, FastOutLinearInEasing, καθώς και προσαρμοσμένο Easing μέσω CubicBezierEasing. Το Compose Easing εφαρμόζεται σε animate*AsState, AnimatedVisibility και AnimatedContent. Η βασική διαφορά από το σύστημα View: το Compose παρεμβάλλει το easing στην πλευρά Kotlin (όχι στο RenderNode), που δίνει περισσότερη ευελιξία για προσαρμοσμένες κινούμενες εικόνες, αλλά απαιτεί βελτιστοποίηση σε οθόνες 120Hz.
Βασική κινούμενη εικόνα θέσης επιπέδου με προσαρμοσμένη καμπύλη ease-in-out. Η CAMediaTimingFunction δέχεται τα σημεία ελέγχου της κυβικής Bézier που ορίζουν ομαλή αρχή και τέλος κίνησης.
import UIKit
func animateLayerPosition() {
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = CGPoint(x: 0, y: 0)
animation.toValue = CGPoint(x: 300, y: 400)
animation.duration = 0.8
let easing = CAMediaTimingFunction(
controlPoints: 0.42, 0.0, 0.58, 1.0
)
animation.timingFunction = easing
animation.fillMode = .forwards
animation.isRemovedOnCompletion = false
layer.add(animation, forKey: "positionAnimation")
}
Τα σημεία ελέγχου (0.42, 0.0, 0.58, 1.0) ορίζουν την τυπική καμπύλη ease-in-out. Το SwiftUI χρησιμοποιεί τα ίδια σημεία για το Animation.easeInOut. Για ease-in, χρησιμοποιήστε (0.42, 0.0, 1.0, 1.0), για ease-out — (0.0, 0.0, 0.58, 1.0). Το Core Animation εκτελεί την κινούμενη εικόνα στον render server με συχνότητα 60/120 fps.
Δηλωτική κινούμενη εικόνα στο SwiftUI με προσαρμοσμένη καμπύλη και εφέ ελατηρίου. Ο τροποποιητής animation συνδέεται με την αλλαγή του isAnimated.
import SwiftUI
struct EasingExampleView: View {
@State private var isAnimated = false
var body: some View {
VStack {
Circle()
.fill(Color.blue)
.frame(width: 60, height: 60)
.offset(x: isAnimated ? 100 : 0, y: 0)
.animation(
.timingCurve(0.42, 0.0, 0.58, 1.0, duration: 0.6),
value: isAnimated
)
Button("Toggle") {
isAnimated.toggle()
}
}
}
}
Ο τροποποιητής .timingCurve(c0x, c0y, c1x, c1y, duration) δημιουργεί μια προσαρμοσμένη κινούμενη εικόνα easing στο SwiftUI. Η παράμετρος value συνδέει την κινούμενη εικόνα με μια συγκεκριμένη κατάσταση — όταν αλλάζει το isAnimated, το SwiftUI εφαρμόζει αυτόματα την καμπύλη. Για κινούμενη εικόνα ελατηρίου, χρησιμοποιήστε .spring(response: 0.4, dampingFraction: 0.6) αντί για timingCurve.
Ισοδύναμο του ease-in-out στο Android μέσω PathInterpolator. Τα σημεία ελέγχου (0.42, 0.0, 0.58, 1.0) ορίζουν την ίδια καμπύλη όπως στο iOS.
import android.animation.ObjectAnimator
import android.graphics.Path
import android.view.animation.PathInterpolator
import android.view.View
fun View.fadeInWithEase() {
val path = Path().apply {
cubicTo(0.42f, 0.0f, 0.58f, 1.0f, 1.0f, 1.0f)
}
val interpolator = PathInterpolator(path)
ObjectAnimator.ofFloat(this, "alpha", 0f, 1f).apply {
duration = 500
interpolator = interpolator
start()
}
}
Το PathInterpolator με σημεία ελέγχου (0.42, 0.0, 0.58, 1.0) είναι πανομοιότυπο με το CAMediaTimingFunction ease-in-out στο iOS. Για έργα μεταξύ πλατφορμών, χρησιμοποιήστε τα ίδια σημεία ελέγχου — αυτό εγγυάται οπτικά πανομοιότυπη συμπεριφορά κινούμενης εικόνας. Το Material Design συνιστά το FastOutSlowInInterpolator (0.4, 0.0, 0.2, 1.0) ως εναλλακτική για διεπαφές ειδικές για Android.
Στο Compose, το easing ορίζεται μέσω CubicBezierEasing με τα ίδια σημεία ελέγχου. Το αποτέλεσμα — ομαλή εμφάνιση ενός στοιχείου με διαφάνεια και μετατόπιση.
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.CubicBezierEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
@Composable
fun EasingComposable() {
var visible by remember { mutableStateOf(false) }
val easing = CubicBezierEasing(0.42f, 0.0f, 0.58f, 1.0f)
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = tween(durationMillis = 500, easing = easing)
)
Column {
Button(onClick = { visible = !visible }) {
Text("Toggle")
}
Box(
modifier = Modifier
.size(100.dp)
.graphicsLayer(alpha = alpha)
.background(MaterialTheme.colorScheme.primary)
)
}
}
Το CubicBezierEasing στο Compose δέχεται τις ίδιες 4 παραμέτρους σημείων ελέγχου. Το animateFloatAsState παρεμβάλλει αυτόματα την τιμή alpha με τη δεδομένη καμπύλη. Για σύγκριση: το FastOutSlowInEasing στο Compose είναι ισοδύναμο του (0.4, 0.0, 0.2, 1.0) — η τυπική καμπύλη Material Design 3. Οι προσαρμοσμένες καμπύλες μέσω CubicBezierEasing δίνουν πλήρη έλεγχο για κινούμενες εικόνες επωνυμίας.
Συχνές ερωτήσεις
Το Easing — η γενική έννοια της συνάρτησης εξομάλυνσης, που χρησιμοποιείται στο iOS (CAMediaTimingFunction), στο web (CSS ease-in-out) και στο σχεδιασμό. Το Interpolator — η υλοποίηση αυτής της έννοιας στο Android (TimeInterpolator). Στην ουσία είναι το ίδιο πράγμα — μια μαθηματική καμπύλη ταχύτητας κινούμενης εικόνας. Η διαφορά είναι μόνο στο όνομα και το API της πλατφόρμας. Σε έργα μεταξύ πλατφορμών, ο όρος easing χρησιμοποιείται ως καθολικός.
Το iOS παρέχει CAMediaTimingFunction με 5 προκαθορισμένες συναρτήσεις: linear, easeIn, easeOut, easeInEaseOut και custom (κυβική Bézier). Στο SwiftUI είναι διαθέσιμα Animation.easeIn, Animation.easeOut, Animation.easeInOut, Animation.spring και Animation.interpolatingSpring. Για προσαρμοσμένες καμπύλες, χρησιμοποιήστε Animation.timingCurve(c0x, c0y, c1x, c1y, duration:).
Ναι, εάν ορίσετε την ίδια κυβική καμπύλη Bézier. Η CAMediaTimingFunction στο iOS και το PathInterpolator στο Android δέχονται σημεία ελέγχου κυβικής Bézier (x1, y1, x2, y2). Για παράδειγμα, η καμπύλη ease-in-out (0.42, 0.0, 0.58, 1.0) θα δώσει πανομοιότυπη συμπεριφορά και στις δύο πλατφόρμες. Το Material Design χρησιμοποιεί αυτήν την προσέγγιση για κινούμενες εικόνες μεταξύ πλατφορμών.
Χρησιμοποιήστε τις οδηγίες Material Design Motion: για εμφάνιση στοιχείων — ease-out (γρήγορη έναρξη, ομαλό τέλος), για εξαφάνιση — ease-in (γρήγορο τέλος), για μετακίνηση μεταξύ οθονών — ease-in-out. Χρησιμοποιήστε κινούμενη εικόνα ελατηρίου για διαδραστικά στοιχεία (κουμπιά, κάρτες). Αποφύγετε το linear — φαίνεται αφύσικο.
Η τραχύτητα (jank) εμφανίζεται όταν η κινούμενη εικόνα δεν φτάνει τα 60fps. Ελέγξτε: χρησιμοποιείτε CAMediaTimingFunction (όχι χρονοδιακόπτη) στο iOS, PathInterpolator (όχι Handler.post) στο Android. Βαριοί υπολογισμοί στο κύριο νήμα, περίπλοκη ιεραρχία επιπέδων ή μεγάλο drawable μπορεί να προκαλέσουν απώλεια καρέ. Στο iOS, χρησιμοποιήστε Core Animation με shouldRasterize για περίπλοκα επίπεδα· στο Android — Hardware Acceleration και GPU rendering.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης