📖 오늘의 학습
국제화나 다크모드, 라이트모드 적용은 프로젝트가 시작할 당시 모두 세팅을 해두어서 이제부터는 피쳐 개발을 진행하면 된다. 그래서 오늘은 간단하게 회원가입 페이지를 마무리했다.
회원가입 페이지의 마지막에 있는 이 체크 이미지에 대한 명세에는 애니메이션이 없었다. 그런데 그냥 이렇게만 있으면 상당히 밋밋하기도 하고 디자이너도 여기에 큰 의도를 담지 않았기에 내 마음대로 애니메이션을 넣기로 했다. ▼
가운데의 이미지는 그대로 두고 주변을 도는 점들이 확 퍼지면서 주위를 천천히 돌게 했으면 하는게 내가 원하는 애니메이션이다. Flutter에는 두 가지 종류의 애니메이션이 있는데 하나는 암묵적, 다른 하나는 명시적이다. 지금 이 애니메이션 같은 경우에는 움직임의 경로에 따른 움직임이 나타나야 하기에 명시적으로 애니메이션을 만들기로 했다. 색상, 길이 정도가 바뀌는건 암묵적으로 충분하지만 그 외에는 명시적으로 개발해주어야 한다. 어디서부터 어디까지 움직일지를 설정해준다는 의미다.
그래서 중앙점으로부터 궤도까지 움직이기와 궤도를 돌기를 선언해줬고, 그 코드는 아래와 같다. ▼
import 'dart:math';
import 'package:flutter/material.dart';
class ExpandingRotatingWidget extends StatefulWidget {
final double size; // 전체 컨테이너 크기
final double radius; // 궤도 반지름
final Duration movingDuration; // 중심에서 궤도로 이동 시간
final Duration rotationDuration; // 한 바퀴 회전 시간
final Widget child; // 회전할 위젯
final double initialAngle; // 회전 시작 각도 (라디안)
const ExpandingRotatingWidget({
required this.size,
required this.radius,
required this.child,
required this.movingDuration,
required this.rotationDuration,
this.initialAngle = 0, // 기본값: 0 라디안 (3시 방향)
super.key,
});
@override
ExpandingRotatingWidgetState createState() => ExpandingRotatingWidgetState();
}
class ExpandingRotatingWidgetState extends State<ExpandingRotatingWidget>
with TickerProviderStateMixin {
late final AnimationController _moveController;
late final AnimationController _rotateController;
late final Animation<double> _radiusAnimation;
@override
void initState() {
super.initState();
// 중심에서 궤도로 이동하는 애니메이션
_moveController = AnimationController(
vsync: this,
duration: widget.movingDuration,
);
_radiusAnimation = Tween<double>(
begin: 0,
end: widget.radius,
).animate(CurvedAnimation(
parent: _moveController,
curve: Curves.easeOut,
));
// 궤도에서 회전하는 애니메이션
_rotateController = AnimationController(
vsync: this,
duration: widget.rotationDuration,
)..repeat();
// 이동 애니메이션 시작
_moveController.forward();
}
@override
void dispose() {
_moveController.dispose();
_rotateController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: widget.size,
height: widget.size,
color: Colors.transparent,
child: AnimatedBuilder(
animation: Listenable.merge([_moveController, _rotateController]),
builder: (context, child) {
// 회전 각도와 반지름 계산
final angle = _rotateController.value * 2 * pi +
widget.initialAngle * pi / 180;
final radius = _radiusAnimation.value;
// x, y 좌표 계산
final offsetX = radius * cos(angle);
final offsetY = radius * sin(angle);
return Transform.translate(
offset: Offset(offsetX, offsetY),
child: child,
);
},
child: widget.child,
),
),
);
}
}
이 코드는 사실 점 하나를 위젯으로 만든 것이다. 이렇게 각 점들의 크기와 궤도로 올라가는 속도, 궤도를 움직이는 속도, 궤도 반지름 등등을 따로따로 선언할 수 있으면 위의 사진과 같은 점들을 내가 원하는 위치에 배치하여 구현할 수 있다.
그 이후에는 `Stack`위젯으로 점 위젯들을 겹치게 그리게 했다. 그런데 각 위젯들을 일일히 전부 작성하면 코드가 너무나도 길어지고 지저분해지기에 모델을 설정한 뒤, 해당 모델의 값을 받아와서 그리게 하여 코드를 좀 더 가독성 있게 해줬다. ▼
import 'package:flutter/material.dart';
import 'package:many_app/core/values/app_color.dart';
import 'package:many_app/core/values/image.dart';
import 'package:many_app/presentation/routes/onboarding/sign_up_done/widget/expanding_rotating_circle.dart';
class CircleModel {
final double size;
final double radius;
final double initialAngle;
const CircleModel({
required this.size,
required this.radius,
required this.initialAngle,
});
}
const List<CircleModel> circleList = [
CircleModel(size: 13, radius: 75, initialAngle: 230),
CircleModel(size: 3, radius: 65, initialAngle: 280),
CircleModel(size: 9, radius: 70, initialAngle: 315),
CircleModel(size: 2.3, radius: 50, initialAngle: 10),
CircleModel(size: 5, radius: 60, initialAngle: 50),
CircleModel(size: 2, radius: 55, initialAngle: 80),
CircleModel(size: 5, radius: 65, initialAngle: 110),
CircleModel(size: 7, radius: 65, initialAngle: 160),
CircleModel(size: 2, radius: 65, initialAngle: 190),
];
class RotatingLogo extends StatelessWidget {
const RotatingLogo({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 200,
height: 200,
child: Stack(
children: [
...circleList.map((circle) {
return ExpandingRotatingWidget(
size: circle.size,
radius: circle.radius,
movingDuration: const Duration(milliseconds: 500),
rotationDuration: const Duration(seconds: 90),
initialAngle: circle.initialAngle,
child: Container(
width: circle.size,
height: circle.size,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: AppColor.main900,
),
),
);
}),
Positioned(
child: Center(
child: Image.asset(
AppImage.done,
width: 90,
height: 90,
),
),
),
],
),
);
}
}
이렇게 만든 뒤, 원하는 위치에 배치하여 해당 페이지로 갈 때 마다 퍼지는 애니메이션을 만들었다.
🤔 오늘의 회고
애니메이션 만들기가 상당히 어렵다. 왜 다들 gif를 넣거나 동영상을 넣는지 좀 확실히 알았다. 시간이 좀 여유로우면 최적화를 위해 할 법 한데, 그게 아니면 약간의 성능을 포기하고 기능 개발을 완료하는게 좀 더 나은거 같다.
오늘은 PS이야기가 전혀 없는데, 그 이유는 이번 문제가 어제 문제의 완벽한 재탕이기 때문이다. 내 코드에서 달라진게 거의 없다. 거의 동일한 문제를 줘서 배운점도 없고 느낀 점도 없다.
'TIL' 카테고리의 다른 글
[TIL] 99클럽 코테 스터디 30일차 TIL : 팀을 위한 RestAPI 규칙 (0) | 2024.11.26 |
---|---|
[TIL] 99클럽 코테 스터디 29일차 TIL : View 레벨 상태관리 (0) | 2024.11.25 |
[TIL] 99클럽 코테 스터디 27일차 TIL : 다이나믹 프로그래밍2 (0) | 2024.11.23 |
[TIL] 99클럽 코테 스터디 26일차 TIL : 다이나믹 프로그래밍 (0) | 2024.11.23 |
[TIL] 99클럽 코테 스터디 25일차 TIL : 완전탐색, 우선 순위 큐 (0) | 2024.11.21 |