이번 글에서는 인스타그램 등의 SNS에서 좋아요 하트 아이콘을 클릭 했을 때 처럼 아이콘에 애니메이션 처리를 하는 방법에 대해서 작성하려고 한다.
애니메이션 처리를 위해 AnimatedSwitcher 위젯을 활용하였고, 아래에서 예시로 만든 방법 외에도 다른 방법으로도 충분히 처리하거나 커스텀으로 만들어서 사용할 수도 있다.
여기서 상태관리는 Provider를 사용하기에 Provider를 등록을 해주었다.
아래 snsUiHeartComponent 위젯을 메소드로 만들어서 다른 부분에서도 공통으로 사용할 수 있게 만들어 주었다.
UI는 인스타그램의 폼과 비슷한 구조로 간단하게 만들었다.
return ChangeNotifierProvider<SnsUIHeartProvider>(
create: (_) => SnsUIHeartProvider(),
child: Consumer<SnsUIHeartProvider>(builder: (context, state, child) {
return Scaffold(
appBar: appBar(title: 'SNS Heart Icon'),
body: snsUIHeartComponent(
context: context,
isHeart: state.isHeart,
imageIndex: 204,
onHeartTap: () => state.onHeartTap()));
}),
);
공통 위젯을 살펴보면 아래 AnimatedSwitcher 위젯이 사용되고 있는 부분이 좋아요 아이콘을 처리하는 UI 부분이다.
AnimatedSwitcher위젯은 duration을 필수 값으로 넣어야 하며, duration은 애니메이션이 작동되는 시간을 설정하는 부분이다.
transitionBuilder에서 좋아요 아이콘을 작아졌다가 다시 커지게 하는 애니메이션을 처리해야 하므로 ScaleTransition을 사용하여 이러한 기능을 구현할 수 있다.
child 부분에 Icon 위젯이 두 개가 있는데, isHeart 불리언 변수에 따라 아이콘 위젯을 서로 바꿔주면서 처리를 하고 있다.
Flutter에서는 같은 위젯을 자식 위젯으로 넣게되면 다른 위젯으로 인식을 하지 못하기에 애니메이션 처리가 되지 않는다. 이럴 때 위젯에 unique key 값을 주어 해당 위젯이 서로 다른 위젯이라고 인식하게 할 수 있다.
Column snsUIHeartComponent({
required BuildContext context,
required bool isHeart,
required Function() onHeartTap,
bool isShowHeart = false,
required int imageIndex,
}) {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
Container(
width: 35,
height: 35,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50), color: Colors.white),
child: Padding(
padding: const EdgeInsets.all(1.5),
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.network(
'https://picsum.photos/id/$imageIndex/400/400',
),
),
),
),
const SizedBox(width: 8),
const Text(
'snsaccount',
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
),
GestureDetector(
onDoubleTap: onHeartTap,
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width,
child: Stack(
children: [
Image.network(
'https://picsum.photos/id/$imageIndex/400/400',
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width,
),
],
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Wrap(
children: [
GestureDetector(
onTap: onHeartTap,
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: ((child, animation) {
return ScaleTransition(
scale: animation,
child: child,
);
}),
child: !isHeart
? const Icon(
key: ValueKey('UN_FAVORITE'),
Icons.favorite_border_outlined,
size: 30,
)
: const Icon(
key: ValueKey('FAVORITE'),
Icons.favorite_outlined,
color: Colors.red,
size: 30),
),
),
const SizedBox(width: 16),
const Icon(
Icons.chat_bubble_outline,
size: 30,
),
],
),
const Icon(
Icons.bookmark_border_outlined,
size: 30,
),
],
),
)
],
);
}
해당 기능은 매우 간단한 기능이라 stateful 또는 ValueNotifier로도 구현해도 되지만 그냥 provider로 만들었다.
isHeart라는 불리언 변수를 선언해주고 onHeartTap()의 이벤트가 발생할 때마다 불리언 값을 변경해준뒤 notifyListeners()로 UI 변경 알림을 전달해 주면 된다.
bool isHeart = false;
void onHeartTap() {
isHeart = !isHeart;
notifyListeners();
}
https://github.com/boglbbogl/flutter_velog_sample/tree/main/lib/ui/heart_motion
해당 기능은 간단한 기능이라 Git repository에서 코드만 복사해서 바로 실행해도 되며, AnimatedSwitcher 위젯의 trans 빌더에는 다양한 애니메이션 처리를 할 수 있는 형태의 기능이 많아서 다양하게 사용해 보면 될 것 같다.
다음 글에서는 이미지가 있는 부분을 더블 클릭했을 때 좋아요 모션을 처리하는 방법에 대해서 작성할 예정인데, 이번 글 내용과 거의 중복되는 내용이어서 한 곳에 작성하려다가 그냥 따로 작성하기로 하였다.