| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
- study book
- llm을 활용 단어장 앱 개발일지
- BOF
- Computer Architecture
- Flutter
- ARM
- Image Processing
- BAEKJOON
- 영상처리
- C++
- system hacking
- MATLAB
- MDP
- bloc
- Got
- ML
- Stream
- Algorithm
- 백준
- FastAPI
- BFS
- PCA
- Widget
- pytorch
- Dreamhack
- DART
- fastapi를 사용한 파이썬 웹 개발
- Kaggle
- rao
- 파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습
- Today
- Total
Bull
[Flutter::Animation] Hero 기법 본문
Flutter 애니메이션으로 자주 사용되는 기법은 Fade와 Hero가 있습니다. 이번엔 Hero가 뭔지 언제 주로 사용하는 지에 대한 기본적인 내용만 다뤄 보겠습니다.
Hero 기본 개념
Flutter의 Hero 위젯은 두 개의 화면 사이에서 애니메이션 효과를 통해 부드럽게 전환되는 위젯을 제공합니다. 주로 이미지나 특정 위젯이 한 화면에서 다른 화면으로 이동할 때 자연스러운 전환 효과를 구현하는 데 사용됩니다.
Hero 애니메이션은 소스 화면과 목적지 화면에서 동일한 Hero 태그를 가진 위젯을 식별하여 작동합니다. 이렇게 하면 Flutter는 두 화면 사이에서 위젯의 위치와 크기를 애니메이션으로 연결할 수 있습니다.
Hero의 이름이 Hero인 이유?
Hero class 공식 문서에서는 Hero 위젯을 날아간다라고 표현하였습니다. 공식적으로 이 주제에 대한 설명은 못찾았지만 우리가 아는 "영웅"의 역할이 슈퍼맨과 같이 날아가거나 주인공이 눈에 띄게 튀는 효과를 내서 Hero라는 이름이 붙었다고 확신해도 될 거 같습니다. GPT와 제 추측이지만 만약 이게 사실이 아니더라도 Hero가 어떤 위젯인지 표현할 때 알기 쉬운 비유법이라고 생각되니 저는 이렇게 생각해도 좋을 것 같습니다.
CODE
본격적으로 Hero의 CODE를 살펴보겠습니다.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hero Animation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FirstPage(),
);
}
}
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('First Page')),
body: GridView.count(
crossAxisCount: 3,
children: <Widget>[
GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailPage(
tag: 'hero-image-1',
imageUrl: 'https://picsum.photos/250?image=9',
description: 'Image 1 Description',
);
}));
},
child: Hero(
tag: 'hero-image-1',
child: Image.network('https://picsum.photos/250?image=9'),
),
),
GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailPage(
tag: 'hero-image-2',
imageUrl: 'https://picsum.photos/250?image=10',
description: 'Image 2 Description',
);
}));
},
child: Hero(
tag: 'hero-image-2',
child: Image.network('https://picsum.photos/250?image=10'),
),
),
GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailPage(
tag: 'hero-image-3',
imageUrl: 'https://picsum.photos/250?image=11',
description: 'Image 3 Description',
);
}));
},
child: Hero(
tag: 'hero-image-3',
child: Image.network('https://picsum.photos/250?image=11'),
),
),
],
),
);
}
}
class DetailPage extends StatelessWidget {
final String tag;
final String imageUrl;
final String description;
DetailPage({required this.tag, required this.imageUrl, required this.description});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Detail Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
SizedBox(height: 50),
Hero(
tag: tag,
child: Container(
child: Image.network(imageUrl, fit: BoxFit.cover),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
description,
style: TextStyle(fontSize: 18),
),
),
],
),
),
);
}
}

이미지 소스는 url을 통해 가져오니 DartPad를 통하여 확인 가능합니다. 결과 내용을 설명해보겠습니다.
사진을 누르면 페이지가 설명페이지로 전환되며 기존에 첫 페이지에 있던 사진이 없어지지 않고 부드럽게 전환되며 정해진 위치로 오게됩니다. 저는 해당 이미지에 대해 강조해주는 느낌을 받았습니다. Hero 위젯의 역할이 이런 게 아닐까요?
Hero의 동작을 한 문장으로 표현해서 글만 보면 힘들 수 있는데 결과를 보면 바로 이해할 수 있을 것 같습니다.
CODE가 어떻게 흘러가는지, 어떤 약속을 지켜야 하는지도 중요합니다. 이번엔 그것에 대해서 말해보겠습니다.
본론만 먼저 말하자면 두 페이지 모두 Hero 위젯을 사용합니다. 여기서 중요한 건, tag프로퍼티를 사용했다는 점 인데요. 감이 오지 않나요? 바로 두 페이지간의 Hero 위젯에 tag 프로퍼티를 일치시켜 주어야합니다. 사실상 본론만으로 충분해요. 다른 개념은 라우트에 대한 기반지식이나 이미지 가져오고 파라미터 전달해주는 정도는 Hero 위젯과는 약간 동떨어진 내용일 수 있으니깐요. 기본으로 알아 두시는 게 좋습니다.
생소할 거 같은 부분만 살펴 보겠습니다.
Image.network: 이미지를 url을 통해 가져올 수 있습니다. 첫 번째 인자로 url을 받고 fit프로퍼티는 사진을 어떻게 컨테이너에 표현할 것인지를 나타냅니다. fit 프로퍼티에 대한 내용이 궁금하다면 Boxfit 공식 문서를 확인해주세요.
Route
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailPage(
tag: 'hero-image-2',
imageUrl: 'https://picsum.photos/250?image=10',
description: 'Image 2 Description',
);
}));
Flutter에서 기본으로 제공하는 라우트를 이동하는 방법입니다. Navigator.push를 통해 위젯을 스택에 쌓아서 화면으로 보여줍니다. 반환값은 우리가 정의했던 DetailPage로 넘어갈 수 있게 반환하고 인자로 tag, url, description을 전달해 줍니다. 원하는 사진을 눌렀을 때 GestureDetector class의 onTap으로 함수를 실행할 수 있는데 이 때, 각각의 이미지 tag에 대한 값을 전달해줄 수 있습니다.
이를 통해 DetailPage에서는 다시 Hero 위젯을 사용하여 전달받은 파라미터를 사용하여 눌렀던 이미지 내용과 동일한 tag를 사용해서 표현할 수 있습니다.
Behind the scenes
Hero 위젯은 어떻게 동작하는 걸까요? 한 번 Hero 공식 문서의 내용을 그대로 설명해보겠습니다.

Hero를 실행하기 전입니다. 소스 지점은 위젯이 표시되어 있고 목적 지점은 아직 존재하지 않습니다. 여기 Overlay 라는 위젯이 표시되는데요.
Overlay는 Flutter에서 위젯 트리를 통해 여러 위젯을 쌓아 올리는 데 사용되는 위젯입니다. 이는 일반적으로 앱의 위젯 계층 구조에서 가장 상위에 위치하며, 위젯을 임시로 표시하거나 특정 애니메이션 효과를 적용하는 데 사용합니다. Hero 애니메이션에서는 오버레이를 사용하여 애니메이션 중에 히어로 위젯을 화면 위에 떠있는 것처럼 보이게 합니다. 설명이 어렵다면 어떤 위젯을 화면에 표시하기 전에 아무것도 없는 장판같은 프레임이라고 봐도 무방합니다.

Navigator를 통해 라우트를 눌렀을 때 입니다. 여기서 t는 누르는 시점에서 초기부분이라는 것을 0에서 1로 표시합니다.
Dest Hero는 우리의 코드로 치면 DetailPage에 나와야 할 Hero 위젯을 우선 Overlay위에 놓습니다. Dest Route 부분은 아직 랜딩이 안됐네요.

이제 Hero 위젯은 Dest Route에 설정했던 Hero 위젯의 위치로 날아갑니다. Hero의 직사각형 경계는 Hero's createRectTween 속성에 지정된 Tween<Rect>를 사용하여 애니메이션화됩니다. 기본적으로 Flutter는 직사각형의 반대쪽 모서리를 곡선 경로를 따라 애니메이션화하는 MaterialRectArcTween 인스턴스를 사용합니다.

비행이 완료되면 Flutter는 Hero 위젯을 Overlay에서 목적지 경로로 이동합니다. 이제 Overlay는 비어 있습니다.
Dest Route의 마지막 위치에 Dest Hero가 나타납니다.
'Software Framework > Flutter' 카테고리의 다른 글
| [Flutter::Widget] Bottom Menu Dropdown 만들어보자. (0) | 2024.07.27 |
|---|---|
| [Flutter::Widget] 채팅 UI를 만들어 봅시다! (0) | 2024.07.25 |
| [Flutter::Animation] Fade 기법 (1) | 2024.07.25 |
| [Flutter::Animation] implicits animations (ImplicitlyAnimatedWidget) (2) | 2024.07.24 |
| [Flutter::Animation] Ticker 이해 (0) | 2024.07.23 |