티스토리 뷰
전체 일지 확인 화면 구현하기
라우팅 설정
좌측 버튼을 통해 전체 일지를 확인할 수 있는 페이지로 넘어가게 할 것이다.
CommonOutlineButton(
width: 65,
height: 18,
onPressed: () {
Navigator.pushNamed(context, NoteScreen.routeName);
},
text: '전체 일지 확인',
textStyle: TextStyles.caption2.copyWith(height: 0.01))
버튼을 눌렀을 때 해당 화면으로 라우팅 하도록 설정
데이터 불러오기
import 'package:escape_anchovy/src/util/shared_preferences_util.dart';
import 'package:flutter/material.dart';
class NoteController with ChangeNotifier {
List<Map<String, dynamic>> dataList = [];
void initDataList() {
dataList = SharedPreferencesUtil.getJsonList('dataList');
}
}
컨트롤러에 데이터를 불러오는 함수를 작성해준 뒤
final _controller = NoteController();
@override
void initState() {
super.initState();
_controller.initDataList();
}
노트화면에서 불러와준다.
예외처리
Widget _buildPage(BuildContext context) {
return _controller.dataList.isEmpty
? // 운동기록이 없을 때의 화면 출력
: // 운동기록 출력
운동기록이 없을 때 출력할 화면을 작성해준다.
홈 화면에서 한 작업과 동일하다.
운동종목별로 전날과 비교한 개수차이 출력하기
int sum(List list) {
return list.reduce((value, element) => value + element);
}
List exList(data, ex) {
return [data[ex][0], data[ex][1], data[ex][2]];
}
코드의 간결화를 위해 배열의 합을 반환해주는 sum함수와
해당 일자의 운동기록을 요소로 하는 배열을 반환하는 exList함수를 선언해줬다.
리스트뷰는 홈화면에서 사용한 것과 동일한 것을 사용했다.
List pullUpSumList = [];
List chinUpSumList = [];
List pushUpSumList = [];
List nucklePushUpSumList = [];
List pullUpList = [];
List chinUpList = [];
List pushUpList = [];
List nucklePushUpList = [];
각 종목별 운동 기록의 합계를 담을 '운동종목sumList' 들을 선언해준다.
이 4개의 배열은 운동종목별로 전날대비 개수차이를 출력하는데 쓰인다.
각 종목별 운동 기록을 담을 배열 '운동종목List' 들을 선언해준다.
이 4개의 배열은 해당 운동종목의 기록이 첫날과 같은지 비교하는데 쓰인다.
for (int i = 0; i <= index; i++) {
Map<String, dynamic> data = _controller.dataList[i];
if (data['ex1_name'] == '풀업') {
pullUpSumList.add(sum(data['ex1']));
pullUpList.add(exList(data, 'ex1'));
}
if (data['ex1_name'] == '친업') {
chinUpSumList.add(sum(data['ex1']));
chinUpList.add(exList(data, 'ex1'));
}
if (data['ex2_name'] == '푸쉬업') {
pushUpSumList.add(sum(data['ex2']));
pushUpList.add(exList(data, 'ex2'));
}
if (data['ex2_name'] == '너클 푸쉬업') {
nucklePushUpSumList.add(sum(data['ex2']));
nucklePushUpList.add(exList(data, 'ex2'));
}
}
데이터의 운동 종목 이름에 따라 선언한 배열에
해당하는 값을 추가해준다.
bool isSameList(List list1, List list2) {
for (int i = 0; i < list1.length; i++) {
if (list1[i] != list2[i]) {
return false;
}
}
return true;
}
bool checkNotFirstEx1() {
if (data['ex1_name'] == '풀업') {
return !isSameList(pullUpList[0], data['ex1']);
}
return !isSameList(chinUpList[0], data['ex1']);
}
bool checkNotFirstEx2() {
if (data['ex2_name'] == '푸쉬업') {
return !isSameList(pushUpList[0], data['ex2']);
}
return !isSameList(nucklePushUpList[0], data['ex2']);
}
isSameList는 오늘의 운동기록과 첫날의 운동기록이 완전히 일치하면 false를 반환하는 함수이다.
루틴을 제데로 수행했다면 다음날은 첫날과 같은 기록이 나올 수가 없기에 이런식으로 함수를 짰다.
해당 함수로 처음하는 운동종목을 감지해 전날 해당 운동종목의 운동기록이 없는데도
전날대비 개수차이가 출력되는 것을 방지할 수 있다.
int substarct(sumList) {
if (sumList.length > 1) {
return sumList[sumList.length - 1] - sumList[sumList.length - 2];
}
return 0;
}
int returnSubtract1() {
if (data['ex1_name'] == '풀업') {
return substarct(pullUpSumList);
}
if (data['ex1_name'] == '친업') {
return substarct(chinUpSumList);
} else {
return 0;
}
}
int returnSubtract2() {
if (data['ex2_name'] == '푸쉬업') {
return substarct(pushUpSumList);
}
if (data['ex2_name'] == '너클 푸쉬업') {
return substarct(nucklePushUpSumList);
} else {
return 0;
}
}
운동종목별로 전날대비 개수차이를 출력하기 위해선
해당 운동 종목의 운동기록의 합이 담긴 배열에서
'맨 마지막 요소 - 뒤에서 2번째 요소'를 반환하면 된다.
Widget returnString1() {
if (returnSubtract1() > 0) {
return Text(
'${returnSubtract1()}↑',
style: TextStyles.b3Bold.copyWith(
color: context.isLight ? LightModeColors.red : DarkModeColors.red),
);
} else if (returnSubtract1() < 0) {
return Text(
'${-1 * returnSubtract1()}↓',
style: TextStyles.b3Bold.copyWith(
color: context.isLight ? LightModeColors.blue : DarkModeColors.blue),
);
} else {
return Text('0',
style: TextStyles.b3Bold.copyWith(
color: context.isLight
? LightModeColors.dark3
: DarkModeColors.dark3));
}
}
Widget returnString2() {
if (returnSubtract2() > 0) {
return Text(
'${returnSubtract2()}↑',
style: TextStyles.b3Bold.copyWith(
color: context.isLight ? LightModeColors.red : DarkModeColors.red),
);
} else if (returnSubtract2() < 0) {
return Text(
'${-1 * returnSubtract2()}↓',
style: TextStyles.b3Bold.copyWith(
color: context.isLight ? LightModeColors.blue : DarkModeColors.blue),
);
} else {
return Text('0',
style: TextStyles.b3Bold.copyWith(
color: context.isLight
? LightModeColors.dark3
: DarkModeColors.dark3));
}
}
개수 차이의 반환 값을 기반으로 텍스트위젯을 작성해준다.
전날 대비 개수차이가 0보다 크다면 n↑, 작다면 n↓를 출력한다.
개수차이가 없다면 0을 출력한다.
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
contentPadding: const EdgeInsets.only(left: 2),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${data['day']}일차',
style: TextStyles.b2Medium,
),
const SizedBox(
height: 3,
),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'${data['ex1_name']}',
style: TextStyles.b3Medium,
),
const SizedBox(
width: 8,
),
Text(
'${data['ex1'].join(' ')}',
style: TextStyles.b3Regular,
),
const SizedBox(
width: 5,
),
Text(
'(${sum(data['ex1'])}개)',
style: TextStyles.b3Regular.copyWith(
color: context.isLight
? LightModeColors.dark3
: DarkModeColors.dark3),
),
const SizedBox(
width: 7,
),
data['day'] != 1 && checkNotFirstEx1()
? returnString1()
: const SizedBox.shrink()
],
),
Row(
children: [
Text(
'${data['ex2_name']}',
style: TextStyles.b3Medium,
),
const SizedBox(
width: 8,
),
Text(
'${data['ex2'].join(' ')}',
style: TextStyles.b3Regular,
),
const SizedBox(
width: 5,
),
Text(
'(${sum(data['ex2'])}개)',
style: TextStyles.b3Regular.copyWith(
color: context.isLight
? LightModeColors.dark3
: DarkModeColors.dark3),
),
const SizedBox(
width: 7,
),
data['day'] != 1 && checkNotFirstEx2()
? returnString2()
: const SizedBox.shrink(),
],
),
],
),
),
],
);
가공한 데이터를 컬럼 내에 출력해준다.
특정 위치로 리스트뷰 이동
final _scrollController = ScrollController();
child: ListView.separated(
controller: _scrollController,
ScrollController를 StatefulWidget내에서 선언해주고
리스트뷰의 컨트롤러를 스크롤 컨트롤러로 지정해준다.
// 스크롤 뷰 맨 위로 이동
_scrollController.jumpTo(0.0);
// 스크롤 뷰 맨 아래로 이동
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
글자를 누르면 스크롤 뷰가 맨 위, 맨 아래로 이동하도록 해준다.
스크롤 시 앱바 색 안 변하게 하기
리스트뷰를 스크롤하면 이런식으로 앱바의 색이 변하게 된다.
Widget _buildPage(BuildContext context) {
return NestedScrollView(
physics: const NeverScrollableScrollPhysics(),
headerSliverBuilder: (context, innerBoxIsScrolled) {return [];},
body: _controller.dataList.isEmpty
위젯을 NestedScrollView로 감싸주면 스크롤해도 앱바의 색이 변하지 않게 된다.
테스트
의도대로 출력이 잘 되는 모습이다.
해당 운동종목의 운동기록이 1개라면 우측에 아무것도 출력하지 않고
운동 종목별로 전날대비 개수차이를 계산해서 우측에 출력한다.
지금은 버튼을 통해 임시적으로 데이터를 넣었지만
나중에 종목 설정과 운동하기(기록) 기능을 구현해 운동이 끝나면
자동으로 데이터가 추가되는 형식으로 구현할 것이다.
원래 1주일 간격으로 페이지네이션을 하고 싶었지만
페이지네이션을 진행하면 각각의 운동종목의 리스트가 초기화 되는 문제가 있어
기록 차이가 제데로 출력되지않아 스크롤 이동으로 대체를 했다.
'멸치탈출 (플러터)' 카테고리의 다른 글
[플러터] 운동항목 설정 다이얼로그 구현 (0) | 2024.05.15 |
---|---|
[플러터] 운동 기록이 없으면 1주일 후 일지를 초기화 시키는 타이머 구현 (0) | 2024.05.12 |
[플러터] ListView를 활용한 운동기록 출력 (json 리스트 데이터 활용) (0) | 2024.04.30 |
[플러터] 홈 화면 그리기 & 다이얼로그 사용법 (1) | 2024.04.21 |
[플러터] 이름 입력 페이지 구현 (싱글톤 패턴을 활용한 shared_preferences 사용법) (1) | 2024.04.20 |