Punti chiave
Easing (funzione di smoothing) è una funzione matematica f(t) che mappa il tempo lineare t ∈ [0,1] in un valore di animazione non lineare. Se un'animazione cambia la posizione di un oggetto da A a B in 500 ms, l'easing determina come l'oggetto si muove tra questi punti in ogni istante. L'easing lineare (f(t) = t) significa velocità costante — l'oggetto si muove meccanicamente. L'easing non lineare sembra naturale: l'oggetto accelera (ease-in), decelera (ease-out) o ha un inizio e una fine fluidi (ease-in-out).
Le funzioni di easing sono descritte da curve Bézier cubiche, dove l'asse X è il tempo normalizzato e l'asse Y è il progresso normalizzato. Due punti di controllo (P1, P2) definiscono la forma della curva. Le curve standard sono fissate dalla specifica 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). Questi stessi valori vengono utilizzati in CAMediaTimingFunction su iOS e PathInterpolator su Android. Secondo le Linee guida sul movimento di Material Design, le curve di easing devono corrispondere al contesto: per l'apparizione — ease-out (partenza rapida), per la scomparsa — ease-in (fine rapida).
Su iOS, l'easing funziona principalmente attraverso la classe CAMediaTimingFunction di Core Animation. Accetta una curva Bézier cubica come due punti di controllo (c1x, c1y, c2x, c2y) e si applica a CAAnimation (CABasicAnimation, CAKeyframeAnimation). CAMediaTimingFunction supporta 5 funzioni predefinite: linear, easeIn, easeOut, easeInEaseOut e default (equivalente a easeInEaseOut).
CAMediaTimingFunction funziona a livello CALayer — l'animazione viene eseguita in un processo separato del server di rendering, garantendo 60fps anche con trasformazioni complesse. Per l'animazione delle proprietà UIView (frame, alpha, transform), UIKit utilizza UIView.animate con il parametro options: .curveEaseInOut, che internamente crea anche una CAMediaTimingFunction con la curva corrispondente. Su iOS 17+, è stato aggiunto UIViewAnimationCurve.custom con BlockParameters per Bézier personalizzata — ciò elimina la necessità di scrivere CAAnimation personalizzata. CAMediaTimingFunction viene utilizzata anche in UIKit Dynamics e UIScrollView per la decelerazione (UIScrollViewDecelerationRate).
In SwiftUI, l'easing viene impostato attraverso la struttura Animation con i modificatori .easeIn, .easeOut, .easeInOut, .spring e .interpolatingSpring. A differenza di UIKit, dove l'animazione è procedurale (beginAnimations/commitAnimations), SwiftUI utilizza un approccio dichiarativo: l'animazione è una proprietà di stato che cambia e il framework determina come animare la transizione.
Il modificatore .timingCurve(c0x, c0y, c1x, c1y) accetta punti di controllo Bézier cubica direttamente — è un analogo completo di CAMediaTimingFunction. SwiftUI Animation supporta le curve predefinite come proprietà statiche: Animation.easeInOut è equivalente a ease-in-out (0.42, 0.0, 0.58, 1.0). Per l'animazione spring, viene utilizzato Animation.spring(response:dampingFraction:blendDuration), dove dampingFraction < 1 crea un effetto molla con smorzamento. Al WWDC 2023, Apple ha presentato Animation.keyframes — animazione fotogramma per fotogramma con easing personalizzato su ogni fotogramma chiave, offrendo il controllo completo sulla traiettoria.
Su Android, l'easing è implementato attraverso PathInterpolator (API 21+) — una classe che accetta un oggetto Path con una curva Bézier cubica. I punti di controllo vengono specificati tramite Path.cubicTo(x1, y1, x2, y2, x3, y3), dove (x1,y1) è il primo punto di controllo, (x2,y2) il secondo e (x3,y3) il punto finale (sempre 1,1). PathInterpolator è obbligatorio se è necessaria una curva personalizzata; per scenari standard, sono disponibili AccelerateDecelerateInterpolator (ease-in-out), AccelerateInterpolator (ease-in) e DecelerateInterpolator (ease-out).
In Jetpack Compose, l'easing viene impostato attraverso l'oggetto Easing del pacchetto androidx.compose.animation. FastOutSlowInEasing, LinearOutSlowInEasing, FastOutLinearInEasing sono disponibili, così come Easing personalizzato attraverso CubicBezierEasing. L'Easing di Compose viene applicato in animate*AsState, AnimatedVisibility e AnimatedContent. La differenza chiave rispetto al sistema View: Compose interpola l'easing sul lato Kotlin (non in RenderNode), offrendo maggiore flessibilità per animazioni personalizzate ma richiedendo ottimizzazione sui display a 120Hz.
Animazione base della posizione del layer con una curva ease-in-out personalizzata. CAMediaTimingFunction accetta punti di controllo Bézier cubica che definiscono un inizio e una fine fluidi del movimento.
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")
}
I punti di controllo (0.42, 0.0, 0.58, 1.0) definiscono la curva standard ease-in-out. SwiftUI utilizza gli stessi punti per Animation.easeInOut. Per ease-in, utilizzare (0.42, 0.0, 1.0, 1.0); per ease-out — (0.0, 0.0, 0.58, 1.0). Core Animation esegue l'animazione nel server di rendering a 60/120 fps.
Animazione dichiarativa in SwiftUI con una curva personalizzata ed effetto spring. Il modificatore di animazione si lega al cambiamento di 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()
}
}
}
}
Il modificatore .timingCurve(c0x, c0y, c1x, c1y, duration) crea un'animazione di easing personalizzata in SwiftUI. Il parametro value lega l'animazione a uno stato specifico — quando isAnimated cambia, SwiftUI applica automaticamente la curva. Per l'animazione spring, utilizzare .spring(response: 0.4, dampingFraction: 0.6) invece di timingCurve.
L'equivalente ease-in-out su Android tramite PathInterpolator. I punti di controllo (0.42, 0.0, 0.58, 1.0) definiscono la stessa curva che su 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 con punti di controllo (0.42, 0.0, 0.58, 1.0) è identico a CAMediaTimingFunction ease-in-out su iOS. Per progetti multipiattaforma, utilizzare gli stessi punti di controllo — ciò garantisce un comportamento di animazione visivamente identico. Material Design raccomanda FastOutSlowInInterpolator (0.4, 0.0, 0.2, 1.0) come alternativa per interfacce specifiche di Android.
In Compose, l'easing viene impostato tramite CubicBezierEasing con gli stessi punti di controllo. Il risultato è un'apparizione fluida dell'elemento con trasparenza e offset.
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 in Compose accetta gli stessi 4 parametri dei punti di controllo. animateFloatAsState interpola automaticamente il valore alpha con la curva specificata. Per confronto: FastOutSlowInEasing in Compose è equivalente a (0.4, 0.0, 0.2, 1.0) — la curva standard di Material Design 3. Le curve personalizzate tramite CubicBezierEasing offrono il controllo completo per animazioni di marca.
Domande frequenti
L'easing è un concetto generale di funzione di smoothing utilizzato in iOS (CAMediaTimingFunction), nel web (CSS ease-in-out) e nel design. L'interpolator è l'implementazione di questo concetto in Android (TimeInterpolator). In sostanza sono la stessa cosa — una curva matematica della velocità di animazione. La differenza è solo nel nome e nell'API della piattaforma. Nei progetti multipiattaforma, il termine easing viene utilizzato come universale.
iOS fornisce CAMediaTimingFunction con 5 funzioni predefinite: linear, easeIn, easeOut, easeInEaseOut e custom (Bézier cubica). In SwiftUI, sono disponibili Animation.easeIn, Animation.easeOut, Animation.easeInOut, Animation.spring e Animation.interpolatingSpring. Per curve personalizzate, utilizzare Animation.timingCurve(c0x, c0y, c1x, c1y, duration:).
Sì, se si imposta la stessa curva Bézier cubica. CAMediaTimingFunction su iOS e PathInterpolator su Android accettano punti di controllo Bézier cubica (x1, y1, x2, y2). Ad esempio, la curva ease-in-out (0.42, 0.0, 0.58, 1.0) darà un comportamento identico su entrambe le piattaforme. Material Design utilizza questo approccio per le animazioni multipiattaforma.
Utilizzare le Linee guida sul movimento di Material Design: per l'apparizione di elementi — ease-out (partenza rapida, fine fluida), per la scomparsa — ease-in (fine rapida), per il movimento tra schermate — ease-in-out. Utilizzare l'animazione spring per elementi interattivi (pulsanti, carte). Evitare linear — sembra innaturale.
Gli scatti (jank) si verificano quando l'animazione non raggiunge 60fps. Verificare: sta utilizzando CAMediaTimingFunction (non un timer) su iOS, PathInterpolator (non Handler.post) su Android. Calcoli pesanti sul thread principale, una gerarchia di layer complessa o un drawable grande possono causare cadute di fotogrammi. Su iOS, utilizzare Core Animation con shouldRasterize per layer complessi; su Android — accelerazione hardware e rendering GPU.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche