Easing: 모바일 애니메이션의 스무딩 함수

저자: IT Sectr 게시일: 2026-03-02 읽는 시간: 8 분
Easing(스무딩 함수)은 시간에 따른 애니메이션 매개변수의 변화 속도를 결정하는 수학적 곡선입니다. 선형 등가속 운동 대신 이징은 물리적 동작을 모방합니다. 객체는 가속(ease-in), 감속(ease-out) 또는 둘 다(ease-in-out) 할 수 있습니다. iOS에서 이징은 CAMediaTimingFunction 클래스(Core Animation)와 SwiftUI 수식자(Animation.easeInOut)를 통해 구현됩니다. Android에서는 PathInterpolator와 TimeInterpolator를 통해 구현됩니다. Apple Developer Documentation에 따르면, 적절히 선택된 이징 곡선은 실제 지속 시간을 변경하지 않고 인지된 애니메이션 시간을 20~35% 단축합니다. IT Sectr에서는 ease-in-out 곡선(0.42, 0.0, 0.58, 1.0)을 모든 모바일 프로젝트의 기업 표준으로 표준화했습니다.

핵심 요점

  • Easing — 물리적 움직임(가속, 감속 또는 조합)을 모방하는 애니메이션 속도 함수입니다.
  • CAMediaTimingFunction — 5개의 미리 정의된 곡선과 사용자 정의 3차 베지어가 있는 iOS Core Animation 클래스입니다.
  • SwiftUI Animation — 선언적 이징을 위한 .easeIn, .easeOut, .easeInOut, .spring 및 .timingCurve 수식자입니다.
  • PathInterpolator — 두 개의 제어점을 통한 3차 베지어 곡선으로 Android에서 이징에 해당합니다.
  • 3차 베지어 — iOS, Android 및 웹에 공통적인 이징 곡선 지정을 위한 범용 형식입니다.

Easing이란?

Easing(스무딩 함수)은 선형 시간 t ∈ [0,1]을 비선형 애니메이션 값에 매핑하는 수학 함수 f(t)입니다. 애니메이션이 500ms 동안 객체의 위치를 A에서 B로 변경하는 경우, 이징은 각 시간 지점에서 객체가 이 지점들 사이를 어떻게 이동하는지 결정합니다. 선형 이징(f(t)=t)은 일정한 속도를 의미하며 객체가 기계적으로 움직입니다. 비선형 이징은 자연스럽게 느껴집니다. 객체가 가속(ease-in), 감속(ease-out) 또는 부드러운 시작과 끝(ease-in-out)을 갖습니다.

이징 함수는 3차 베지어 곡선으로 설명되며, 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). 이 동일한 값이 iOS의 CAMediaTimingFunction과 Android의 PathInterpolator에서 사용됩니다. Material Design Motion Guidelines에 따르면, 이징 곡선은 컨텍스트와 일치해야 합니다: 나타날 때는 ease-out(빠른 시작), 사라질 때는 ease-in(빠른 끝).

iOS의 Easing: CAMediaTimingFunction

iOS에서 이징은 주로 Core Animation의 CAMediaTimingFunction 클래스를 통해 작동합니다. 이 클래스는 두 개의 제어점(c1x, c1y, c2x, c2y)으로 3차 베지어 곡선을 받아 CAAnimation(CABasicAnimation, CAKeyframeAnimation)에 적용합니다. CAMediaTimingFunction은 5개의 미리 정의된 함수를 지원합니다: linear, easeIn, easeOut, easeInEaseOut 및 default(easeInEaseOut과 동일).

CAMediaTimingFunction은 CALayer 수준에서 작동하며, 애니메이션은 별도의 렌더 서버 프로세스에서 실행되어 복잡한 변환에서도 60fps를 보장합니다. UIView 속성(frame, alpha, transform) 애니메이션의 경우 UIKit은 options 매개변수 .curveEaseInOut과 함께 UIView.animate를 사용하며, 내부적으로 해당 곡선으로 CAMediaTimingFunction도 생성합니다. iOS 17+에서는 사용자 정의 베지어를 위한 UIViewAnimationCurve.custom과 BlockParameters가 추가되어 사용자 정의 CAAnimation을 작성할 필요가 없어졌습니다. CAMediaTimingFunction은 UIKit Dynamics 및 UIScrollView의 감속(UIScrollViewDecelerationRate)에도 사용됩니다.

SwiftUI의 Easing

SwiftUI에서 이징은 Animation 구조체를 통해 .easeIn, .easeOut, .easeInOut, .spring 및 .interpolatingSpring 수식자로 설정됩니다. 애니메이션이 절차적(beginAnimations/commitAnimations)인 UIKit과 달리, SwiftUI는 선언적 접근 방식을 사용합니다: 애니메이션은 변경되는 상태 속성이며 프레임워크가 전환을 애니메이션하는 방법을 결정합니다.

.timingCurve(c0x, c0y, c1x, c1y) 수식자는 3차 베지어 제어점을 직접 받아들이며, 이는 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를 발표했습니다. 이는 각 키프레임에서 사용자 정의 이징을 사용한 프레임별 애니메이션으로, 궤적을 완전히 제어할 수 있습니다.

Android의 Easing: PathInterpolator

Android에서 이징은 PathInterpolator(API 21+)를 통해 구현됩니다. 이는 3차 베지어 곡선이 있는 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에서 이징은 androidx.compose.animation 패키지의 Easing 객체를 통해 설정됩니다. FastOutSlowInEasing, LinearOutSlowInEasing, FastOutLinearInEasing 및 CubicBezierEasing을 통한 사용자 정의 Easing을 사용할 수 있습니다. Compose Easing은 animate*AsState, AnimatedVisibility 및 AnimatedContent에 적용됩니다. View 시스템과의 주요 차이점: Compose는 Kotlin 쪽에서 이징을 보간하며(RenderNode가 아님), 사용자 정의 애니메이션에 더 많은 유연성을 제공하지만 120Hz 디스플레이에서 최적화가 필요합니다.

코드 예제

iOS: CABasicAnimation과 함께 CAMediaTimingFunction 사용

사용자 정의 ease-in-out 곡선을 사용한 기본 레이어 위치 애니메이션입니다. CAMediaTimingFunction은 움직임의 부드러운 시작과 끝을 정의하는 3차 베지어 제어점을 받습니다.

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은 렌더 서버에서 60/120fps로 애니메이션을 실행합니다.

SwiftUI: timingCurve로 Easing 사용

사용자 정의 곡선과 스프링 효과를 사용한 SwiftUI의 선언적 애니메이션입니다. 애니메이션 수식자는 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) 수식자는 SwiftUI에서 사용자 정의 이징 애니메이션을 만듭니다. value 매개변수는 애니메이션을 특정 상태에 바인딩합니다. isAnimated가 변경되면 SwiftUI가 자동으로 곡선을 적용합니다. 스프링 애니메이션의 경우 timingCurve 대신 .spring(response: 0.4, dampingFraction: 0.6)을 사용합니다.

Android: 사용자 정의 Easing으로 PathInterpolator 사용

PathInterpolator를 통한 Android의 ease-in-out에 해당합니다. 제어점(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()
    }
}

제어점(0.42, 0.0, 0.58, 1.0)이 있는 PathInterpolator는 iOS의 CAMediaTimingFunction ease-in-out과 동일합니다. 크로스 플랫폼 프로젝트의 경우 동일한 제어점을 사용하여 시각적으로 동일한 애니메이션 동작을 보장합니다. Material Design은 Android 특정 인터페이스의 대안으로 FastOutSlowInInterpolator(0.4, 0.0, 0.2, 1.0)를 권장합니다.

Jetpack Compose: CubicBezierEasing으로 Easing 사용

Compose에서 이징은 동일한 제어점으로 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)
        )
    }
}

Compose의 CubicBezierEasing은 동일한 4개의 제어점 매개변수를 받습니다. animateFloatAsState는 지정된 곡선으로 alpha 값을 자동으로 보간합니다. 비교: Compose의 FastOutSlowInEasing은 (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이라는 용어가 범용적으로 사용됩니다.

iOS에서 어떤 이징 함수를 사용할 수 있나요?

iOS는 5개의 미리 정의된 함수(linear, easeIn, easeOut, easeInEaseOut, custom(3차 베지어))가 있는 CAMediaTimingFunction을 제공합니다. SwiftUI에서는 Animation.easeIn, Animation.easeOut, Animation.easeInOut, Animation.spring 및 Animation.interpolatingSpring을 사용할 수 있습니다. 사용자 정의 곡선의 경우 Animation.timingCurve(c0x, c0y, c1x, c1y, duration:)를 사용합니다.

iOS와 Android에서 동일한 이징 함수를 사용할 수 있나요?

네, 동일한 3차 베지어 곡선을 설정하면 가능합니다. iOS의 CAMediaTimingFunction과 Android의 PathInterpolator는 3차 베지어 제어점(x1, y1, x2, y2)을 받습니다. 예를 들어, ease-in-out(0.42, 0.0, 0.58, 1.0) 곡선은 두 플랫폼에서 동일한 동작을 제공합니다. Material Design은 크로스 플랫폼 애니메이션에 이 접근 방식을 사용합니다.

내 애플리케이션에 적합한 이징 곡선을 선택하는 방법은?

Material Design Motion Guidelines을 사용하세요: 요소 나타남에는 ease-out(빠른 시작, 부드러운 끝), 사라짐에는 ease-in(빠른 끝), 화면 간 이동에는 ease-in-out을 사용합니다. 대화형 요소(버튼, 카드)에는 스프링 애니메이션을 사용합니다. linear는 부자연스러워 보이므로 피하세요.

이징 애니메이션이 끊겨 보이는 이유는 무엇인가요?

끊김(잉크)은 애니메이션이 60fps에 도달하지 못할 때 발생합니다. 확인하세요: iOS에서 CAMediaTimingFunction(타이머 아님)을 사용하고 있나요? Android에서 PathInterpolator(Handler.post 아님)를 사용하고 있나요? 메인 스레드에서의 무거운 계산, 복잡한 레이어 계층 구조 또는 큰 drawable이 프레임 드롭을 유발할 수 있습니다. iOS에서는 복잡한 레이어에 shouldRasterize와 함께 Core Animation을 사용하고, Android에서는 하드웨어 가속과 GPU 렌더링을 사용합니다.

요약

  • Easing은 시간에 따른 비선형 매개변수 변화를 통해 움직임을 자연스럽게 만드는 애니메이션 속도 함수입니다.
  • iOS CAMediaTimingFunction과 SwiftUI Animation.timingCurve는 두 개의 제어점이 있는 3차 베지어 곡선을 통해 이징을 구현합니다.
  • Android PathInterpolator와 Compose CubicBezierEasing은 사용자 정의 스무딩 곡선을 위한 완전한 iOS 유사체입니다.
  • 크로스 플랫폼 이징 곡선은 동일한 제어점으로 정의되며, 이는 애니메이션 통합의 기초입니다.
  • Material Design 3는 나타남에 fast-out-slow-in, 사라짐에 linear-out-slow-in을 권장합니다.
  • SwiftUI(Animation.spring)와 Compose(spring())의 스프링 애니메이션은 dampingFraction으로 물리적 스프링 효과를 만듭니다.
  • 선형 이징은 인터페이스 애니메이션에 권장되지 않으며 부자연스럽고 기계적으로 보입니다.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기