티스토리 뷰
개요
해당 사진은 운동시작 버튼을 눌렀을 때 출력할 운동화면들이다.
항목1 > 휴식 페이지 > 항목2 > 휴식 페이지 > (반복) > 완료 페이지 순으로 화면이 라우팅 된다.
항목1과 항목2 운동페이지는 운동항목 설정 다이얼로그에서 설정한 종목으로 화면이 출력된다.
운동파트 구현
라우팅 설정
exercise폴더에 필요한 파일들을 만들어주고
class ExerciseScreen1 extends StatefulWidget {
const ExerciseScreen1(
{super.key,
required this.exerciseController,
required this.homeController});
final ExerciseController exerciseController;
final HomeController homeController;
static const routeName = '/exercise1';
@override
State<ExerciseScreen1> createState() => _ExerciseScreen1State();
}
각각의 screen파일에서 받아올 컨트롤러와 루트네임을 정의한다.
class _MyAppState extends State<MyApp> {
final settingController = SettingController();
final homeController = HomeController();
final exerciseController = ExerciseController();
app.dart에서 필요한 컨트롤러들의 인스턴스를 생성한다.
MaterialPageRoute<void> route(RouteSettings routeSettings) {
return MaterialPageRoute<void>(
settings: routeSettings,
builder: (BuildContext context) {
switch (routeSettings.name) {
// 다른 화면들 라우팅 설정
case ExerciseScreen1.routeName:
return ExerciseScreen1(
exerciseController: exerciseController,
homeController: homeController);
case ExerciseScreen2.routeName:
return ExerciseScreen2(
exerciseController: exerciseController,
homeController: homeController);
case TimerScreen.routeName:
return TimerScreen(exerciseController: exerciseController);
case CompleteScreen.routeName:
return CompleteScreen(
exerciseController: exerciseController,
homeController: homeController);
default:
return const Text('Error');
}
});
}
route함수에서 만들어둔 화면의 라우팅 설정을 진행한다.
뒤로가기 막기
실수로 뒤로가기를 눌러 운동기록이 꼬이는 일이 없도록
운동 중에는 뒤로가기를 막아둘 것이다.
leading: widget.isHome
? // 로고 출력
: widget.isExercise
? GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
return homeDialog();
});
},
child: const Padding(
padding: EdgeInsets.fromLTRB(18, 12, 24, 12),
child: CommonSvg(
src: 'asset/svg/home.svg',
),
),
)
: // 뒤로가기 버튼 출력
CommonAppBar에서 isExercise프로퍼티를 추가하고 기본값은 false로 설정한다.
모든 운동화면의 앱바에서 isExercise값을 true로 줌으로써 좌측에 홈 아이콘을 띄운다.
운동항목을 잘못 설정했거나 운동을 도중에 멈춰야 하는 일이 있을 때를 위해
홈 아이콘을 누르면 다이얼로그를 띄워서 주의 사항을 알려준 뒤 홈화면으로 라우팅을 할 것이다.
Widget homeDialog() {
return CommonDialog(
dialogHeight: 140,
dialogPadding: 60,
title: '홈화면으로 이동',
titleSpacing: 18,
explain: '작성한 운동기록이 초기화됩니다.\n홈 화면으로 이동하시겠습니까?',
body: const SizedBox.shrink(),
buttonText: '이동',
buttonHeight: 38,
onPressed: () => Navigator.pushNamedAndRemoveUntil(
context, HomeScreen.routeName, (route) => false));
}
만들어둔 CommonDialog를 활용해 다이얼로그 위젯을 제작한다.
Widget _buildPage(BuildContext context) {
return PopScope(
canPop: false,
child: // 화면 출력
모든 화면 위젯을 PopScope로 감싼다음 canPop을 false로 주면
뒤로가기 버튼을 작동하는 것을 막을 수 있다.
운동화면에서 쓰이는 변수들
List<Map<String, dynamic>> dataList = [];
void initDataList() {
dataList = SharedPreferencesUtil.getJsonList('dataList');
}
int set = 1;
String num1 = '';
String num2 = '';
List<int> ex1 = [];
List<int> ex2 = [];
ExerciseController에서 해당 변수들을 선언한다.
홈화면과 동일하게 initState에서 dataList를 초기화 시킨다.
그 밑에 선언해둔 변수들은 어디 쓰이는지 나중에 설명하겠다.
운동화면 1
Text('${widget.exerciseController.set}/6 세트')
인자로 받은 exerciseController의 set값을 이용해 현재 진행상황을 출력한다.
Widget returnSvg() {
if (widget.homeController.isSelected1 == true) {
return SvgPicture.asset('asset/svg/pull_up_color.svg');
} else {
return SvgPicture.asset('asset/svg/chin_up_color.svg');
}
}
Widget returnCategoryName() {
if (widget.homeController.isSelected1 == true) {
return const Text('풀업', style: TextStyles.h1Medium);
} else {
return const Text('친업', style: TextStyles.h1Medium);
}
}
홈컨트롤러에서 받아온 운동항목 설정값에 따라
Svg와 텍스트 위젯을 반환하는 함수를 맨 아래 작성해준다.
CommonTextField(
onChanged: (value) {
setState(() {
_controller.num1 = value;
});
},
textInputType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
hintText: '몇 개를 하셨나요?',
),
ExerciseController의 num1을 입력한 값으로 초기화해준다.
텍스트필드를 활성화 화면 숫자 키보드가 표시되고 입력은 숫자만 허용한다.
CommonButton(
text: '완료',
onPressed: _controller.num1.isNotEmpty
? () {
widget.exerciseController.ex1
.add(int.parse(_controller.num1));
widget.exerciseController.set++;
Navigator.pushNamed(
context, TimerScreen.routeName);
}
: null),
num1은 해당 세트 기록한 개수에 해당하고
ex1은 운동1의 운동기록을 담을 리스트에 해당한다.
개수를 입력했을 때 버튼을 활성화시키고 버튼을 누르면
ex1에 입력한 개수를 더하고, set값을 1 증가시킨다.
휴식 화면
Text((7 - widget.exerciseController.set).toString())
운동 완료까지 현재 몇 세트가 남았는지 출력해준다.
int seconds = 120;
void moveScreen(context, set) {
Timer.periodic(const Duration(seconds: 1), (Timer timer) {
if (seconds == 0) {
timer.cancel();
if (set % 2 != 0) {
Navigator.pushNamed(context, ExerciseScreen1.routeName);
} else {
Navigator.pushNamed(context, ExerciseScreen2.routeName);
}
} else {
seconds--;
notifyListeners();
}
});
}
화면 이동 함수를 initState에서 실행시켜준다.
휴식시간은 2분으로 설정해주었고 시간이 다되면 타이머를 캔슬시킨다음
운동화면에서 넘겨받은 set값이 짝수면 '운동화면2'로 홀수면 '운동화면1'로 이동한다.
seconds값의 변경을 화면에 즉각적으로 반영하기 위해 noitifyListeners()를 호출해준다.
String formatTime(int second) {
int minutes = second ~/ 60;
int seconds = second % 60;
return '$minutes:${seconds.toString().padLeft(2, '0')}';
}
남은 시간을 포맷팅해서 반환하는 함수이다.
'~/' 는 나눗셈 결과를 정수 형태로 반환한다.
운동화면 2
CommonButton(
text: '완료',
onPressed: _controller.num2.isNotEmpty
? () {
widget.exerciseController.ex2.add(int.parse(_controller.num2));
if (widget.exerciseController.set == 6) {
Navigator.pushNamed(
context, CompleteScreen.routeName);
} else {
widget.exerciseController.set++;
Navigator.pushNamed(
context, TimerScreen.routeName);
}
}
: null),
'운동화면1'과 전반적인 로직은 동일하고 버튼만 좀 달라진다.
'운동화면2'에서 set는 짝수값을 가지므로 set가 6이라면 (해당 화면이 마지막 세트에 해당한다면)
완료화면으로 이동하고 아니라면 휴식 화면으로 이동한다.
완료 화면
void addData(data) {
dataList.add(data);
SharedPreferencesUtil.setJsonList('dataList', dataList);
}
데이터 추가 함수를 컨트롤러에 추가해준다.
Padding(
padding: const EdgeInsets.fromLTRB(30, 40, 30, 25),
child: CommonButton(
text: '홈으로',
onPressed: () {
_controller.addData({
'time': DateTime.now().toString(),
'day': _controller.dataList.length + 1,
'ex1_name': widget.homeController.isSelected1 == true ? '풀업' : '친업',
'ex2_name':
widget.homeController.isSelected3 == true ? '푸쉬업' : '너클 푸쉬업',
'ex1': widget.exerciseController.ex1,
'ex2': widget.exerciseController.ex2,
});
Navigator.pushNamedAndRemoveUntil(
context, HomeScreen.routeName, (route) => false);
},
),
);
운동 완료화면의 버튼을 누르면 데이터리스트에 데이터를 추가하고,
모든 스택을 제거한 뒤 홈화면으로 이동한다.
운동기록 초기화
void initExData(){
set = 1;
ex1 = [];
ex2 = [];
}
이 변수들은 해당 변수들이 선언된 컨트롤러를 인자값으로 넘겨받음으로써 상태관리를 하기 때문에
홈화면의 initState에서 이 함수를 실행시켜 운동기록을 초기값으로 초기화해줘야 한다.
테스트
테스트를 위해 휴식시간을 임시적으로 3초로 설정해놨다.
의도한 대로 라우팅이 이루어지고 데이터가 알맞게 추가된 모습이다.
깃허브
GitHub - hamond12/EscapeAnchovy: 운동일지 앱
운동일지 앱. Contribute to hamond12/EscapeAnchovy development by creating an account on GitHub.
github.com
'멸치탈출 > 플러터 버전' 카테고리의 다른 글
[플러터] 운동항목 설정 다이얼로그 구현 (0) | 2024.05.15 |
---|---|
[플러터] 운동 기록이 없으면 1주일 후 일지를 초기화 시키는 타이머 구현 (0) | 2024.05.12 |
[플러터] 전체 일지 확인 화면 구현 (운동 종목 별로 전날 대비 개수 출력) (0) | 2024.05.06 |
[플러터] ListView를 활용한 운동기록 출력 (json 리스트 데이터 활용) (0) | 2024.04.30 |
[플러터] 홈 화면 그리기 & 다이얼로그 사용법 (1) | 2024.04.21 |