Flutter

[Flutter] Hero 애니메이션 사용법

참깨빵위에참깨빵_ 2024. 9. 16. 03:33
728x90
반응형

Hero 애니메이션은 안드로이드에서 공유 요소 전환과 같은 효과를 내는 애니메이션이다.

 

https://developer.android.com/guide/fragments/animate?hl=ko#shared

 

Hero 애니메이션의 공식문서부터 먼저 확인한다.

 

https://docs.flutter.dev/ui/animations/hero-animations

 

Hero animations

How to animate a widget to fly between two screens.

docs.flutter.dev

한 화면에서 다른 화면으로 이미지를 이동하는 걸 Hero 애니메이션이라 하며 공유 요소 전환이라고도 한다. Hero 위젯을 써서 쉽게 구현할 수 있고 MaterialPageRoute를 써서 새 경로를 지정하면 머티리얼 디자인 모션 사양에 설명된 대로 이미지가 곡선 경로를 따라 날아간다

 

아래는 플러터 문서에서 링크된 깃허브로 이동하면 볼 수 있는 코드다.

 

class PhotoHero extends StatelessWidget {
  const PhotoHero({
    super.key,
    required this.photo,
    this.onTap,
    required this.width,
  });

  final String photo;
  final VoidCallback? onTap;
  final double width;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
      child: Hero(
        tag: photo,
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            onTap: onTap,
            child: Image.asset(
              photo,
              fit: BoxFit.contain,
            ),
          ),
        ),
      ),
    );
  }
}

 

이 코드의 핵심 정보는 아래와 같다.

 

  • 시작 경로는 Hero 애니메이션이 앱의 home 프로퍼티로 제공될 때 MaterialApp에 의해 암시적으로 push된다
  • InkWell이 이미지를 감싸서 onTap 제스처를 쉽게 추가할 수 있다
  • 머티리얼 위젯을 투명한 색으로 정의하면 이미지가 목적지로 날아갈 때 배경에서 튀어나오게 할 수 있다
  • SizedBox는 애니메이션 시작, 끝에서 Hero 위젯 크기를 지정한다
  • 이미지의 맞춤 속성을 BoxFit.contain으로 설정하면 화면 비율을 바꾸지 않고 전환하는 동안 이미지가 최대한 커진다

 

아래는 HeroAnimation 클래스의 전체 코드다.

 

class HeroAnimation extends StatelessWidget {
  const HeroAnimation({super.key});

  Widget build(BuildContext context) {
    timeDilation = 5.0; // 1.0 means normal animation speed.

    return Scaffold(
      appBar: AppBar(
        title: const Text('Basic Hero Animation'),
      ),
      body: Center(
        child: PhotoHero(
          photo: 'images/flippers-alpha.png',
          width: 300.0,
          onTap: () {
            Navigator.of(context).push(MaterialPageRoute<void>(
              builder: (context) {
                return Scaffold(
                  appBar: AppBar(
                    title: const Text('Flippers Page'),
                  ),
                  body: Container(
                    color: Colors.lightBlueAccent,
                    padding: const EdgeInsets.all(16),
                    alignment: Alignment.topLeft,
                    child: PhotoHero(
                      photo: 'images/flippers-alpha.png',
                      width: 100.0,
                      onTap: () {
                        Navigator.of(context).pop();
                      },
                    ),
                  ),
                );
              }
            ));
          },
        ),
      ),
    );
  }
}

 

이 코드의 핵심은 아래와 같다.

 

  • 유저가 Hero가 포함된 InkWell을 탭하면 MaterialPageRoute를 써서 목적지 경로를 생성한다. 이 경로를 네비게이터의 스택으로 push하면 Hero 애니메이션이 트리거된다
  • Container는 대상 경로의 왼쪽 상단 모서리, AppBar 아래에 PhotoHero를 배치한다
  • 목적지 PhotoHero의 onTap은 네비게이터 스택을 팝업해서 Hero를 원래 경로로 되돌리는 애니메이션을 트리거한다
  • 디버깅하는 동안 전환 속도를 늦추려면 timeDilation 프로퍼티를 사용한다

 

위 코드를 실행하면 아래처럼 작동한다.

 

 

PhotoHero 클래스는 Hero의 크기, 이미지, onTap에 정의한 동작을 유지하는데 아래와 같은 위젯 트리를 빌드한다.

 

 

정리하면 Hero 애니메이션은 한 화면에서 다른 화면으로 이동할 때 같이 날아가는 것처럼 보이게 만들어주는 애니메이션이다. 구현하려면 Hero 위젯을 사용하며 애니메이션을 트리거하기 위해 InkWell을 사용할 수 있다. InkWell이 아니라도 GestureDetector를 써서 onTap 매개변수를 구현하면 동일한 효과를 낼 수 있다.

그리고 Hero 애니메이션을 구현할 때 아래를 신경써야 한다.

 

  • 두 화면에서 각각 Hero 위젯을 사용해야 한다
  • Hero 위젯을 사용하면 tag는 필수 구현대상이다
  • Navigator 기반 화면 전환(push, pop 등)

 

PhotoHero의 build()에서 Hero 위젯의 첫 번째 매개변수로 tag를 적고 photo를 넣은 걸 볼 수 있다.

그리고 HeroAnimation 클래스에서 Center, Container 안에서 각각 PhotoHero 위젯을 구현하고 push, pop을 통해 앞으로 / 뒤로 화면을 이동할 때마다 Hero 애니메이션을 보여준다.

적절하게 넣으면 단조로운 화면 이동 과정을 색다르게 보여줄 수 있을 것이다.

반응형