Easing: funciones de suavizado en la animación móvil

Autor: IT Sectr Publicado: 2026-03-02 Tiempo de lectura: 8 min
Easing (función de suavizado) es una curva matemática que determina la velocidad de cambio de un parámetro de animación en el tiempo. En lugar de un movimiento lineal uniformemente acelerado, el easing simula un comportamiento físico: un objeto puede acelerar (ease-in), desacelerar (ease-out) o hacer ambas cosas (ease-in-out). En iOS, el easing se implementa a través de la clase CAMediaTimingFunction (Core Animation) y los modificadores de SwiftUI (Animation.easeInOut). En Android — a través de PathInterpolator y TimeInterpolator. Según la Documentación para Desarrolladores de Apple, una curva de easing correctamente seleccionada reduce el tiempo de animación percibido en un 20–35% sin cambiar la duración real. En IT Sectr, estandarizamos la curva ease-in-out (0.42, 0.0, 0.58, 1.0) como estándar corporativo para todos los proyectos móviles.

Puntos clave

  • Easing — una función de velocidad de animación que imita el movimiento físico: aceleración, desaceleración o una combinación.
  • CAMediaTimingFunction — la clase de Core Animation de iOS con 5 curvas predefinidas y Bézier cúbica personalizada.
  • SwiftUI Animation — modificadores .easeIn, .easeOut, .easeInOut, .spring y .timingCurve para easing declarativo.
  • PathInterpolator — el equivalente de easing en Android con una curva Bézier cúbica mediante dos puntos de control.
  • Bézier cúbica — un formato universal para especificar curvas de easing, común en iOS, Android y la web.

¿Qué es el Easing?

Easing (función de suavizado) es una función matemática f(t) que asigna el tiempo lineal t ∈ [0,1] a un valor de animación no lineal. Si una animación cambia la posición de un objeto de A a B en 500 ms, el easing determina cómo se mueve el objeto entre estos puntos en cada momento del tiempo. El easing lineal (f(t) = t) significa velocidad constante — el objeto se mueve mecánicamente. El easing no lineal se siente natural: el objeto acelera (ease-in), desacelera (ease-out) o tiene un inicio y final suaves (ease-in-out).

Las funciones de easing se describen mediante curvas Bézier cúbicas, donde el eje X es el tiempo normalizado y el eje Y es el progreso normalizado. Dos puntos de control (P1, P2) definen la forma de la curva. Las curvas estándar están fijadas por la especificación 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). Estos mismos valores se usan en CAMediaTimingFunction en iOS y PathInterpolator en Android. Según las Guías de Diseño de Movimiento de Material Design, las curvas de easing deben coincidir con el contexto: para aparición — ease-out (inicio rápido), para desaparición — ease-in (final rápido).

Easing en iOS: CAMediaTimingFunction

En iOS, el easing funciona principalmente a través de la clase CAMediaTimingFunction de Core Animation. Acepta una curva Bézier cúbica como dos puntos de control (c1x, c1y, c2x, c2y) y se aplica a CAAnimation (CABasicAnimation, CAKeyframeAnimation). CAMediaTimingFunction soporta 5 funciones predefinidas: linear, easeIn, easeOut, easeInEaseOut y default (equivalente a easeInEaseOut).

CAMediaTimingFunction funciona a nivel de CALayer — la animación se ejecuta en un proceso separado del servidor de renderizado, garantizando 60fps incluso con transformaciones complejas. Para la animación de propiedades de UIView (frame, alpha, transform), UIKit usa UIView.animate con el parámetro options: .curveEaseInOut, que internamente también crea una CAMediaTimingFunction con la curva correspondiente. En iOS 17+, se agregó UIViewAnimationCurve.custom con BlockParameters para Bézier personalizada — esto elimina la necesidad de escribir CAAnimation personalizada. CAMediaTimingFunction también se usa en UIKit Dynamics y UIScrollView para la deceleración (UIScrollViewDecelerationRate).

Easing en SwiftUI

En SwiftUI, el easing se configura a través de la estructura Animation con los modificadores .easeIn, .easeOut, .easeInOut, .spring y .interpolatingSpring. A diferencia de UIKit, donde la animación es procedural (beginAnimations/commitAnimations), SwiftUI utiliza un enfoque declarativo: la animación es una propiedad de estado que cambia, y el framework determina cómo animar la transición.

El modificador .timingCurve(c0x, c0y, c1x, c1y) acepta puntos de control Bézier cúbica directamente — es un análogo completo de CAMediaTimingFunction. SwiftUI Animation soporta curvas predefinidas como propiedades estáticas: Animation.easeInOut es equivalente a ease-in-out (0.42, 0.0, 0.58, 1.0). Para la animación spring, se usa Animation.spring(response:dampingFraction:blendDuration), donde dampingFraction < 1 crea un efecto de resorte con amortiguación. En WWDC 2023, Apple presentó Animation.keyframes — animación fotograma a fotograma con easing personalizado en cada fotograma clave, dando control total sobre la trayectoria.

Easing en Android: PathInterpolator

En Android, el easing se implementa a través de PathInterpolator (API 21+) — una clase que acepta un objeto Path con una curva Bézier cúbica. Los puntos de control se especifican mediante Path.cubicTo(x1, y1, x2, y2, x3, y3), donde (x1,y1) es el primer punto de control, (x2,y2) es el segundo y (x3,y3) es el punto final (siempre 1,1). PathInterpolator es obligatorio si se necesita una curva personalizada; para escenarios estándar, están disponibles AccelerateDecelerateInterpolator (ease-in-out), AccelerateInterpolator (ease-in) y DecelerateInterpolator (ease-out).

En Jetpack Compose, el easing se configura mediante el objeto Easing del paquete androidx.compose.animation. Están disponibles FastOutSlowInEasing, LinearOutSlowInEasing, FastOutLinearInEasing, así como Easing personalizado a través de CubicBezierEasing. El easing de Compose se aplica en animate*AsState, AnimatedVisibility y AnimatedContent. La diferencia clave con el sistema View: Compose interpola el easing en el lado de Kotlin (no en RenderNode), lo que brinda más flexibilidad para animaciones personalizadas pero requiere optimización en pantallas de 120Hz.

Ejemplos de código

iOS: CAMediaTimingFunction con CABasicAnimation

Animación básica de posición de capa con una curva ease-in-out personalizada. CAMediaTimingFunction acepta puntos de control Bézier cúbica que definen un inicio y final suaves del movimiento.

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

Los puntos de control (0.42, 0.0, 0.58, 1.0) definen la curva estándar ease-in-out. SwiftUI usa los mismos puntos para Animation.easeInOut. Para ease-in, use (0.42, 0.0, 1.0, 1.0); para ease-out — (0.0, 0.0, 0.58, 1.0). Core Animation ejecuta la animación en el servidor de renderizado a 60/120 fps.

SwiftUI: Easing con timingCurve

Animación declarativa en SwiftUI con una curva personalizada y efecto spring. El modificador animation se vincula al cambio de 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()
            }
        }
    }
}

El modificador .timingCurve(c0x, c0y, c1x, c1y, duration) crea una animación de easing personalizada en SwiftUI. El parámetro value vincula la animación a un estado específico — cuando isAnimated cambia, SwiftUI aplica automáticamente la curva. Para animación spring, use .spring(response: 0.4, dampingFraction: 0.6) en lugar de timingCurve.

Android: PathInterpolator con Easing personalizado

El equivalente de ease-in-out en Android mediante PathInterpolator. Los puntos de control (0.42, 0.0, 0.58, 1.0) definen la misma curva que en 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 con puntos de control (0.42, 0.0, 0.58, 1.0) es idéntico a CAMediaTimingFunction ease-in-out en iOS. Para proyectos multiplataforma, use los mismos puntos de control — esto garantiza un comportamiento de animación visualmente idéntico. Material Design recomienda FastOutSlowInInterpolator (0.4, 0.0, 0.2, 1.0) como alternativa para interfaces específicas de Android.

Jetpack Compose: Easing con CubicBezierEasing

En Compose, el easing se configura mediante CubicBezierEasing con los mismos puntos de control. El resultado es una aparición suave del elemento con transparencia y desplazamiento.

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 en Compose acepta los mismos 4 parámetros de puntos de control. animateFloatAsState interpola automáticamente el valor alpha con la curva especificada. En comparación: FastOutSlowInEasing en Compose es equivalente a (0.4, 0.0, 0.2, 1.0) — la curva estándar de Material Design 3. Las curvas personalizadas mediante CubicBezierEasing brindan control total para animaciones de marca.

Preguntas frecuentes

¿En qué se diferencia el easing del interpolator?

Easing es un concepto general de función de suavizado utilizado en iOS (CAMediaTimingFunction), la web (CSS ease-in-out) y el diseño. Interpolator es la implementación de este concepto en Android (TimeInterpolator). En esencia son lo mismo — una curva matemática de velocidad de animación. La diferencia está solo en el nombre y la API de la plataforma. En proyectos multiplataforma, el término easing se usa como universal.

¿Qué funciones de easing están disponibles en iOS?

iOS proporciona CAMediaTimingFunction con 5 funciones predefinidas: linear, easeIn, easeOut, easeInEaseOut y custom (Bézier cúbica). En SwiftUI están disponibles Animation.easeIn, Animation.easeOut, Animation.easeInOut, Animation.spring y Animation.interpolatingSpring. Para curvas personalizadas, use Animation.timingCurve(c0x, c0y, c1x, c1y, duration:).

¿Se puede usar la misma función de easing en iOS y Android?

Sí, si se configura la misma curva Bézier cúbica. CAMediaTimingFunction en iOS y PathInterpolator en Android aceptan puntos de control Bézier cúbica (x1, y1, x2, y2). Por ejemplo, la curva ease-in-out (0.42, 0.0, 0.58, 1.0) dará un comportamiento idéntico en ambas plataformas. Material Design utiliza este enfoque para animaciones multiplataforma.

¿Cómo elegir la curva de easing adecuada para mi aplicación?

Use las Guías de Movimiento de Material Design: para la aparición de elementos — ease-out (inicio rápido, final suave), para la desaparición — ease-in (final rápido), para el movimiento entre pantallas — ease-in-out. Use animación spring para elementos interactivos (botones, tarjetas). Evite linear — se ve antinatural.

¿Por qué mi animación con easing se ve entrecortada?

El entrecortamiento (jank) ocurre cuando la animación no alcanza los 60fps. Verifique: ¿está usando CAMediaTimingFunction (no un timer) en iOS, PathInterpolator (no Handler.post) en Android. Los cálculos pesados en el hilo principal, una jerarquía de capas compleja o un drawable grande pueden causar caídas de fotogramas. En iOS, use Core Animation con shouldRasterize para capas complejas; en Android — Hardware Acceleration y renderizado por GPU.

Resumen

  • Easing es una función de velocidad de animación que hace que el movimiento sea natural mediante cambios no lineales de parámetros en el tiempo.
  • iOS CAMediaTimingFunction y SwiftUI Animation.timingCurve implementan el easing mediante una curva Bézier cúbica con dos puntos de control.
  • Android PathInterpolator y Compose CubicBezierEasing son análogos completos de iOS para curvas de suavizado personalizadas.
  • Las curvas de easing multiplataforma se definen con los mismos puntos de control — la base para la unificación de animaciones.
  • Material Design 3 recomienda fast-out-slow-in para aparición, linear-out-slow-in para desaparición.
  • La animación spring en SwiftUI (Animation.spring) y Compose (spring()) crea un efecto físico de resorte con dampingFraction.
  • El easing lineal no se recomienda para animaciones de interfaz — se ve antinatural y mecánico.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también