티스토리 뷰
홈 화면 레이아웃 그리기
그릴 화면
일단 그려야 할 홈 화면의 레이아웃이다.
라우팅은 버튼을 통해서 이루어지며,
도전과제 클리어 여부, 운동기록의 여부에 따라 다른 화면을 출력할 것이다.
단색으로 이루어진 아이콘이 아닌 경우
라이트모드와 다크모드에서 출력할 svg파일 2개를 프로젝트에 추가해줘야 한다.
common_outline_button.dart
import 'package:escape_anchovy/res/text/colors.dart';
import 'package:flutter/material.dart';
class CommonOutlineButton extends StatefulWidget {
const CommonOutlineButton(
{super.key,
this.width = double.maxFinite,
this.height = 50,
this.onPressed,
this.text = '',
this.textStyle,
this.textColor,
this.borderColor,
this.borderRadius = 10});
final double width;
final double height;
final void Function()? onPressed;
final String text;
final TextStyle? textStyle;
final Color? textColor;
final Color? borderColor;
final double borderRadius;
@override
State<CommonOutlineButton> createState() => _CommonOutlineButtonState();
}
class _CommonOutlineButtonState extends State<CommonOutlineButton> {
@override
Widget build(BuildContext context) {
return SizedBox(
width: widget.width,
height: widget.height,
child: OutlinedButton(
onPressed: widget.onPressed,
style: OutlinedButton.styleFrom(
side: BorderSide(
width: 1.0,
color: widget.borderColor ??
(context.isLight
? LightModeColors.blue
: DarkModeColors.blue)),
padding: const EdgeInsets.all(0.0),
foregroundColor: widget.textColor ??
(context.isLight ? LightModeColors.blue : DarkModeColors.blue),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(widget.borderRadius),
),
),
child: Text(widget.text, style: widget.textStyle)),
);
}
}
만들어둔 버튼 위젯이 있지만 홈 화면에서 사용되는 버튼들은
테두리만 있는 버튼이라 OutlinedButton을 반환하는
버튼 클래스를 하나 더 만들면 좋겠다는 생각이 들었다.
textColor와 borderColor 프로퍼티로 버튼의 색상을 컨트롤 할 수 있다.
home_screen.dart
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key, required this.settingController});
static const routeName = '/home';
final SettingController settingController;
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _controller = HomeController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CommonAppBar(
title: '메인화면',
isHome: true,
settingController: widget.settingController,
),
body: AnimatedBuilder(
animation: _controller,
builder: (context, snapshot) {
return _buildPage(context);
}),
);
}
Widget _buildPage(BuildContext context) {
return Column(
children: [
const SizedBox(
height: 24,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: Column(
children: [
Row(
children: [
Row(
children: [
SvgPicture.asset(context.isLight
? 'asset/svg/heart.svg'
: 'asset/svg/dark_heart.svg'),
const SizedBox(
width: 8,
),
Text(
'운동하기',
style: TextStyles.b1Medium.copyWith(
color: context.isLight
? DarkModeColors.background
: LightModeColors.background),
),
const SizedBox(
width: 4,
),
GestureDetector(
onTap: () {},
child: Text(
'(자세한 설명 보기)',
style: TextStyles.b3Regular.copyWith(
color: context.isLight
? LightModeColors.dark2
: DarkModeColors.dark2),
),
)
],
),
],
),
const SizedBox(
height: 20,
),
Center(
child: GestureDetector(
onTap: () {},
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SvgPicture.asset(context.isLight
? 'asset/svg/exercise_category.svg'
: 'asset/svg/dark_exercise_category.svg'),
const SizedBox(
height: 4,
),
Text(
'운동항목',
style: TextStyles.b1Regular.copyWith(
color: context.isLight
? DarkModeColors.background
: LightModeColors.background),
),
],
),
),
),
const SizedBox(
height: 16,
),
CommonOutlineButton(
width: 180,
height: 40,
onPressed: () {},
text: '운동시작',
textStyle: TextStyles.b1Medium,
borderRadius: 8,
),
],
),
),
divideSection(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Row(
children: [
SvgPicture.asset(context.isLight
? 'asset/svg/note.svg'
: 'asset/svg/dark_note.svg'),
const SizedBox(
width: 9,
),
Text(
'최근 일지 확인',
style: TextStyles.b1Medium.copyWith(
color: context.isLight
? DarkModeColors.background
: LightModeColors.background),
),
const SizedBox(
width: 4,
),
Text(
'(3일)',
style: TextStyles.b3Regular.copyWith(
color: context.isLight
? LightModeColors.dark2
: DarkModeColors.dark2),
)
],
),
),
CommonOutlineButton(
width: 65,
height: 18,
onPressed: () {},
text: '전체 일지 확인',
textStyle: TextStyles.caption2.copyWith(height: 0.01),
)
],
),
const SizedBox(
height: 7,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'당신의 성장과정을 살펴보세요.',
style: TextStyles.b3Regular.copyWith(
color: context.isLight
? LightModeColors.dark2
: DarkModeColors.dark2),
),
],
),
Center(
child: Column(
children: [
const SizedBox(
height: 18,
),
SvgPicture.asset(
'asset/svg/no_data.svg',
colorFilter: ColorFilter.mode(
context.isLight
? LightModeColors.dark3
: DarkModeColors.dark3,
BlendMode.srcIn),
),
const SizedBox(
height: 8,
),
Text(
'운동기록이 없습니다',
style: TextStyles.b2Medium.copyWith(
color: context.isLight
? LightModeColors.dark3
: DarkModeColors.dark3),
),
Text(
'운동시작을 눌러 일지를 작성해보세요',
style: TextStyles.b4Regular.copyWith(
color: context.isLight
? const Color(0XFFADA8B0)
: const Color(0XFF8A848D)),
),
],
),
)
],
),
),
divideSection(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Row(
children: [
SvgPicture.asset(context.isLight
? 'asset/svg/challenges.svg'
: 'asset/svg/dark_challenges.svg'),
const SizedBox(
width: 7,
),
Text(
'도전과제',
style: TextStyles.b1Medium.copyWith(
color: context.isLight
? DarkModeColors.background
: LightModeColors.background),
),
const SizedBox(
width: 8,
),
CommonOutlineButton(
width: 65,
height: 18,
onPressed: () {},
text: '전체 업적 확인',
textStyle: TextStyles.caption2.copyWith(height: 0.01),
textColor: context.isLight
? LightModeColors.yellow
: DarkModeColors.yellow,
borderColor: context.isLight
? LightModeColors.yellow
: DarkModeColors.yellow,
)
],
),
),
],
),
const SizedBox(
height: 7,
),
Text(
'운동을 완료하고 도전과제를 달성해보세요.',
style: TextStyles.b3Regular.copyWith(
color: context.isLight
? LightModeColors.dark2
: DarkModeColors.dark2),
),
const SizedBox(
height: 3,
),
Text.rich(TextSpan(children: [
TextSpan(
text: '고등어 ',
style: TextStyles.b3Regular.copyWith(
color: context.isLight
? LightModeColors.blue
: DarkModeColors.blue),
),
TextSpan(
text: '도전과제를 달성하면 무언가 바뀔수도?',
style: TextStyles.b3Regular.copyWith(
color: context.isLight
? LightModeColors.dark2
: DarkModeColors.dark2),
)
])),
],
),
),
],
);
}
Widget divideSection() {
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Container(
height: 8,
color:
context.isLight ? LightModeColors.dark4 : DarkModeColors.dark4,
),
),
],
);
}
}
context.isLight를 활용해 라이트모드와 다크모드일 때의 색상을 지정해준다.
구역을 나누는 컨테이너를 위젯을 따로 분류하여 코드 가독성을 높였다.
운동 설명 다이얼로그 추가
화면 폴더에 다이얼로그 폴더를 추가해
그 안에 '다이얼로그이름.dart' 파일을 추가한다.
explain_dialog.dart
class ExplainDialog {
static void showBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (context) => Container(
height: 300,
decoration: BoxDecoration(
color: context.isLight
? LightModeColors.background
: DarkModeColors.dark4,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
),
),
child: Column(children: [
const SizedBox(
height: 14,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(
width: 40, // svgSize(24) + svgPadding(12)
),
const Text(
'운동설명',
style: TextStyles.h2Bold,
),
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: const Icon(
Icons.close,
)),
)
],
),
const SizedBox(
height: 20,
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'운동방식',
style: TextStyles.b1Medium,
),
SizedBox(height: 4),
Text(
'운동시작을 누르면 2개의 종목을 번갈아가며 6세트를 진행합니다. 한 세마다 자신이 수행할 수 있는 최대의 개수를 수행해주세요. 오늘의 기록을 전날의 기록과 비교해서 볼 수 있습니다. 전날보다 1개씩 더하는 걸 목표로 삼아보세요!',
style: TextStyles.b4Regular,
),
SizedBox(height: 20),
Text(
'설정',
style: TextStyles.b1Medium,
),
SizedBox(height: 4),
Text(
'운동항목 아이콘을 눌러 2개의 항목을 선택해주세요. 항목당 하나의 운동만 선택할 수 있습니다. 휴식시간은 2분으로 설정되어 있습니다.',
style: TextStyles.b4Regular,
),
],
),
)
]),
));
}
}
클래스 내에서 static형의 다이얼로그 생성함수를 작성해
클래스의 인스턴스를 따로 생성할 필요없이 바로 클래스의 함수를 갖다 쓸 수 있도록 해준다.
showModalBottomSheet는 하단 다이얼로그를 생성하는 플러터의 내장함수이다.
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(
width: 40, // svgSize(24) + svgPadding(12)
),
const Text(
'운동설명',
style: TextStyles.h2Bold,
),
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: const Icon(
Icons.close,
)),
)
]
)
저번에 앱바를 구현했던 것처럼 글자와 아이콘을
한 줄에 배치할 때 글자를 중앙으로 주고싶다면
아이콘이 차지하는 width값과 반대쪽 width값을 같게 해주면 된다.
Text(
'운동시작을 누르면 2개의 종목을 번갈아가며 6세트를 진행합니다. 한 세마다 자신이 수행할 수 있는 최대의 개수를 수행해주세요. 오늘의 기록을 전날의 기록과 비교해서 볼 수 있습니다. 전날보다 1개씩 더하는 걸 목표로 삼아보세요!',
style: TextStyles.b4Regular,
),
Text 위젯은 Row나 Column안에 있는 상태에서
화면이 넘어가는 길이를 가지면 overflow가 나게 되지만
단일 객체로써 쓰게되면 화면을 넘어가는 시점에서 자동으로 개행이된다.
테스트
레이아웃 그리는 게 전부라 이번 파트는 별로 어려운 게 없었다.
'멸치탈출 > 플러터 버전' 카테고리의 다른 글
[플러터] 전체 일지 확인 화면 구현 (운동 종목 별로 전날 대비 개수 출력) (1) | 2024.05.06 |
---|---|
[플러터] ListView를 활용한 운동기록 출력 (json 리스트 데이터 활용) (0) | 2024.04.30 |
[플러터] 이름 입력 페이지 구현 (싱글톤 패턴을 활용한 shared_preferences 사용법) (1) | 2024.04.20 |
[플러터] 앱 테마 변경 기능 구현 (1) | 2024.04.19 |
[플러터] 스플래쉬 화면 구현 (이미지에 애니메이션 주기) (0) | 2024.04.13 |