개발/클론코딩

[Nomad Coders] Flutter로 Pomodoros 앱 만들기

걍판자 2024. 1. 10. 23:35
반응형

https://nomadcoders.co/flutter-for-beginners

 

Flutter 로 웹툰 앱 만들기 – 노마드 코더 Nomad Coders

Flutter for Beginners

nomadcoders.co

이 글은 Nomad Coders 님의 강의를 참고해 작성하였습니다.

작성 날짜 기준 무료 강의로, 누구나 쉽게 가입하고 배울 수 있습니다.

 

2024.01.10 - [개발] - [Nomad Coders] Flutter로 UI 만들기

 

[Nomad Coders] Flutter로 UI 만들기

Nomad Coders님의 Flutter 강의를 듣고 요약한 내용입니다. 링크는 아래와 같습니다. 해당 강의는 현재 무료로 이용할 수 있습니다. https://nomadcoders.co/flutter-for-beginners Flutter 로 웹툰 앱 만들기 – 노마

juneforpay.tistory.com

 

Stateful Widgets

state

import 'package:flutter/material.dart';
void main() {
  runApp(App());
}

class App extends StatefulWidget {
  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  int counter = 0;

  void onClicked() {
    counter = counter + 1;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: const Color(0xFFF4EDDB),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                'Click Count',
                style: TextStyle(fontSize: 30),
              ),
              Text(
                '$counter',
                style: const TextStyle(fontSize: 30),
              ),
              IconButton(
                iconSize: 40,
                onPressed: onClicked,//버튼이 눌렸을때 OnClicked 함수 발동
                icon: const Icon(
                  Icons.add_box_rounded,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
  • 지금까지 사용한 stateless widget은 그냥 build 메서드를 통해 정적인 내용을 출력한다.
  • stateful widget은 이와 달리 동적으로 상태에 따라 변한다.
  • statelss는 데이터가 없다는 뜻이다. stateful은 데이터 즉 상태가 있는 것이다.
  • stateful widget을 사용하면 실시간 데이터 변화를 ui를 통해 볼 수 있다.
  • stateful widget은 다음과 같이 계승받는 부분과 하는 부분 2가지로 나뉜다
  • 다음 코드에서는 버튼을 눌러도 숫자가 증가하지 않는다 왜 그럴까?

 

 

 

setState

void onClicked() {
setState(){    
counter = counter + 1;
  }
}

void onClicked() {
setState(){}    
counter = counter + 1;
  }

//setState의 중괄호 위치 상관 없이 둘다 된다.
  • 변경된 값을 보기 위해서는 setState(){} 안에서 변경된 값을 호출해주어야 한다. setstate를 통해 build 메서드를 다시 호출한다. setState를 통해 다시 만드는 것이다.
  • 꼭 수정되는 데이터 코드들을 setState 안에 넣을 필요는 없다. setState를 호출하는 것 만으로 그냥 업데이트가 된다. 하지만 가능하면 가독성을 위해 setState 안에 넣도록 하자
  • state를 사용하지 않는다고 데이터 자체가 불변하는 것은 아니다. 다만 변경된 데이터가 화면에 표시되지 않는 것이다.
  • 즉 필요한 건 stateful 했을때 나오는 것에서 다 아래쪽에 적고 변경필요한 건 setState 안에 적으면 된다.
  • setState는 위젯의 새로고침과 같은 기능을 준다.

 

 

BuildContext

import 'package:flutter/material.dart';
void main() {
  runApp(App());
}
class App extends StatefulWidget {
  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        textTheme: const TextTheme(
          titleLarge: TextStyle(
            color: Colors.red,
          ),
        ),
      ),
      home: Scaffold(
        backgroundColor: const Color(0xFFF4EDDB),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: const [
              MyLargeTitle(),
            ],
          ),
        ),
      ),
    );
  }
}

class MyLargeTitle extends StatelessWidget {
  const MyLargeTitle({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      'My Large Title',
      style: TextStyle(
        fontSize: 30,
        color: Theme.of(context).textTheme.titleLarge?.color,
      ),
    );
  }
}
  • Context는 위젯의 상하관계에서 더 상위관계의 위젯 그러니까 더 일찍 호출되는 더 뿌리가 되는 위젯의 성질을 계승할 수 있게 한다.
  • 이는 class를 위젯으로 따로 분리시켜도 마찬가지로 작동한다.
  • context를 계승할때 null이 아니어도 dart는 null일수도 있다고 생각해 오류창을 띄울 수 있다. 이때는 ?를 통해 null이 아닐 때만 처리한다고 하거나, 이건 null이 아니라고 확신을 주는! 를 쓰면 된다.
  • theme: ThemeData를 통해 기존의 테마 값들을 저장해놓을 수 있다.
  • Theme.of(context)를 통해 상위 값들에 접근 할 수 있다.
  • 상위 계층 중 가장 가까운 값을 받는다고 한다.

 

 

Widget LifeCycle

void initState(){
		super.iniState();
}

void dispose(){
		super.dispose();
}
  • initState()는 build를 하기 전 불러올때 실행된다. 대표적으로 API를 불러올 때 사용된다
  • dispose()는 화면에서 사라질 때 실행한다.
  • 꼭 initState()를 쓰지 않아도 build override 전에 변숫값을 초기화하거나 하는 식으로 초기상황을 불러올 수 있다. 부모요소에 의존하는 데이터를 초기화하기 위해 주로 쓰게 된다.

 

Pomodoro App

UI

아래의 코드를 적용했을 때 나오는 UI

 

import 'package:flutter/material.dart';

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData( // theme 데이터 다른곳에도 불러오기 위한 빌드업
        backgroundColor: const Color(0xFFE7626C),//붉은 배경
        textTheme: const TextTheme(
          headline1: TextStyle(
            color: Color(0xFF232B55),//검은 글자
          ),
        ),
        cardColor: const Color(0xFFF4EDDB),//흰 카드
      ),
 home: const HomeScreen(),//HomeScreen class 불러오기
    );
  }
}      

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

  @override
  State<HomeScreen> createState() => _HomeScreenState(); //Stateful이어서 있는 것
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,//theme에서 불러오기
      body: Column(
        children: [ // column의 children으로 flexible이 셋, 각자 화면 비율을 1 :3:1 로 가져간다.
          Flexible(
            flex: 1,
            child: Container( //container 생성 후 테마 받아 타이머 글자 ui 표시 
              alignment: Alignment.bottomCenter,// row의 아래중앙 정렬
              child: Text(
                '25:00',
                style: TextStyle(
                  color: Theme.of(context).cardColor,
                  fontSize: 89,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
          ),
          Flexible(
            flex: 3,
            child: Center( //중앙정렬
              child: IconButton(
                iconSize: 120,
                color: Theme.of(context).cardColor,
                onPressed: () {},//아이콘버튼이니 눌렀을때 반응 함수 필요
                icon: const Icon(Icons.play_circle_outline),
              ),
            ),
          ),
          Flexible(
            flex: 1,
            child: Row( //컨테이너가 더 넓게 열 전체를 차지하게 하려고 row로 지정
              children: [
               Expanded( // 그후 expanded로 row의 모든 공간 차지하게 함
                child: Container(
                    decoration: BoxDecoration(
                      color: Theme.of(context).cardColor,
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,//좌우 중앙 정렬
                      children: [
                        Text(
                          'Pomodoros',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.w600,
                            color: Theme.of(context).textTheme.headline1!.color,
                          ),
                        ),
                        Text(
                          '0',
                          style: TextStyle(
                            fontSize: 58,
                            fontWeight: FontWeight.w600,
                            color: Theme.of(context).textTheme.headline1!.color,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          )
        ],
      ),
    );//return scaffold
  }
}
  • flex는 해당 열이나 행의 화면을 각각 flex의 비율로 채우게 한다. 핸드폰 크기가 각자 다른 것에 맞추어 반응형으로 작동하는 것이다.
  • container가 차지하는 공간을 row,column과 expanded를 통해 열이나 행 전체를 차지하게 할 수 있다.

 

 

Timer

아래의 코드를 적용했을 때 나오는 UI

import 'package:flutter/material.dart';
import 'dart:async'; // timer를 불러오기 위한 추가 import

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData( // theme 데이터 다른곳에도 불러오기 위한 빌드업
        backgroundColor: const Color(0xFFE7626C),//붉은 배경
        textTheme: const TextTheme(
          headline1: TextStyle(
            color: Color(0xFF232B55),//검은 글자
          ),
        ),
        cardColor: const Color(0xFFF4EDDB),//흰 카드
      ),
 home: const HomeScreen(),//HomeScreen class 불러오기
    );
  }
}      

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

  @override
  State<HomeScreen> createState() => _HomeScreenState(); //Stateful이어서 있는 것
}

class _HomeScreenState extends State<HomeScreen> {
  
  int totalSeconds = 1500;
  late Timer timer; //Timer를 써서 타이머를 호출한다. 그리고 버튼 누를때 부터 초기화가 진행될 것이기에 late로 나중에 초기화 해주겠다고 선언한다.
  
  void onTick(Timer timer){
    setState((){
      totalSeconds--;
    });
  
  }
  
  void onStartPressed(){
    timer = Timer.periodic(
      const Duration(seconds:1),
      onTick,
    );
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,//theme에서 불러오기
      body: Column(
        children: [ // column의 children으로 flexible이 셋, 각자 화면 비율을 1 :3:1 로 가져간다.
          Flexible(
            flex: 1,
            child: Container( //container 생성 후 테마 받아 타이머 글자 ui 표시 
              alignment: Alignment.bottomCenter,// row의 아래중앙 정렬
              child: Text(
                '$totalSeconds',
                style: TextStyle(
                  color: Theme.of(context).cardColor,
                  fontSize: 89,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
          ),
          Flexible(
            flex: 3,
            child: Center( //중앙정렬
              child: IconButton(
                iconSize: 120,
                color: Theme.of(context).cardColor,
                onPressed: onStartPressed,//아이콘버튼이니 눌렀을때 반응 함수 필요
                icon: const Icon(Icons.play_circle_outline),
              ),
            ),
          ),
          Flexible(
            flex: 1,
            child: Row( //컨테이너가 더 넓게 열 전체를 차지하게 하려고 row로 지정
              children: [
               Expanded( // 그후 expanded로 row의 모든 공간 차지하게 함
                child: Container(
                    decoration: BoxDecoration(
                      color: Theme.of(context).cardColor,
                      borderRadius:BorderRadius.circular(50),//끝 둥글게 만들기
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,//좌우 중앙 정렬
                      children: [
                        Text(
                          'Pomodoros',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.w600,
                            color: Theme.of(context).textTheme.headline1!.color,
                          ),
                        ),
                        Text(
                          '0',
                          style: TextStyle(
                            fontSize: 58,
                            fontWeight: FontWeight.w600,
                            color: Theme.of(context).textTheme.headline1!.color,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          )
        ],
      ),
    );//return scaffold
  }
}
  • Timer.periodic( Duration(seconds:1), onTick,); 으로 주기적으로 실행하는 Timer.periodic 안에 몇 초마다 반복할 건지 duration을 설정하고, 그 duration 마다 함수 onTick을 실행시켰다.
  • 이때 periodic 안의 함수는 소괄호() 없이 이름만 입력한다.
  • onTick 함수는 periodic 안에서 시간마다 실행되기에 파라미터로 타이머인 Timer timer를 받아야 한다. 함수를 리턴받는 콜백함수기에 그렇다.
  • ontick에서 SetState를 통해 숫자를 1씩 감소시키고 ui를 업데이트 시켰다.
  • 버튼을 눌렀을 때 onStartpressed를 실행시켰다.
  • 그래서 타이머는 버튼을 눌렀을때 초기화가 시작되어 작동한다.

 

 

 

Pause Play

아래의 코드를 적용했을 때 나오는 UI

import 'package:flutter/material.dart';
import 'dart:async'; // timer를 불러오기 위한 추가 import

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData( // theme 데이터 다른곳에도 불러오기 위한 빌드업
        backgroundColor: const Color(0xFFE7626C),//붉은 배경
        textTheme: const TextTheme(
          headline1: TextStyle(
            color: Color(0xFF232B55),//검은 글자
          ),
        ),
        cardColor: const Color(0xFFF4EDDB),//흰 카드
      ),
 home: const HomeScreen(),//HomeScreen class 불러오기
    );
  }
}      

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

  @override
  State<HomeScreen> createState() => _HomeScreenState(); //Stateful이어서 있는 것
}

class _HomeScreenState extends State<HomeScreen> {
  bool isRunning =false;
  int totalSeconds = 1500;
  late Timer timer; //Timer를 써서 타이머를 호출한다. 그리고 버튼 누를때 부터 초기화가 진행될 것이기에 late로 나중에 초기화 해주겠다고 선언한다.
  
  void onTick(Timer timer){
    setState((){
      totalSeconds--;
    });
  
  }
  
  void onStartPressed(){
    timer = Timer.periodic(
      const Duration(seconds:1),
      onTick,
    );
    setState((){
    isRunning = true;
    });
  }
  
  void OnPausePressed(){
    timer.cancel();
    setState((){
      isRunning = false;
    });  
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,//theme에서 불러오기
      body: Column(
        children: [ // column의 children으로 flexible이 셋, 각자 화면 비율을 1 :3:1 로 가져간다.
          Flexible(
            flex: 1,
            child: Container( //container 생성 후 테마 받아 타이머 글자 ui 표시 
              alignment: Alignment.bottomCenter,// row의 아래중앙 정렬
              child: Text(
                '$totalSeconds',
                style: TextStyle(
                  color: Theme.of(context).cardColor,
                  fontSize: 89,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
          ),
          Flexible(
            flex: 3,
            child: Center( //중앙정렬
              child: IconButton(
                iconSize: 120,
                color: Theme.of(context).cardColor,
                onPressed: isRunning? OnPausePressed: 
                onStartPressed,//아이콘버튼이니 눌렀을때 반응 함수 필요
                icon: Icon(isRunning ?
                           Icons.pause_circle_outline:
                           Icons.play_circle_outline),
              ),
            ),
          ),
          Flexible(
            flex: 1,
            child: Row( //컨테이너가 더 넓게 열 전체를 차지하게 하려고 row로 지정
              children: [
               Expanded( // 그후 expanded로 row의 모든 공간 차지하게 함
                child: Container(
                    decoration: BoxDecoration(
                      color: Theme.of(context).cardColor,
                      borderRadius:BorderRadius.circular(50),//끝 둥글게 만들기
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,//좌우 중앙 정렬
                      children: [
                        Text(
                          'Pomodoros',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.w600,
                            color: Theme.of(context).textTheme.headline1!.color,
                          ),
                        ),
                        Text(
                          '0',
                          style: TextStyle(
                            fontSize: 58,
                            fontWeight: FontWeight.w600,
                            color: Theme.of(context).textTheme.headline1!.color,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          )
        ],
      ),
    );//return scaffold
  }
}
  • 일시정지 기능을 구현하였다.
  • bool isRunning에 따라 아이콘과 눌렀을때 실행되는 함수가 달라지며 함수가 실행될 때마다 참 거짓을 바꿔준다.
  • timer.cancel()을 통해 타이머를 멈출 수 있다.

 

 

Date Format

아래의 코드를 적용했을 때 나오는 UI

import 'package:flutter/material.dart';
import 'dart:async'; // timer를 불러오기 위한 추가 import

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData( // theme 데이터 다른곳에도 불러오기 위한 빌드업
        backgroundColor: const Color(0xFFE7626C),//붉은 배경
        textTheme: const TextTheme(
          headline1: TextStyle(
            color: Color(0xFF232B55),//검은 글자
          ),
        ),
        cardColor: const Color(0xFFF4EDDB),//흰 카드
      ),
 home: const HomeScreen(),//HomeScreen class 불러오기
    );
  }
}      

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

  @override
  State<HomeScreen> createState() => _HomeScreenState(); //Stateful이어서 있는 것
}

class _HomeScreenState extends State<HomeScreen> {
  bool isRunning =false;
  static const twentyFive = 1500;
  int totalSeconds = twentyFive;
  int totalPomodoros =0;
  late Timer timer; //Timer를 써서 타이머를 호출한다. 그리고 버튼 누를때 부터 초기화가 진행될 것이기에 late로 나중에 초기화 해주겠다고 선언한다.
  
  void onTick(Timer timer){
    if(totalSeconds==0){
      setState((){
        totalPomodoros++;
        isRunning=false;
        totalSeconds = twentyFive;
      });
      timer.cancel();
    }else{
      setState((){
        totalSeconds--;
      });
    
    }
   
  
  }
  
  void onStartPressed(){
    timer = Timer.periodic(
      const Duration(seconds:1),
      onTick,
    );
    setState((){
    isRunning = true;
    });
  }
  
  void onPausePressed(){
    timer.cancel();
    setState((){
      isRunning = false;
    });  
  }
  
  String format(int seconds){
    var duration = Duration(seconds: seconds);
    return duration.toString().split(".").first.substring(2,7);
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,//theme에서 불러오기
      body: Column(
        children: [ // column의 children으로 flexible이 셋, 각자 화면 비율을 1 :3:1 로 가져간다.
          Flexible(
            flex: 1,
            child: Container( //container 생성 후 테마 받아 타이머 글자 ui 표시 
              alignment: Alignment.bottomCenter,// row의 아래중앙 정렬
              child: Text(
                format(totalSeconds),
                style: TextStyle(
                  color: Theme.of(context).cardColor,
                  fontSize: 89,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
          ),
          Flexible(
            flex: 3,
            child: Center( //중앙정렬
              child: IconButton(
                iconSize: 120,
                color: Theme.of(context).cardColor,
                onPressed: isRunning? onPausePressed: 
                onStartPressed,//아이콘버튼이니 눌렀을때 반응 함수 필요
                icon: Icon(isRunning ?
                           Icons.pause_circle_outline:
                           Icons.play_circle_outline),
              ),
            ),
          ),
          Flexible(
            flex: 1,
            child: Row( //컨테이너가 더 넓게 열 전체를 차지하게 하려고 row로 지정
              children: [
               Expanded( // 그후 expanded로 row의 모든 공간 차지하게 함
                child: Container(
                    decoration: BoxDecoration(
                      color: Theme.of(context).cardColor,
                      borderRadius:BorderRadius.circular(50),//끝 둥글게 만들기
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,//좌우 중앙 정렬
                      children: [
                        Text(
                          'Pomodoros',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.w600,
                            color: Theme.of(context).textTheme.headline1!.color,
                          ),
                        ),
                        Text(
                          '$totalPomodoros',
                          style: TextStyle(
                            fontSize: 58,
                            fontWeight: FontWeight.w600,
                            color: Theme.of(context).textTheme.headline1!.color,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          )
        ],
      ),
    );//return scaffold
  }
}
  • 1500은 고정 상수기에 클래스 내에 귀속시키고, 불변한다는 의미로 static const 처리하였다.
  • 타이머가 다 되면 포모도로에 숫자가 1 오르고 타이머는 리셋되며 정지된다.
  • Duration(hours: a, minutes: b, seconds: c)로 입력하면 a시 b분 c초 형식의 시간단위로 바꿔준다.. 여기서 원하는 단위만 남기고 써도 된다.
  • duration으로 나온 현재 타이머 값을 문자열 포맷팅을 통해 필요한 값만 추출하였다.

 

5.4강 Code Challenge

import 'package:flutter/material.dart';
import 'dart:async'; // timer를 불러오기 위한 추가 import

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData( // theme 데이터 다른곳에도 불러오기 위한 빌드업
        backgroundColor: const Color(0xFFE7626C),//붉은 배경
        textTheme: const TextTheme(
          headline1: TextStyle(
            color: Color(0xFF232B55),//검은 글자
          ),
        ),
        cardColor: const Color(0xFFF4EDDB),//흰 카드
      ),
 home: const HomeScreen(),//HomeScreen class 불러오기
    );
  }
}      

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

  @override
  State<HomeScreen> createState() => _HomeScreenState(); //Stateful이어서 있는 것
}

class _HomeScreenState extends State<HomeScreen> {
  bool isRunning =false;
  static const twentyFive = 1500;
  int totalSeconds = twentyFive;
  int totalPomodoros =0;
  late Timer timer; //Timer를 써서 타이머를 호출한다. 그리고 버튼 누를때 부터 초기화가 진행될 것이기에 late로 나중에 초기화 해주겠다고 선언한다.
  
  void onTick(Timer timer){
    if(totalSeconds==0){
      setState((){
        totalPomodoros++;
        isRunning=false;
        totalSeconds = twentyFive;
      });
      timer.cancel();
    }else{
      setState((){
        totalSeconds--;
      });
    
    }
   
  
  }
  
  void onStartPressed(){
    timer = Timer.periodic(
      const Duration(seconds:1),
      onTick,
    );
    setState((){
    isRunning = true;
    });
  }
  
  void onPausePressed(){
    timer.cancel();
    setState((){
      isRunning = false;
      
    });  
  }
  
  void onReplayPressed(){
    timer.cancel();
    setState((){
      isRunning=false;
      totalSeconds = twentyFive;
      
    });
  }
  
  String format(int seconds){
    var duration = Duration(seconds: seconds);
    return duration.toString().split(".").first.substring(2,7);
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,//theme에서 불러오기
      body: Column(
        children: [ // column의 children으로 flexible이 셋, 각자 화면 비율을 1 :3:1 로 가져간다.
          Flexible(
            flex: 1,
            child: Container( //container 생성 후 테마 받아 타이머 글자 ui 표시 
              alignment: Alignment.bottomCenter,// row의 아래중앙 정렬
              child: Text(
                format(totalSeconds),
                style: TextStyle(
                  color: Theme.of(context).cardColor,
                  fontSize: 89,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
          ),
          Flexible(
            flex: 3,
            child: Center( //중앙정렬
              child: IconButton(
                iconSize: 120,
                color: Theme.of(context).cardColor,
                onPressed: isRunning? onPausePressed: 
                onStartPressed,//아이콘버튼이니 눌렀을때 반응 함수 필요
                icon: Icon(isRunning ?
                           Icons.pause_circle_outline:
                           Icons.play_circle_outline),
              ),
            ),
          ),
          Flexible(
            flex: 1,
            child: Center( //중앙정렬
              child: IconButton(
                iconSize: 120,
                color: Theme.of(context).cardColor,
                onPressed: onReplayPressed,
                icon: const Icon(Icons.replay_outlined),
              ),
            ),
          ),
          Flexible(
            flex: 1,
            child: Row( //컨테이너가 더 넓게 열 전체를 차지하게 하려고 row로 지정
              children: [
               Expanded( // 그후 expanded로 row의 모든 공간 차지하게 함
                child: Container(
                    decoration: BoxDecoration(
                      color: Theme.of(context).cardColor,
                      borderRadius:BorderRadius.circular(50),//끝 둥글게 만들기
                    ),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,//좌우 중앙 정렬
                      children: [
                        Text(
                          'Pomodoros',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.w600,
                            color: Theme.of(context).textTheme.headline1!.color,
                          ),
                        ),
                        Text(
                          '$totalPomodoros',
                          style: TextStyle(
                            fontSize: 58,
                            fontWeight: FontWeight.w600,
                            color: Theme.of(context).textTheme.headline1!.color,
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          )
        ],
      ),
    );//return scaffold
  }
}
  • Reset 버튼을 만들어 누르면 다시 25분으로 돌아가게 해야 한다.
  • 시간을 원래대로 되돌리고 일시정지하는 reset 버튼을 추가했다.
  • 아이콘 관련해서 내가 착각하고 있는 부분이 있었다. 전에 ui만 만들 때의 아이콘은 이미 오버라이드 받은 거라 icon: 이미지 명 이었지만, 지금은 그렇지 않아 icon: const Icon(Icons.replay_outlined)과 같이 다 써주어야 했는데, 전에 것을 보고하다가 이 부분에서 잠깐 헤맸다.
반응형