Easing: същност, функции за изглаждане в мобилната анимация

Автор: IT Sectr Публикувано: 2026-03-02 Време за четене: 8 мин
Easing (функция за изглаждане) — математическа крива, която определя скоростта на промяна на анимационния параметър във времето. Вместо линейно равномерно ускорено движение, easing имитира физическо поведение: обектът може да ускорява (ease-in), да забавя (ease-out) или да прави и двете (ease-in-out). В iOS easing се реализира чрез класа CAMediaTimingFunction (Core Animation) и модификаторите на SwiftUI (Animation.easeInOut). В Android — чрез PathInterpolator и TimeInterpolator. Според документацията на Apple Developer, правилно подбраната easing крива намалява възприеманото време на анимация с 20–35% без промяна на действителната продължителност. В IT Sectr стандартизирахме кривата ease-in-out (0.42, 0.0, 0.58, 1.0) като корпоративен стандарт за всички мобилни проекти.

Основни изводи

  • Easing — функция на скоростта на анимация, имитираща физическо движение: ускорение, забавяне или комбинация.
  • CAMediaTimingFunction — клас на iOS Core Animation с 5 предопределени криви и персонализирана кубична крива на Безие.
  • SwiftUI Animation — модификатори .easeIn, .easeOut, .easeInOut, .spring и .timingCurve за декларативен easing.
  • PathInterpolator — аналог на easing в Android с кубична крива на Безие чрез две контролни точки.
  • Кубична крива на Безие — универсален формат за задаване на easing крива, общ за iOS, Android и уеб.

Какво е Easing?

Easing (функция за изглаждане) — математическа функция f(t), която преобразува линейния ход на времето t ∈ [0,1] в нелинейна стойност на анимацията. Ако анимацията променя позицията на обект от A до B за 500 ms, easing определя как обектът се движи между тези точки във всеки момент от времето. Линейният easing (f(t) = t) означава постоянна скорост — обектът се движи механично. Нелинейният easing — естествено: обектът ускорява (ease-in), забавя (ease-out) или има гладко начало и край (ease-in-out).

Функциите за easing се описват чрез кубични криви на Безие (Cubic Bézier), където оста X е нормализираното време, а оста Y е нормализираният напредък. Две контролни точки (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 (бърз финал).

Easing в iOS: CAMediaTimingFunction

В iOS основната работа с easing се извършва чрез класа CAMediaTimingFunction от Core Animation. Той приема кубична крива на Безие под формата на две контролни точки (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 за персонализирана крива на Безие — това елиминира необходимостта от писане на собствена CAAnimation. CAMediaTimingFunction се използва също в UIKit Dynamics и UIScrollView за забавяне (UIScrollViewDecelerationRate).

Easing в SwiftUI

В SwiftUI easing се задава чрез структурата Animation с модификатори .easeIn, .easeOut, .easeInOut, .spring и .interpolatingSpring. За разлика от UIKit, където анимацията е процедурна (beginAnimations/commitAnimations), SwiftUI използва декларативен подход: анимацията е свойство на състоянието, което се променя, и рамката сама определя как да анимира прехода.

Модификаторът .timingCurve(c0x, c0y, c1x, c1y) приема директно контролните точки на кубичната крива на Безие — това е пълният аналог на 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 на всеки ключов кадър, което дава пълен контрол върху траекторията.

Easing в Android: PathInterpolator

В Android easing е реализиран чрез PathInterpolator (API 21+) — клас, който приема обект Path с кубична крива на Безие. Контролните точки се задават чрез 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 дисплеи.

Примери за код

iOS: CAMediaTimingFunction с CABasicAnimation

Основна анимация на позицията на слой с персонализирана крива ease-in-out. CAMediaTimingFunction приема контролните точки на кубичната крива на Безие, които определят гладко начало и край на движението.

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")
}

Контролните точки (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: Easing с timingCurve

Декларативна анимация в SwiftUI с персонализирана крива и пружинен ефект. Модификаторът animation е свързан с промяната на 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()
            }
        }
    }
}

Модификаторът .timingCurve(c0x, c0y, c1x, c1y, duration) създава персонализирана easing анимация в SwiftUI. Параметърът value свързва анимацията с конкретно състояние — при промяна на isAnimated SwiftUI автоматично прилага кривата. За пружинна анимация използвайте .spring(response: 0.4, dampingFraction: 0.6) вместо timingCurve.

Android: PathInterpolator с персонализиран easing

Еквивалент на ease-in-out в Android чрез PathInterpolator. Контролните точки (0.42, 0.0, 0.58, 1.0) определят същата крива като в 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 с контролни точки (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-специфични интерфейси.

Jetpack Compose: Easing с CubicBezierEasing

В Compose easing се задава чрез CubicBezierEasing със същите контролни точки. Резултатът — гладко появяване на елемент с прозрачност и преместване.

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 в Compose приема същите 4 параметъра на контролните точки. animateFloatAsState автоматично интерполира стойността alpha със зададената крива. За сравнение: FastOutSlowInEasing в Compose е еквивалентен на (0.4, 0.0, 0.2, 1.0) — стандартната крива на Material Design 3. Персонализираните криви чрез CubicBezierEasing дават пълен контрол за бранд анимации.

Често задавани въпроси

С какво се различава easing от interpolator?

Easing — общото понятие за функция за изглаждане, използвано в iOS (CAMediaTimingFunction), уеб (CSS ease-in-out) и дизайн. Interpolator — реализацията на тази концепция в Android (TimeInterpolator). По същество това е едно и също нещо — математическа крива на скоростта на анимация. Разликата е само в името и API на платформата. В междуплатформени проекти терминът easing се използва като универсален.

Какви easing функции са налични в iOS?

iOS предоставя CAMediaTimingFunction с 5 предопределени функции: linear, easeIn, easeOut, easeInEaseOut и custom (кубична крива на Безие). В SwiftUI са налични Animation.easeIn, Animation.easeOut, Animation.easeInOut, Animation.spring и Animation.interpolatingSpring. За персонализирани криви използвайте Animation.timingCurve(c0x, c0y, c1x, c1y, duration:).

Мога ли да използвам една и съща easing функция на iOS и Android?

Да, ако зададете една и съща кубична крива на Безие. CAMediaTimingFunction в iOS и PathInterpolator в Android приемат контролните точки на кубичната крива на Безие (x1, y1, x2, y2). Например кривата ease-in-out (0.42, 0.0, 0.58, 1.0) ще даде идентично поведение и на двете платформи. Material Design използва този подход за междуплатформени анимации.

Как да избера правилната easing крива за моето приложение?

Използвайте насоките на Material Design Motion: за появяване на елементи — ease-out (бърз старт, гладък финал), за изчезване — ease-in (бърз финал), за преместване между екрани — ease-in-out. Използвайте пружинна анимация за интерактивни елементи (бутони, карти). Избягвайте linear — изглежда неестествено.

Защо анимацията ми с easing изглежда неравномерна?

Неравномерността (jank) възниква, когато анимацията не достига 60fps. Проверете: използвате ли CAMediaTimingFunction (не таймер) в iOS, PathInterpolator (не Handler.post) в Android. Тежки изчисления в основната нишка, сложна йерархия на слоеве или голям drawable могат да причинят пропускане на кадри. В iOS използвайте Core Animation с shouldRasterize за сложни слоеве; в Android — Hardware Acceleration и GPU rendering.

Резюме

  • Easing е функция на скоростта на анимация, която прави движението естествено чрез нелинейна промяна на параметрите във времето.
  • iOS CAMediaTimingFunction и SwiftUI Animation.timingCurve реализират easing чрез кубична крива на Безие с две контролни точки.
  • Android PathInterpolator и Compose CubicBezierEasing — пълни аналози на iOS за персонализирани криви на изглаждане.
  • Междуплатформените easing криви се задават с идентични контролни точки — основа за унифициране на анимациите.
  • Material Design 3 препоръчва fast-out-slow-in за появяване, linear-out-slow-in за изчезване.
  • Пружинната анимация в SwiftUI (Animation.spring) и Compose (spring()) създава физически пружинен ефект с dampingFraction.
  • Линейният easing не се препоръчва за интерфейсни анимации — изглежда неестествено и механично.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също