Najważniejsze
Easing (funkcja wygładzania) — funkcja matematyczna f(t), która odwzorowuje liniowy przebieg czasu t ∈ [0,1] na nieliniową wartość animacji. Jeśli animacja zmienia położenie obiektu z A do B w ciągu 500 ms, easing określa, jak obiekt porusza się między tymi punktami w każdym momencie czasu. Liniowe easing (f(t) = t) oznacza stałą prędkość — obiekt porusza się mechanicznie. Nieliniowe easing — naturalnie: obiekt przyspiesza (ease-in), zwalnia (ease-out) lub ma płynny początek i koniec (ease-in-out).
Funkcje easingu są opisywane sześciennymi krzywymi Béziera (Cubic Bézier), gdzie oś X to znormalizowany czas, oś Y to znormalizowany postęp. Dwa punkty kontrolne (P1, P2) określają kształt krzywej. Standardowe krzywe są ustalone przez specyfikację 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). Te same wartości są używane w CAMediaTimingFunction w iOS i PathInterpolator w Android. Według wytycznych Material Design Motion, krzywe easingu powinny odpowiadać kontekstowi: dla pojawiania się — ease-out (szybki start), dla znikania — ease-in (szybki finisz).
W iOS główna praca z easingiem odbywa się przez klasę CAMediaTimingFunction z Core Animation. Przyjmuje ona sześcienną krzywą Béziera w postaci dwóch punktów kontrolnych (c1x, c1y, c2x, c2y) i jest stosowana do CAAnimation (CABasicAnimation, CAKeyframeAnimation). CAMediaTimingFunction obsługuje 5 predefiniowanych funkcji: linear, easeIn, easeOut, easeInEaseOut i default (odpowiednik easeInEaseOut).
CAMediaTimingFunction działa na poziomie CALayer — animacja jest wykonywana w oddzielnym procesie render server, co gwarantuje 60fps nawet przy złożonych transformacjach. Do animacji właściwości UIView (frame, alpha, transform) UIKit używa UIView.animate z parametrem options: .curveEaseInOut, który wewnętrznie również tworzy CAMediaTimingFunction z odpowiednią krzywą. W iOS 17+ dodano UIViewAnimationCurve.custom z BlockParameters dla niestandardowej Béziera — eliminuje to konieczność pisania własnej CAAnimation. CAMediaTimingFunction jest również używana w UIKit Dynamics i UIScrollView do deceleracji (UIScrollViewDecelerationRate).
W SwiftUI easing jest definiowany przez strukturę Animation z modyfikatorami .easeIn, .easeOut, .easeInOut, .spring i .interpolatingSpring. W przeciwieństwie do UIKit, gdzie animacja jest proceduralna (beginAnimations/commitAnimations), SwiftUI stosuje podejście deklaratywne: animacja to właściwość stanu, która się zmienia, a framework sam określa, jak animować przejście.
Modyfikator .timingCurve(c0x, c0y, c1x, c1y) przyjmuje punkty kontrolne sześciennej Béziera bezpośrednio — jest to pełny odpowiednik CAMediaTimingFunction. SwiftUI Animation obsługuje predefiniowane krzywe jako właściwości statyczne: Animation.easeInOut jest równoważne ease-in-out (0.42, 0.0, 0.58, 1.0). Do animacji sprężynowej używany jest Animation.spring(response:dampingFraction:blendDuration), gdzie dampingFraction < 1 tworzy efekt sprężyny z tłumieniem. Na WWDC 2023 Apple zaprezentowało Animation.keyframes — animację klatkową z niestandardowym easingiem na każdej klatce kluczowej, co daje pełną kontrolę nad trajektorią.
W Android easing jest zaimplementowany przez PathInterpolator (API 21+) — klasę przyjmującą obiekt Path z sześcienną krzywą Béziera. Punkty kontrolne są definiowane przez Path.cubicTo(x1, y1, x2, y2, x3, y3), gdzie (x1,y1) to pierwszy punkt kontrolny, (x2,y2) to drugi, (x3,y3) to punkt końcowy (zawsze 1,1). PathInterpolator jest wymagany, jeśli potrzebna jest niestandardowa krzywa; dla standardowych scenariuszy dostępne są AccelerateDecelerateInterpolator (ease-in-out), AccelerateInterpolator (ease-in) i DecelerateInterpolator (ease-out).
W Jetpack Compose easing jest definiowany przez obiekt Easing z pakietu androidx.compose.animation. Dostępne są FastOutSlowInEasing, LinearOutSlowInEasing, FastOutLinearInEasing, a także niestandardowy Easing przez CubicBezierEasing. Compose Easing jest stosowany w animate*AsState, AnimatedVisibility i AnimatedContent. Kluczowa różnica w stosunku do systemu View: Compose interpoluje easing po stronie Kotlin (nie w RenderNode), co daje większą elastyczność dla niestandardowych animacji, ale wymaga optymalizacji przy wyświetlaczach 120Hz.
Podstawowa animacja pozycji warstwy z niestandardową krzywą ease-in-out. CAMediaTimingFunction przyjmuje punkty kontrolne sześciennej Béziera, definiujące płynny początek i koniec ruchu.
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")
}
Punkty kontrolne (0.42, 0.0, 0.58, 1.0) definiują standardową krzywą ease-in-out. SwiftUI używa tych samych punktów dla Animation.easeInOut. Dla ease-in stosuj (0.42, 0.0, 1.0, 1.0), dla ease-out — (0.0, 0.0, 0.58, 1.0). Core Animation wykonuje animację w render server z częstotliwością 60/120 fps.
Deklaratywna animacja w SwiftUI z niestandardową krzywą i efektem sprężyny. Modyfikator animation jest powiązany ze zmianą 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()
}
}
}
}
Modyfikator .timingCurve(c0x, c0y, c1x, c1y, duration) tworzy niestandardową animację easingu w SwiftUI. Parametr value łączy animację z konkretnym stanem — przy zmianie isAnimated SwiftUI automatycznie stosuje krzywą. Dla animacji sprężynowej używaj .spring(response: 0.4, dampingFraction: 0.6) zamiast timingCurve.
Odpowiednik ease-in-out w Android przez PathInterpolator. Punkty kontrolne (0.42, 0.0, 0.58, 1.0) definiują tę samą krzywą co w 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 z punktami kontrolnymi (0.42, 0.0, 0.58, 1.0) jest identyczny z CAMediaTimingFunction ease-in-out na iOS. Dla projektów wieloplatformowych używaj identycznych punktów kontrolnych — gwarantuje to wizualnie identyczne zachowanie animacji. Material Design zaleca FastOutSlowInInterpolator (0.4, 0.0, 0.2, 1.0) jako alternatywę dla interfejsów specyficznych dla Androida.
W Compose easing jest definiowany przez CubicBezierEasing z tymi samymi punktami kontrolnymi. Rezultat — płynne pojawianie się elementu z przezroczystością i przesunięciem.
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 w Compose przyjmuje te same 4 parametry punktów kontrolnych. animateFloatAsState automatycznie interpoluje wartość alpha z zadaną krzywą. Dla porównania: FastOutSlowInEasing w Compose jest równoważne (0.4, 0.0, 0.2, 1.0) — standardowa krzywa Material Design 3. Niestandardowe krzywe przez CubicBezierEasing dają pełną kontrolę dla animacji brandingowych.
Często zadawane pytania
Easing — ogólne pojęcie funkcji wygładzania, używane w iOS (CAMediaTimingFunction), web (CSS ease-in-out) i projektowaniu. Interpolator — implementacja tej koncepcji w Android (TimeInterpolator). W istocie to to samo — matematyczna krzywa prędkości animacji. Różnica polega tylko na nazwie i API platformy. W projektach wieloplatformowych termin easing jest używany jako uniwersalny.
iOS udostępnia CAMediaTimingFunction z 5 predefiniowanymi funkcjami: linear, easeIn, easeOut, easeInEaseOut i custom (sześcienna Béziera). W SwiftUI dostępne są Animation.easeIn, Animation.easeOut, Animation.easeInOut, Animation.spring i Animation.interpolatingSpring. Dla niestandardowych krzywych używaj Animation.timingCurve(c0x, c0y, c1x, c1y, duration:).
Tak, jeśli ustawisz tę samą sześcienną krzywą Béziera. CAMediaTimingFunction w iOS i PathInterpolator w Android przyjmują punkty kontrolne sześciennej Béziera (x1, y1, x2, y2). Na przykład krzywa ease-in-out (0.42, 0.0, 0.58, 1.0) da identyczne zachowanie na obu platformach. Material Design stosuje to podejście dla animacji wieloplatformowych.
Korzystaj z Material Design Motion Guidelines: dla pojawiania się elementów — ease-out (szybki start, płynny finisz), dla znikania — ease-in (szybki finisz), dla przemieszczania między ekranami — ease-in-out. Animacji sprężynowej używaj dla elementów interaktywnych (przyciski, karty). Unikaj linear — wygląda nienaturalnie.
Szarpnięcia (jank) występują, gdy animacja nie osiąga 60fps. Sprawdź: czy używasz CAMediaTimingFunction (nie timera) w iOS, PathInterpolator (nie Handler.post) w Android. Ciężkie obliczenia w głównym wątku, złożona hierarchia warstw lub duży drawable mogą powodować opuszczanie klatek. W iOS używaj Core Animation z shouldRasterize dla złożonych warstw; w Android — Hardware Acceleration i GPU rendering.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również