와챠의 우당탕탕 코딩 일기장

[Flutter] Flutter 로 웹툰 앱 만들기: Pomodoro 앱 만들기/Timer/Flexible 본문

코딩 일기장/Flutter

[Flutter] Flutter 로 웹툰 앱 만들기: Pomodoro 앱 만들기/Timer/Flexible

minWachya 2023. 1. 27. 18:35
반응형

Pomodoro 앱 만들기

  • 타이머 시작 기능: 25분에서 1초씩 줄어들기
  • 타이머 중지 기능: 타이머 일시 멈춤
  • 타이머 재시작 기능: 25분부터 재시작
  • 뽀모도로 한 번 끝나면 아래 숫자 ++

주석으로 설명 달겠슴다.


+ Flexible 위젯으로 비율 설정 가능한데, 이를 사용해서 UI를 꾸며보겠다.

Flexible(
            flex: 1,
            child: Container(
              decoration: const BoxDecoration(color: Colors.red),
            ),
          ),
          Flexible(
            flex: 2,
            child: Container(
              decoration: const BoxDecoration(color: Colors.green),
            ),
          ),
          Flexible(
            flex: 1,
            child: Container(
              decoration: const BoxDecoration(color: Colors.blue),
            ),
          ),

완성 코드

프로퍼티들

// 타이머가 25분동안 돌기 때문에 1500이라는 숫자를 상수로 지정해둠
static const twentyFiveMinnutes = 1500; // 25분 == 1500초

int totalSeconds = twentyFiveMinnutes;	// 총 시간
bool isRunning = false;			// 타이머가 돌고있는지 아닌지 여부
int totalPomodoros = 0;			// 뽀모도로 끝낸 횟수

late Timer timer;			// 타이머

 

함수 부분

  • 시작 버튼 클릭 리스너
  • 일지 중지 버튼 클릭 리스너
  • 재시작 버튼 클릭 리스너
  • 1초마다 실행되는 함수
  • mm:ss로 format해주는 함수
  /* 시작 버튼 클릭 */
  void onStartPressed() {
  	// 타이머 시작, onTick함수를 1초마다 호출
    timer = Timer.periodic(
      const Duration(seconds: 1),
      onTick,
    );

	// 타이머가 돌고있으니 isRunnng 을 true로 변경
    setState(() {
      isRunning = true;
    });
  }
  
  /* 타이머가 1초씩 없어지도록 함 */
   void onTick(Timer timer) {
   	// 25분 끝나면
    if (totalSeconds == 0) {
      setState(() {
        totalPomodoros = totalPomodoros + 1;	// 총 뽀모도로 횟수 증가
        isRunning = false;			// 타이머 상태 변경
        totalSeconds = twentyFiveMinnutes;	// 타이머를 다시 25분으로 돌려놓음
      });
      // 타이머 중지
      timer.cancel();
    } else {
    // 타이머 진행중이면 1초씩 빼기
      setState(() {
        totalSeconds = totalSeconds - 1;
      });
    }
  }
  
// ---------------------

	/* 일시 중지 버튼 클릭 */
  void onPausePressed() {
    timer.cancel();	// 타이머 중지

    setState(() {	// 타이머 상태 변경
      isRunning = false;
    });
  }

// ---------

	/* 재시작 버튼 클릭 */
  void onReStartPressed() {
    timer.cancel();	// 타이머 중지
    // 타이머 상태 저장 및 총 시간 25분으로 초기화
    setState(() {
      isRunning = false;
      totalSeconds = twentyFiveMinnutes;
    });
  }
  
// ---------

	/* mm:ss로 변경 */
  String format(int seconds) {
    var duration = Duration(seconds: seconds); // 0:25:00.000000
    return duration
        .toString()
        .split(".") // [0:25:00, 000000]
        .first // 0:25:00
        .substring(2, 7); // 25:00
  }

 

ui부분

@override
  Widget build(BuildContext context) {
    return Scaffold(
    // 배경색
      backgroundColor: Theme.of(context).scaffoldBackgroundColor,
      body: Column(
        children: [
        // 25:00 숫자 부분
          Flexible(
            flex: 1,	// 비율
            child: Container(
              alignment: Alignment.bottomCenter,
              // mm:ss로 포맷되어진 숫자 출력
              child: Text(
                format(totalSeconds),
                style: TextStyle(
                  color: Theme.of(context).cardColor,
                  fontSize: 89,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
          ),
          
          // 시작<>중지 버튼, 재시작 버튼
          Flexible(
              flex: 3,	// 비율
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                	// 타이머가 진행중이면 중지버튼, 멈춰있으면 시작 버튼
                  IconButton(
                    onPressed: isRunning ? onPausePressed : onStartPressed,
                    iconSize: 120,
                    color: Theme.of(context).cardColor,
                    icon: Icon(isRunning
                        ? Icons.pause_circle_outline
                        : Icons.play_circle_outline),
                  ),
                  // 재시작 버튼
                  IconButton(
                    onPressed: onReStartPressed,
                    iconSize: 50,
                    color: Theme.of(context).cardColor,
                    icon: const Icon(Icons.restart_alt),
                  ),
                ],
              )),
              
          // 뽀모도로 횟수
          Flexible(
            flex: 1,
            child: Row(
              children: [
                Expanded(
                  child: Container(
                    decoration: BoxDecoration(
                      color: Theme.of(context).cardColor,
                      // 모서리 둥글게
                      borderRadius: const BorderRadius.vertical(
                          top: Radius.circular(50), bottom: Radius.zero),
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          'Pomodoros',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.w600,
                            color:
                                Theme.of(context).textTheme.displayLarge!.color,
                          ),
                        ),
                        Text(
                          '$totalPomodoros',
                          style: TextStyle(
                            fontSize: 58,
                            fontWeight: FontWeight.w600,
                            color:
                                Theme.of(context).textTheme.displayLarge!.color,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

전체코드

import 'dart:async';

import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  static const twentyFiveMinnutes = 1500;

  int totalSeconds = twentyFiveMinnutes; // 25분 == 1500초
  bool isRunning = false;
  int totalPomodoros = 0;
  late Timer timer;

  void onTick(Timer timer) {
    if (totalSeconds == 0) {
      setState(() {
        totalPomodoros = totalPomodoros + 1;
        isRunning = false;
        totalSeconds = twentyFiveMinnutes;
      });
      timer.cancel();
    } else {
      setState(() {
        totalSeconds = totalSeconds - 1;
      });
    }
  }

  // 매 초마다 동작
  void onStartPressed() {
    timer = Timer.periodic(
      const Duration(seconds: 1),
      onTick,
    );

    setState(() {
      isRunning = true;
    });
  }

  void onPausePressed() {
    timer.cancel();

    setState(() {
      isRunning = false;
    });
  }

  void onReStartPressed() {
    timer.cancel();
    setState(() {
      isRunning = false;
      totalSeconds = twentyFiveMinnutes;
    });
  }

  String format(int seconds) {
    var duration = Duration(seconds: seconds); // 0:25:00.000000
    return duration
        .toString()
        .split(".") // [0:25:00, 000000]
        .first // 0:25:00
        .substring(2, 7); // 25:00
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).scaffoldBackgroundColor,
      body: Column(
        children: [
          Flexible(
            flex: 1,
            child: Container(
              alignment: Alignment.bottomCenter,
              child: Text(
                format(totalSeconds),
                style: TextStyle(
                  color: Theme.of(context).cardColor,
                  fontSize: 89,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
          ),
          Flexible(
              flex: 3,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  IconButton(
                    onPressed: isRunning ? onPausePressed : onStartPressed,
                    iconSize: 120,
                    color: Theme.of(context).cardColor,
                    icon: Icon(isRunning
                        ? Icons.pause_circle_outline
                        : Icons.play_circle_outline),
                  ),
                  IconButton(
                    onPressed: onReStartPressed,
                    iconSize: 50,
                    color: Theme.of(context).cardColor,
                    icon: const Icon(Icons.restart_alt),
                  ),
                ],
              )),
          Flexible(
            flex: 1,
            child: Row(
              children: [
                Expanded(
                  child: Container(
                    decoration: BoxDecoration(
                      color: Theme.of(context).cardColor,
                      borderRadius: const BorderRadius.vertical(
                          top: Radius.circular(50), bottom: Radius.zero),
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Text(
                          'Pomodoros',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.w600,
                            color:
                                Theme.of(context).textTheme.displayLarge!.color,
                          ),
                        ),
                        Text(
                          '$totalPomodoros',
                          style: TextStyle(
                            fontSize: 58,
                            fontWeight: FontWeight.w600,
                            color:
                                Theme.of(context).textTheme.displayLarge!.color,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

 


느낀 점

  • 플러터 상수 네이밍 컨벤션도 변수와 같나,,, 대문자로 써줘야 하지 않나
  • 안드에서는 타이머,,,진짜 지독하게 사용하기 귀찮은데 플러터는 정말 간단하군아,,,,
반응형
Comments