Transition (전환 애니메이션)은 하나의 콘텐츠를 다른 콘텐츠로 시각적으로 교체하는 메커니즘입니다: 화면, 프래그먼트, 뷰 또는 인터페이스 상태. 올바르게 구현된 전환은 사용자의 주의를 유도하고, 요소 간의 관계를 설명하며, 내비게이션을 직관적으로 만듭니다. Material Design Guidelines(2026)에 따르면, 잘 설계된 전환이 있는 앱은 두 번째 세션에서 사용자 유지율이 22% 더 높습니다. 다른 유형의 애니메이션에 대한 자세한 내용은 일반 애니메이션 가이드를 참조하세요.
주요 내용
Transition은 하나의 시각적 인터페이스 상태를 다른 상태로 대체할 때 발생하는 애니메이션입니다. Property Animation(단일 객체의 속성 애니메이션)과 달리, Transition은 전체 변경 세트를 관리합니다: 요소의 나타남/사라짐, 위치, 크기 및 속성 변경을 하나의 전환 내에서 처리합니다. Transition은 사용자가 무엇이 어디로 이동했는지 이해할 수 있도록 콘텐츠 변경 문제를 해결합니다.
모바일 플랫폼에서 Transition은 세 가지 주요 시나리오에서 사용됩니다: 화면 간 내비게이션(push, pop, modal present), 프래그먼트 변경(Android FragmentTransaction), 화면 내 상태 변경(카드 확장, 탭 전환). 각 시나리오에는 고유한 접근 방식이 필요합니다: 내비게이션 전환 — iOS의 UIViewControllerAnimatedTransitioning 또는 Android의 ActivityOptions, 화면 내 전환 — Transition Framework 또는 UIView.transition.
Apple HIG(2026)에 따르면, 표준 전환의 지속 시간은 400~500ms를 초과해서는 안 됩니다 — 더 긴 애니메이션은 지연으로 인식됩니다. Material Design은 모바일 전환에 300~350ms를 권장합니다. 큰 요소(카드, 이미지)의 경우, 인지 성능 저하 없이 시간을 500~600ms까지 늘릴 수 있습니다.
전환 유형은 어떤 콘텐츠가 어떻게 변경되는지에 따라 여러 범주로 나뉩니다. 기본 유형: cross-fade — 하나가 부드럽게 사라지고 다른 하나가 나타남; slide — 한 화면이 나가고 다른 화면이 들어옴; scale/zoom — 요소가 확대되어 상세 화면을 엶. 복합 전환은 여러 효과를 혼합합니다.
| 전환 유형 | 설명 | iOS API | Android API |
|---|---|---|---|
| Cross-fade | 페이드: 한 콘텐츠가 사라지고 다른 콘텐츠가 나타남 | UIView.transition(with:duration:options:.transitionCrossDissolve) | TransitionSet + Fade() |
| Slide | 슬라이드: 콘텐츠가 가로 또는 세로로 들어오고 나감 | CATransition(type: .push, subtype: .fromLeft) | Slide() / FragmentTransaction.setCustomAnimations |
| Scale / Zoom | 확대/축소: 요소가 전체 화면으로 확대됨 | UIViewPropertyAnimator(확대/축소 애니메이션) | Scale() + ActivityOptions.makeScaleUpAnimation |
| Explode | 폭발: 콘텐츠가 사라질 때 조각으로 분해됨 | CATransition(type: .reveal) | Explode()(API 21+) |
| Fade-through | 흰색/검은색 화면을 통한 페이드 | UIView.transition(with: .transitionCrossDissolve, 흰색 배경) | ChangeBounds를 통한 Fade() |
Material Design 규칙: 통일된 시각적 언어를 위해 하나의 앱 내에서 하나의 전환 유형을 사용합니다. 권장: 동일한 수준의 화면 간 내비게이션에는 slide(마스터 → 세부), 하나의 화면 내 콘텐츠 변경에는 cross-fade(탭, 보기 전환), 카드에서 상세 화면으로의 전환에는 scale/zoom(제품 카드 → 설명).
iOS에서 UIView.transition은 단일 View 내에서 콘텐츠 변경을 애니메이션하는 가장 간단한 방법입니다. cross-dissolve, curl-up, curl-down, flip-from-left, flip-from-right를 지원합니다. UIViewController 간의 내비게이션 전환에는 UIViewControllerAnimatedTransitioning과 함께 UINavigationControllerDelegate를 사용합니다 — 이렇게 하면 push/pop 애니메이션을 완전히 제어할 수 있습니다.
import UIKit
// UIView.transition: cross-dissolve로 이미지 변경
UIView.transition(with: imageView,
duration: 0.4,
options: [.transitionCrossDissolve, .curveEaseInOut]
) {
self.imageView.image = UIImage(named: "new_image")
} completion: { _ in
print("Transition completed")
}
// UIView.transition: 두 뷰 교체
UIView.transition(from: firstView, to: secondView,
duration: 0.5,
options: [.transitionFlipFromRight, .showHideTransitionViews]
) { _ in
// firstView 숨김, secondView 표시
}
// CATransition 레이어용(Auto Layout 없음)
let transition = CATransition()
transition.type = .push
transition.subtype = .fromRight
transition.duration = 0.35
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
view.layer.add(transition, forKey: "pushTransition")
UIViewPropertyAnimator는 iOS 10+에서 맞춤 전환을 위한 현대적인 대안입니다. UIView.transition과 달리 PropertyAnimator는 일시 중지, 취소 및 역방향 애니메이션을 지원하므로 대화형 전환(스와이프하여 뒤로 가기)에 중요합니다. UIViewController 간 애니메이션을 위해 PropertyAnimator와 함께 UIPercentDrivenInteractiveTransition을 사용합니다 — 이것이 대화형 내비게이션의 표준 패턴입니다.
Android에서 FragmentTransaction과 setCustomAnimations는 Fragment 간 전환을 애니메이션하는 주요 방법입니다. 매개변수: enter(새 Fragment의 진입 애니메이션), exit(이전 Fragment의 퇴장 애니메이션), popEnter 및 popExit — 뒤로 가기 내비게이션용. FragmentTransaction은 애니메이션 리소스(R.anim)와 Transition Framework(R.transition)를 지원합니다.
import androidx.fragment.app.FragmentTransaction
import androidx.transition.TransitionInflater
// FragmentTransaction 리소스 애니메이션 포함
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.slide_in_right, // enter
R.anim.slide_out_left, // exit
R.anim.slide_in_left, // popEnter
R.anim.slide_out_right // popExit
)
.replace(R.id.container, DetailFragment())
.addToBackStack(null)
.commit()
// FragmentTransaction Transition Framework 포함
val detailFragment = DetailFragment()
detailFragment.enterTransition = TransitionInflater.from(this)
.inflateTransition(R.transition.slide_right)
detailFragment.exitTransition = TransitionInflater.from(this)
.inflateTransition(R.transition.fade_out)
supportFragmentManager.beginTransaction()
.replace(R.id.container, detailFragment)
.addToBackStack(null)
.commit()
// ActivityOptions: Activity 시작 시 애니메이션
val options = ActivityOptions.makeScaleUpAnimation(
view, 0, 0, view.width, view.height
).toBundle()
startActivity(intent, options)
Android 모범 사례: Slide() — 내비게이션에서 Fragment 교체용(enter = Slide(Gravity.END), exit = Slide(Gravity.START)). Fade() — 오버레이 및 대화상자 표시/숨기기용. Explode() — 악센트 전환용(목록에서 항목 삭제). 이전 기기(API < 21)에서는 R.anim 리소스를 폴백으로 사용합니다. Transition Framework는 SurfaceView, TextureView, GLSurfaceView를 지원하지 않습니다 — 비디오 및 카메라에는 R.anim 애니메이션만 사용하세요.
UIViewControllerAnimatedTransitioning은 완전히 맞춤화된 뷰 컨트롤러 전환 애니메이션을 생성하기 위한 iOS 프로토콜입니다. 컨테이너 뷰(transitionContext.containerView), 소스 및 대상 뷰에 대한 액세스를 제공합니다. UIPercentDrivenInteractiveTransition과 결합하면 이 접근 방식은 손가락 스크롤로 대화형 전환을 만들 수 있습니다.
import UIKit
"> 커스텀 전환 애니메이터
class ScaleTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let isPresenting: Bool
init(presenting: Bool) { self.isPresenting = presenting }
func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.4
}
func animateTransition(using ctx: UIViewControllerContextTransitioning) {
guard let toView = ctx.view(forKey: .to),
let fromView = ctx.view(forKey: .from)
else { return }
let container = ctx.containerView
let initialScale: CGAffineTransform = CGAffineTransform(scaleX: 0.8, y: 0.8)
let finalFrame = ctx.finalFrame(for: toView)
if isPresenting {
toView.transform = initialScale
toView.alpha = 0
toView.frame = finalFrame
container.addSubview(toView)
}
UIView.animate(withDuration: transitionDuration(using: ctx),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.3) {
if self.isPresenting {
toView.transform = .identity
toView.alpha = 1
fromView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
fromView.alpha = 0
} else {
fromView.transform = initialScale
fromView.alpha = 0
}
} completion: { _ in
fromView.transform = .identity
ctx.completeTransition(!ctx.transitionWasCancelled)
}
}
}
// UINavigationControllerDelegate에서 적용
extension MainViewController: UINavigationControllerDelegate {
func navigationController(
_ nc: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from: UIViewController, to: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return ScaleTransitionAnimator(presenting: operation == .push)
}
}
대화형 전환: 소스 화면에 UIPanGestureRecognizer를 추가합니다. 제스처가 트리거되면 UIPercentDrivenInteractiveTransition을 만들고 update(CGFloat(percent))를 호출합니다. 임계값(보통 30%)에 도달하지 않을 때 취소하려면 cancel()을 호출합니다. 이 패턴은 UINavigationController의 pop 제스처와 pagingEnabled가 있는 UIScrollView에서 기본적으로 사용됩니다. 맞춤 제스처의 경우 UIViewControllerInteractiveTransitioning을 구현합니다.
Transition Framework(android.transition)는 두 장면(Scene) 간의 변경을 자동으로 애니메이션하는 선언형 애니메이션 시스템입니다. 장면은 특정 상태를 가진 Views의 집합입니다. 장면 A에서 장면 B로 전환할 때 Transition Framework는 차이를 분석하고 애니메이션합니다: 위치 변경(ChangeBounds), 크기 변경(ChangeClipBounds, ChangeTransform), 나타남(Fade) 및 사라짐(Fade).
import android.transition.Scene
import android.transition.TransitionManager
import android.transition.TransitionSet
import android.transition.AutoTransition
import android.transition.ChangeBounds
import android.transition.Fade
// TransitionSet: ChangeBounds + Fade 조합
val transition = TransitionSet().apply {
ordering = TransitionSet.ORDERING_TOGETHER
addTransition(ChangeBounds())
addTransition(Fade(Fade.IN))
duration = 400L
interpolator = FastOutSlowInInterpolator()
}
// ViewGroup 내 장면 간 전환
val sceneRoot = findViewById<ViewGroup>(R.id.scene_root)
val scene1 = Scene.getSceneForLayout(sceneRoot, R.layout.scene_initial)
val scene2 = Scene.getSceneForLayout(sceneRoot, R.layout.scene_expanded)
"> 수동 설정 없는 AutoTransition
TransitionManager.go(scene2, AutoTransition().apply {
duration = 350L
})
// TransitionManager.beginDelayedTransition — 장면 없음
TransitionManager.beginDelayedTransition(sceneRoot, ChangeBounds())
itemView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
itemView.requestLayout() "> layout 변경이 자동으로 애니메이션됩니다
ChangeBounds는 Android에서 가장 많이 사용되는 전환으로, View의 위치 및 크기 변경을 애니메이션합니다. ChangeTransform은 회전 및 배율을 애니메이션합니다. ChangeImageTransform — ImageView 애니메이션용(centerCrop → fitCenter). 계층 구조 변경이 있는 복잡한 전환의 경우 ordering = ORDERING_SEQUENTIAL과 함께 TransitionSet을 사용합니다(애니메이션이 병렬이 아닌 순차적으로 실행됨). Android Developers(2026) 문서에 따르면, Transition Framework는 모든 표준 View를 지원하지만 표면 애니메이션(SurfaceView, TextureView, VideoView)은 지원하지 않습니다.
자주 묻는 질문
Animation — 단일 객체의 속성(alpha, translation, scale)을 애니메이션합니다. Transition — 객체 또는 상태 집합의 변경을 애니메이션합니다(fragment → fragment, scene → scene). Transition은 여러 변경을 동시에 관리합니다: 나타남, 사라짐, 이동, 크기 변경 — 그리고 두 상태 간의 차이를 자동으로 애니메이션합니다. Animation은 각 속성을 수동으로 관리해야 합니다.
Transition Framework와 함께 FragmentTransaction.setCustomAnimations을 사용합니다: fragment1.exitTransition = Fade(Fade.OUT), fragment2.enterTransition = Slide(Gravity.END). 순서 지정을 위해 setReorderingAllowed(true) — Fragment가 부드러운 병렬 전환을 위해 재정렬됩니다. 간단한 경우 R.anim의 slide_in_right / slide_out_left로 충분합니다. 뒤로 가기 내비게이션에는 popEnter와 popExit을 지정합니다.
Transition Framework는 레이아웃 재계산을 트리거하지 않는 속성만 애니메이션합니다. 높이 애니메이션에는 ChangeBounds를 사용합니다 — 두 레이아웃 상태 간의 View 경계 변경을 자동으로 애니메이션합니다. LayoutParams를 변경하기 전에 TransitionManager.beginDelayedTransition(viewGroup, ChangeBounds())를 호출합니다. 높이가 WRAP_CONTENT를 통해 변경되는 경우 layout_height가 전후에 다른지 확인합니다 — 두 상태가 모두 match_parent이면 ChangeBounds가 작동하지 않습니다.
AnyTransition과 함께 .transition() 수정자를 사용합니다: .transition(.move(edge: .trailing).combined(with: .opacity)). 맞춤 전환의 경우 AnyTransition.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))을 구현합니다. NavigationStack 내에서 애니메이션의 경우 iOS 18+에서 .navigationTransition(.slide)을 사용합니다. SwiftUI는 UIViewControllerAnimatedTransitioning을 지원하지 않습니다 — AnyTransition을 통한 선언형 전환만 지원합니다.
Profile GPU Rendering(Android) 또는 Core Animation Instrument(iOS)를 사용합니다. Android의 경우 adb shell setprop debug.transition.profiling 1을 통해 Transition Profiling을 활성화합니다 — 로그에 각 전환 단계의 지속 시간이 표시됩니다. iOS의 경우 시뮬레이터에서 Slow Animations을 활성화합니다. 목표 지표: 지속 시간 < 400ms, FPS >= 55, 프레임 드롭 — 전환당 2회 이하. 15개 이상의 동시 애니메이션 View가 있는 복잡한 장면의 경우 300ms 지속 시간의 ChangeBounds를 사용합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.