Hero Animation이란? Flutter에서의 공유 요소 전환

저자: IT Sectr 게시일: 2026-03-02 읽는 시간: 8 분
Hero Animation은 UI 요소가 화면 사이를 부드럽게 이동하고 변환되며, 한 콘텍스트에서 다른 콘텍스트로 여행하는 “히어로”의 효과를 만드는 애니메이션 패턴입니다. 표준 push/pop 전환과 달리, 히어로 애니메이션은 시각적 연속성을 유지합니다: 사용자는 썸네일이 전체 화면 이미지로 변하거나 셀 타이틀이 화면 타이틀로 이동하는 것을 보게 됩니다. Google Material Design(2025)에 따르면, Hero Animation은 리스트와 세부 사이를 네비게이션할 때 인지 부하를 30% 줄입니다. 요소의 위치를 기억하기 때문에 사용자는 집중을 잃지 않습니다. Flutter에서는 Hero 위젯을 통해 구현되고, SwiftUI에서는 matchedGeometryEffect 수식자를 통해, Jetpack Compose에서는 Modifier.sharedElement를 통해 구현됩니다.

주요 포인트

  • Hero Animation — 화면 사이의 부드러운 공유 요소 전환으로, 사용자의 시각적 맥락을 보존합니다.
  • Flutter Hero — 태그로 짝을 자동으로 찾고 화면 사이에 FlightShuttle 애니메이션을 시작하는 위젯.
  • matchedGeometryEffect — 애니메이션 전환을 위해 namespace의 식별자를 통해 요소를 연결하는 SwiftUI 수식자.
  • Shared Element Transition — ActivityOptions 또는 Jetpack Compose의 Modifier.sharedElement를 통해 구현되는 Android 메카니즘.
  • FlightShuttle — 전환 중 Hero 위젯의 궤적과 변환을 제어하는 Flutter의 내부 메카니즘.

Hero Animation이란?

Hero Animation은 공유 요소가 소스 화면에서 목적 화면으로 부드럽게 이동하며, 정체성을 유지하는 UI 애니메이션 기법입니다. 이 이름은 화면 사이의 스크립트화된 전환을 거쳐 사용자의 주목을 끜는 주요 요소인 “히어로”의 은유를 반영합니다. 표준 전환과 달리, Hero Animation은 시각적 연결을 끝지 않습니다: 사용자는 요소의 지속적인 궤적을 보고, 네비게이션 계층에 대한 이해가 향상됩니다.

Hero Animation의 개념은 Material Design에서 리스트-세부 화면, 갤러리 그리고 카드에 대한 권장으로 입혀져 있습니다. 애니메이션 알고리는 세 단계로 구성됩니다: 소스 화면에서 요소가 하이라이트되고 결대 위치에 고정됩니다; 전환 중에 목적 컨테이너에 따라 크기가 조절되고, 이동하고, 형태가 바뀝니다; 최종 화면에서 요소가 새 레이아웃에 임베딩됩니다. Material Design 3에 따르면, 올바르게 구현된 히어로 애니메이션은 잘못된 “뒤로” 버튼 누르머 수를 22% 줄입니다.

Flutter에서의 Hero: 태그와 FlightShuttle

Flutter에서 Hero Animation은 Hero 위젯을 통해 구현되며, 필수 매개변수인 tag를 가집니다. tag는 고유 식별자입니다. Flutter는 소스 라우트와 목적 라우트에서 동일한 tag를 가진 Hero 위젯 짝을 자동으로 찾고 애니메이션을 시작합니다. Hero 위젯은 수동 애니메이션 관리가 필요 없습니다. 프레임워크가 독립적으로 궤적을 계산하고 내부 FlightShuttle 객체를 통해 변환을 적용합니다.

FlightShuttle은 보이지 않는 “셉틀”이며, 전환 순간에 Hero 위젯의 오버레이 사본을 만들고 결대 좌표계에 배치한 다음, 초기 위치에서 최종 위치까지 변환(크기 조절, 이동, 회전)을 애니메이션합니다. flightShuttleBuilder 특성을 통해 FlightShuttle에 사용자 정의 곡선을 전달할 수 있어 비행 궤적을 제어할 수 있습니다. Flutter 3.16+에서 Hero는 clipRect와 borderRadius를 유지하면서 애니메이션을 지원하여, 동근 카드가 직사각 화면로 변하는 효과를 제공합니다.

SwiftUI에서의 matchedGeometryEffect

SwiftUI에서 Hero Animation의 밸룹은 iOS 14에서 도입된 matchedGeometryEffect 수식자입니다. 이는 식별자(id)와 namespace를 통해 두 요소를 연결합니다. Hero가 자동으로 전환을 가로채는 Flutter와 달리, SwiftUI는 namespace의 명시적 선언과 동일한 계층안이나 GeometryReader를 통해 시널의 양 쪽(소스와 목적)에 수식자를 적용해야 합니다.

matchedGeometryEffect 애니메이션은 위치, 크기, 회전, 그리고 모나이의 변환을 지원합니다. 부드러운 운동을 위해 withAnimation에 Animation을 지정합니다. SwiftUI는 기본 easeInOut 곡선을 기반으로 중간 상태를 자동으로 보간합니다. iOS 17+에서는 TabView와 NavigationStack에 대한 matchedTransitionSource 지원이 추가되었습니다. 표준 전환을 위한 미리 정의된 궤적을 가진 Hero Animation의 시스템 아나로그입니다.

Android에서의 Shared Element Transition

Android에서 Hero Animation은 Shared Element Transition이라고 하며, ActivityOptions.makeSceneTransitionAnimation 또는 FragmentTransaction.addSharedElement를 통해 구현됩니다. 각 공유 요소는 XML의 transitionName 속성이나 코드로 표시되고, Android가 액티비티 사이의 애니메이션을 처리합니다. Android 5.0(API 21)부터 전환이 프레임워크의 일부가 되었습니다. 그 이전에는 ValueAnimator를 사용하여 커스텀 애니메이션을 작성해야 했습니다.

Jetpack Compose에서 Shared Element Transition은 Modifier.sharedElement과 Modifier.sharedBounds 수식자를 AnimatedContent 또는 SharedTransitionLayout(Compose BOM 2024.01부터 사용 가능)과 함께 구현됩니다. View 시스템과 달리, Compose는 요소를 지연적으로 다시 그리기 때문에 공유 요소가 CompositionLocal에 캐시됩니다. Compose는 또한 scrollOffset을 유지하면서 리스트에서 세부로의 애니메이션을 지원합니다. Google I/O 2024에 따르면, Compose Shared Element Transition은 직접적인 RenderNode 관리로 인해 View 버전보다 15% 빠릅니다.

코드 예제

Flutter: 이미지와 Hero

Flutter에서 Hero Animation의 기본 예제입니다. 리스트 화면의 썸네일과 세부 화면의 전체 화면 이미지입니다. image-hero 태그가 두 위젯을 연결하고, FlightShuttle이 카드에서 전체 화면으로의 전환을 자동으로 애니메이션합니다.

dart
import 'package:flutter/material.dart';

class ListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          Hero(
            tag: 'image-hero',
            child: GestureDetector(
              onTap: () {
                Navigator.push(context,
                  MaterialPageRoute(
                    builder: (_) => DetailScreen(),
                  ),
                );
              },
              child: ClipRRect(
                borderRadius: BorderRadius.circular(12),
                child: Image.network(
                  'https://picsum.photos/200',
                  width: 100, height: 100,
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Detail')),
      body: Center(
        child: Hero(
          tag: 'image-hero',
          child: Image.network(
            'https://picsum.photos/800',
            width: 300, height: 400,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

포인트는 두 화면 모두에서 동일한 태그(image-hero)를 사용하는 것입니다. Flutter는 라우트 전환도중에 태그를 기준으로 Hero 위젯을 매칭합니다. 태그가 다른 경우 애니메이션이 시작되지 않고, 요소가 전환 없이 새 화면에 나타납니다. 태그에 어떤 타입이나 사용 가능하지만, 문자열이 가장 신뢰할 수 있는 옵션입니다.

SwiftUI: 화면 사이 matchedGeometryEffect

NavigationStack을 사용한 SwiftUI에서 Hero Animation의 예입니다. matchedGeometryEffect 수식자가 namespace를 통해 리스트의 이미지와 세부 화면을 연결하고, withAnimation이 부드러운 전환을 시작합니다.

swift
import SwiftUI

struct HeroExampleView: View {
    @Namespace private var heroNamespace
    @State private var isDetailShown = false

    var body: some View {
        VStack {
            if isDetailShown {
                DetailView(namespace: heroNamespace)
                    .transition(.identity)
            } else {
                ListView(namespace: heroNamespace)
                    .transition(.identity)
            }
        }
        .animation(Animation.easeInOut(duration: 0.4),
                   value: isDetailShown)
    }
}

struct ListView: View {
    let namespace: Namespace.ID

    var body: some View {
        Image("thumbnail")
            .resizable()
            .frame(width: 100, height: 100)
            .cornerRadius(12)
            .matchedGeometryEffect(id: "hero", in: namespace)
    }
}

struct DetailView: View {
    let namespace: Namespace.ID

    var body: some View {
        Image("thumbnail")
            .resizable()
            .frame(width: 300, height: 400)
            .cornerRadius(0)
            .matchedGeometryEffect(id: "hero", in: namespace)
    }
}

@Namespace matchedGeometryEffect 식별자에 대한 이름 공간을 만듭니다. 둘 다 동일한 id(“hero”)와 공유 namespace를 사용합니다. isDetailShown이 토글될 때 SwiftUI는 두 뷰 사이의 크기, 위치 그리고 모나이의 변환을 애니메이션하여 히어로 효과를 만듭니다. iOS 16+의 표준 NavigationLink에는 navigationTransition(.hero)를 사용하세요.

Android: Jetpack Compose와 Shared Element Transition

SharedTransitionLayout을 사용한 Jetpack Compose에서 Shared Element Transition의 예입니다. sharedElement 수식자가 네비게이션 중에 화면 사이의 공유 요소를 애니메이션합니다.

kotlin
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun HeroExample() {
    SharedTransitionLayout {
        var isDetail by remember { mutableStateOf(false) }

        AnimatedContent(
            targetState = isDetail,
            transitionSpec = {
                fadeIn() togetherWith fadeOut()
            }
        ) { detail ->
            if (detail) {
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .sharedElement(
                            state = rememberSharedContentState(key = "hero"),
                            animatedVisibilityScope = this
                        )
                ) {
                    AsyncImage(
                        model = "https://picsum.photos/800",
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            } else {
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .sharedElement(
                            state = rememberSharedContentState(key = "hero"),
                            animatedVisibilityScope = this
                        )
                ) {
                    AsyncImage(
                        model = "https://picsum.photos/200",
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            }
        }
    }
}

Compose SharedTransitionLayout(Compose BOM 2024.01+)은 계층을 래핑하고, “hero” 키를 가진 sharedElement 수식자가 초기 요소와 최종 요소를 연결합니다. AnimatedContent가 상태를 토글하고, Compose가 이미지의 크기, 위치 그리고 형태를 자동으로 애니메이션합니다. 주요 장점은 bounds와 clipBounds 애니메이션 지원으로, 전환 중 카드 동근 효과를 제공합니다.

자주 묻는 질문

Hero Animation은 iOS와 Android에서 작동합니까?

네, Hero Animation은 두 플랫폼 모두에서 사용 가능합니다. iOS는 SwiftUI에서 matchedGeometryEffect 또는 UINavigationController.transitioningDelegate를 사용합니다. Android는 ActivityOptions.makeSceneTransitionAnimation 또는 Jetpack Compose에서 Modifier.sharedElement를 사용합니다. Flutter는 Hero 위젯을 통해 Hero Animation을 구현하며, 두 플랫폼에서 동일하게 작동합니다.

여러 요소를 동시에 애니메이션할 수 있습니까?

네, Hero Animation은 여러 공유 요소를 지원합니다. Flutter에서는 다른 태그를 가진 여러 Hero 위젯을 지정하세요. SwiftUI에서는 각 요소에 다른 식별자를 가진 matchedGeometryEffect를 사용하세요. 모든 애니메이션이 병렬로 실행되고, 각 요소가 자체 궤적으로 이동합니다.

Hero Animation은 큰 이미지에서 늘어집니까?

성능은 구현에 따라 다릅니다. Flutter FlightShuttle은 RenderObject를 통해 이미지를 처리하여 60fps를 달성합니다. SwiftUI matchedGeometryEffect는 벡터화되어 있습니다. 큰 이미지의 경우, Flutter에서 precacheImage 또는 SwiftUI에서 prefetch를 사용하면 첫 번째 전환에서 발생하는 지연이 제거됩니다. Android Compose는 AsyncImage를 통한 지연 로딩이 필요합니다.

Hero는 표준 push 전환과 어떻게 다릅니까?

표준 push 전환은 전체 화면을 슬라이드하여, 카드 더미의 옷음을 만듭니다. Hero Animation은 시각적 주목을 하나의 요소에 고정시쿜, 사용자가 그 지속적인 움직임을 보고 화면 간 관계에 대한 이해가 향상됩니다. Push는 계층적 네비게이션에 적합하고, Hero는 콘텐츠를 강조하는 데 적합합니다.

Hero Animation에 추가 라이브러리가 필요한가요?

아니요, Hero Animation은 내장 도구로 구현되어 있습니다. Flutter에서는 flutter/material.dart 패키지의 Hero 위젯입니다. SwiftUI에서는 matchedGeometryEffect 수식자입니다. Android Jetpack Compose에서는 표준 Compose의 SharedTransitionLayout과 Modifier.sharedElement입니다. 추가 라이브러리는 비표준 궤적이나 복잡한 변환에만 필요합니다.

요약

  • Hero Animation은 시각적 연속성을 유지하고 인지 부하를 줄이는 화면 간 공유 요소 전환입니다.
  • Flutter Hero와 tag 그리고 FlightShuttle은 수동 애니메이션 관리 없이 히어로 애니메이션을 구현하는 가장 간단한 방법입니다.
  • SwiftUI matchedGeometryEffect @Namespace.id로 두 상태 간의 요소 위치, 크기, 그리고 모나이를 애니메이션합니다.
  • Android SharedTransitionLayout과 Jetpack Compose의 Modifier.sharedElement는 RenderNode 캐싱으로 전환을 제공합니다.
  • 세 프레임워크 모두 병렬 애니메이션으로 여러 공유 요소를 지원합니다.
  • FlightShuttle은 Flutter에서 flightShuttleBuilder를 통해 비행 궤적을 커스터마이즈할 수 있습니다.
  • Hero Animation은 Material Design에서 리스트-세부 화면, 갤러리, 그리고 카드에 권장됩니다.

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

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

프로젝트 논의

더 읽어보기