Easing: istota, funkcje wygładzania w animacji mobilnej

Autor: IT Sectr Opublikowano: 2026-03-02 Czas czytania: 8 min
Easing (funkcja wygładzania) — krzywa matematyczna, która określa prędkość zmiany parametru animacji w czasie. Zamiast liniowego ruchu jednostajnie przyspieszonego, easing imituje fizyczne zachowanie: obiekt może przyspieszać (ease-in), zwalniać (ease-out) lub robić jedno i drugie (ease-in-out). W iOS easing jest zaimplementowany przez klasę CAMediaTimingFunction (Core Animation) i modyfikatory SwiftUI (Animation.easeInOut). W Android — przez PathInterpolator i TimeInterpolator. Według dokumentacji Apple Developer, odpowiednio dobrana krzywa easingu skraca postrzegany czas animacji o 20–35% bez zmiany rzeczywistego czasu trwania. W IT Sectr ustandaryzowaliśmy krzywą ease-in-out (0.42, 0.0, 0.58, 1.0) jako standard korporacyjny dla wszystkich projektów mobilnych.

Najważniejsze

  • Easing — funkcja prędkości animacji, imitująca fizyczny ruch: przyspieszenie, zwalnianie lub kombinację.
  • CAMediaTimingFunction — klasa iOS Core Animation z 5 predefiniowanymi krzywymi i niestandardową sześcienną Béziera.
  • SwiftUI Animation — modyfikatory .easeIn, .easeOut, .easeInOut, .spring i .timingCurve dla deklaratywnego easingu.
  • PathInterpolator — odpowiednik easingu w Android z krzywą sześciennej Béziera przez dwa punkty kontrolne.
  • Sześcienna Béziera — uniwersalny format definiowania krzywej easingu, wspólny dla iOS, Android i web.

Czym jest Easing?

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).

Easing w iOS: CAMediaTimingFunction

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).

Easing w SwiftUI

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ą.

Easing w Android: PathInterpolator

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.

Przykłady kodu

iOS: CAMediaTimingFunction z CABasicAnimation

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.

swift
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.

SwiftUI: Easing z timingCurve

Deklaratywna animacja w SwiftUI z niestandardową krzywą i efektem sprężyny. Modyfikator animation jest powiązany ze zmianą isAnimated.

swift
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.

Android: PathInterpolator z niestandardowym easingiem

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.

kotlin
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.

Jetpack Compose: Easing z CubicBezierEasing

W Compose easing jest definiowany przez CubicBezierEasing z tymi samymi punktami kontrolnymi. Rezultat — płynne pojawianie się elementu z przezroczystością i przesunięciem.

kotlin
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

Czym różni się easing od interpolatora?

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.

Jakie funkcje easingu są dostępne w iOS?

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:).

Czy można używać tej samej funkcji easingu na iOS i Android?

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.

Jak wybrać odpowiednią krzywą easingu dla mojej aplikacji?

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.

Dlaczego moja animacja z easingiem wygląda szarpane?

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

  • Easing — funkcja prędkości animacji, czyniąca ruch naturalnym przez nieliniową zmianę parametrów w czasie.
  • iOS CAMediaTimingFunction i SwiftUI Animation.timingCurve implementują easing przez sześcienną krzywą Béziera z dwoma punktami kontrolnymi.
  • Android PathInterpolator i Compose CubicBezierEasing — pełne odpowiedniki iOS dla niestandardowych krzywych wygładzania.
  • Wieloplatformowe krzywe easingu są definiowane przez identyczne punkty kontrolne — podstawa unifikacji animacji.
  • Material Design 3 zaleca fast-out-slow-in dla pojawiania się, linear-out-slow-in dla znikania.
  • Animacja sprężynowa w SwiftUI (Animation.spring) i Compose (spring()) tworzy fizyczny efekt sprężyny z dampingFraction.
  • Linear easing nie jest zalecany dla animacji interfejsu — wygląda nienaturalnie i mechanicznie.

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.

Omów projekt

Przeczytaj również