티스토리 뷰

멸치탈출

[플러터] 운동파트 구현

하몬드 2024. 5. 18. 00:51

개요

 

 

해당 사진은 운동시작 버튼을 눌렀을 때 출력할 운동화면들이다.

 

항목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

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함