티스토리 뷰
개요
운동항목은 풀업, 친업, 푸쉬업, 너클 푸쉬업 이렇게 총 4개이다.
풀업과 친업은 항목1에 해당하고 푸쉬업과 너클 푸쉬업은 항목2에 해당한다.
원래는 운동항목 설정 없이 풀업과 푸쉬업 기록만 추가하려고 했는데
그립을 살짝 바꿔서 다른 부분도 단련할 수 있는 종목도 추가하면 좋겠단 생각이 들어
총 4개의 항목을 중 2개의 항목을 선택하는 다이얼로그를 구현할 것이다.
운동항목 설정 다이얼로그 구현
CommonDialog
import 'package:escape_anchovy/res/text/colors.dart';
import 'package:escape_anchovy/res/text/styles.dart';
import 'package:escape_anchovy/src/common/common_button.dart';
import 'package:flutter/material.dart';
class CommonDialog extends StatefulWidget {
const CommonDialog(
{super.key,
this.dialogPadding = 30,
this.dialogHeight = 300,
this.topPadding = 18,
required this.title,
this.titleSpacing = 2,
required this.explain,
this.bodySpacing = 20,
required this.body,
this.buttonHeight = 42,
required this.onPressed});
final double dialogPadding;
final double dialogHeight;
final double topPadding;
final String title;
final double titleSpacing;
final String explain;
final double bodySpacing;
final Widget body;
final double buttonHeight;
final void Function()? onPressed;
@override
State<CommonDialog> createState() => _CommonDialogState();
}
class _CommonDialogState extends State<CommonDialog> {
@override
Widget build(BuildContext context) {
return Dialog(
insetPadding: EdgeInsets.symmetric(horizontal: widget.dialogPadding),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(15.0),
),
),
child: Padding(
padding: EdgeInsets.only(top: widget.topPadding),
child: SizedBox(
height: widget.dialogHeight,
child: Column(
children: [
Expanded(
child: Column(
children: [
Text(widget.title, style: TextStyles.b2Medium),
SizedBox(
height: widget.titleSpacing,
),
Text(
widget.explain,
style: TextStyles.b4Regular.copyWith(
color: context.isLight
? LightModeColors.dark3
: DarkModeColors.dark3),
),
SizedBox(
height: widget.bodySpacing,
),
widget.body
],
)),
CommonButton(
text: '완료',
height: widget.buttonHeight,
onPressed: widget.onPressed,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0),
),
),
],
),
),
));
}
}
지금 구현할 운동항목 설정 다이얼로그 말고도 많은 곳에서 Dialog 위젯이 쓰일 것이기 때문에
공통적으로 사용할 수 있는 Dialog 양식을 작성한다.
다이얼로그는 크게 title, explain, body 이렇게 3파트로 나뉘어져있고,
파트별 패딩값은 해당 위젯에서 지정이 되있으며 프로퍼티를 통해 값을 바꿀 수도 있다.
다이얼로그 하단에 위치한 '완료'버튼은 누르면 해당 다이얼로그에서
한 설정을 저장하고 다이얼로그를 끄는 역할을 한다.
운동항목 설정 다이얼로그 구현
다이얼로그 폴더에 해당 다이얼로그 파일 추가
// 운동항목 다이얼로그 관련
late bool isSelected1; // 풀업
late bool isSelected2; // 친업
late bool isSelected3; // 푸쉬업
late bool isSelected4; // 너클 푸쉬업
void initCategory() {
isSelected1 = SharedPreferencesUtil.getBool('category1') ?? true;
isSelected2 = SharedPreferencesUtil.getBool('category2') ?? false;
isSelected3 = SharedPreferencesUtil.getBool('category3') ?? true;
isSelected4 = SharedPreferencesUtil.getBool('category4') ?? false;
notifyListeners();
}
void saveCategory(bool c1, bool c2, bool c3, bool c4) {
SharedPreferencesUtil.setBool('category1', c1);
SharedPreferencesUtil.setBool('category2', c2);
SharedPreferencesUtil.setBool('category3', c3);
SharedPreferencesUtil.setBool('category4', c4);
}
HomeController에 운동항목 다이얼로그를 구현하는데 필요한 것들을 선언해준다.
isSelected들을 late로 선언한 까닭은 해당 변수들이 제데로 초기화 되지 않았을 때
null 오류를 띄움으로써 초기화 여부를 확인할 수 있기 때문이다.
isSelected는 각각의 항목의 선택여부에 해당하는 bool형 변수이다.
initCategory()는 운동항목들의 선택여부를 초기화한다.
운동항목을 설정하지 않았다면 풀업과 푸쉬업을 기본적으로 선택하도록 한다.
saveCategory는 다이얼로그에서 초기화된 선택값을 받아
각각의 키에 선택값을 저장하는 함수이다.
static bool? getBool(String key, {bool? defValue = false}) {
return _prefs?.getBool(key) ?? defValue;
}
getBool함수는 키값에 null일 때 false를 반환하기 때문에
initCategory()가 제데로 작동하지 않는다.
static bool? getBool(String key) {
return _prefs?.getBool(key);
}
생각해보니까 굳이 defValue가 필요없는 거 같아서 지워줬다.
class HomeScreen extends StatefulWidget {
const HomeScreen(
{super.key,
required this.settingController,
required this.homeController});
static const routeName = '/home';
final SettingController settingController;
final HomeController homeController;
홈화면에서 홈 컨트롤러를 인자로 받아오도록 한다.
case HomeScreen.routeName:
return HomeScreen(
settingController: settingController,
homeController: homeController);
app.dart에서의 홈화면 라우팅 설정
widget.homeController.initCategory();
HomeScreen의 initState에서 해당 함수를 실행시킨다.
onTap: () {
showDialog(
context: context,
builder: (BuildContext context) => ExCategoryDialog(
homeController: widget.homecontroller,
));
},
홈화면에서 운동항목 설정 이미지를 누르면 다이얼로그를 활성화하도록 한다.
해당 다이얼로그에서 HomeScreen 위젯의 속성으로 정의된 HomeController를
넘겨줌으로써 홈에서 초기화한 운동항목 선택여부를 해당 다이얼로그에 적용시킨다.
class ExCategoryDialog extends StatefulWidget {
const ExCategoryDialog({super.key, required this.homeController});
final HomeController homeController;
@override
State<StatefulWidget> createState() {
return _ExCategoryDialogState();
}
}
class _ExCategoryDialogState extends State<ExCategoryDialog> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: widget.homeController,
builder: (context, snapshot) {
return CommonDialog(
title: '운동항목 설정',
explain: '각 항목에서 운동을 하나씩 선택해주세요',
body: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Text(
'항목1',
style: TextStyles.b1Regular.copyWith(
color: context.isLight
? LightModeColors.dark2
: DarkModeColors.dark2),
),
),
const SizedBox(
width: 28,
),
GestureDetector(
onTap: () {
setState(() {
widget.homeController.isSelected1 = true;
widget.homeController.isSelected2 = false;
});
},
child: Column(
children: [
const CommonSvg(src: 'asset/svg/pull_up.svg'),
const SizedBox(
height: 4,
),
Text(
'풀업',
style: TextStyles.b3Regular.copyWith(
color: widget.homeController.isSelected1
? (context.isLight
? LightModeColors.green
: DarkModeColors.green)
: (context.isLight
? DarkModeColors.background
: LightModeColors.background)),
),
],
),
),
const SizedBox(
width: 36,
),
GestureDetector(
onTap: () {
setState(() {
widget.homeController.isSelected1 = false;
widget.homeController.isSelected2 = true;
});
},
child: Column(
children: [
const CommonSvg(src: 'asset/svg/chin_up.svg'),
const SizedBox(
height: 4,
),
Text(
'친업',
style: TextStyles.b3Regular.copyWith(
color: widget.homeController.isSelected2
? (context.isLight
? LightModeColors.green
: DarkModeColors.green)
: (context.isLight
? DarkModeColors.background
: LightModeColors.background)),
),
],
),
),
],
),
const SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Text(
'항목2',
style: TextStyles.b1Regular.copyWith(
color: context.isLight
? LightModeColors.dark2
: DarkModeColors.dark2),
),
),
const SizedBox(
width: 24,
),
GestureDetector(
onTap: () {
setState(() {
widget.homeController.isSelected3 = true;
widget.homeController.isSelected4 = false;
});
},
child: Column(
children: [
const CommonSvg(src: 'asset/svg/push_up.svg'),
const SizedBox(
height: 4,
),
Text(
'푸쉬업',
style: TextStyles.b3Regular.copyWith(
color: widget.homeController.isSelected3
? (context.isLight
? LightModeColors.green
: DarkModeColors.green)
: (context.isLight
? DarkModeColors.background
: LightModeColors.background)),
),
],
),
),
const SizedBox(
width: 20,
),
GestureDetector(
onTap: () {
setState(() {
widget.homeController.isSelected3 = false;
widget.homeController.isSelected4 = true;
});
},
child: Column(
children: [
const CommonSvg(src: 'asset/svg/nuckle_push_up.svg'),
const SizedBox(
height: 4,
),
Text(
'너클 푸쉬업',
style: TextStyles.b3Regular.copyWith(
color: widget.homeController.isSelected4
? (context.isLight
? LightModeColors.green
: DarkModeColors.green)
: (context.isLight
? DarkModeColors.background
: LightModeColors.background)),
),
],
),
),
],
)
],
),
buttonHeight: 42,
onPressed: () {
widget.homeController.saveCategory(
widget.homeController.isSelected1,
widget.homeController.isSelected2,
widget.homeController.isSelected3,
widget.homeController.isSelected4);
Navigator.pop(context);
},
);
},
);
}
}
다이얼로그 내에서 위젯의 상태를 변환하려면
StatefulWidget으로 선언한 다음 Dialog 위젯을 반환하도록 하면 된다.
선택된 운동항목의 글자 색은 초록색으로 변하도록 설정했고,
항목별로 하나의 운동종목만 설정할 수 있도록 하였다.
확인버튼을 누르면 설정한 운동종목의 선택여부를 키에 저장하게 된다.
테스트
다음 시간에 구현할 운동화면에선
운동종목 선택값(isSelected)에 따라 다른 화면을 출력하게 된다.
'멸치탈출 (플러터)' 카테고리의 다른 글
[플러터] 운동파트 구현 (0) | 2024.05.18 |
---|---|
[플러터] 운동 기록이 없으면 1주일 후 일지를 초기화 시키는 타이머 구현 (0) | 2024.05.12 |
[플러터] 전체 일지 확인 화면 구현 (운동 종목 별로 전날 대비 개수 출력) (0) | 2024.05.06 |
[플러터] ListView를 활용한 운동기록 출력 (json 리스트 데이터 활용) (0) | 2024.04.30 |
[플러터] 홈 화면 그리기 & 다이얼로그 사용법 (1) | 2024.04.21 |