반응형
SMALL
요즘 플러터에 재미가 들려서 니꼴쌤의 플러터를 사용하여 뽀모도로 만들기를 공부하였다.
참 재미있는 프레임워크라 오랜만에 블로깅을 한다.
준비물 : 플러터 sdk, xcode, vscode
■ 플러터 프로젝트 세팅하기
하기의 명령어를 통하여 플러터 프로젝트를 설치한다.
flutter create pomodoro
■ Main.dart 에서 테마 및 레이아웃 기본 세팅하기
import 'package:flutter/material.dart';
import 'package:pomodoro/screens/home_screen.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
scaffoldBackgroundColor: const Color(0xFFE7627c),
colorScheme: const ColorScheme.light(
surface: Color(0x0ff32b55),
),
textTheme: const TextTheme(
headlineLarge: TextStyle(
color: Color(0x0ff32b55),
),
),
cardColor: const Color(0xFFF4EDDB),
),
home: const HomeScreen(),
);
}
}
■ MaterialApp 은 우리가 만들 앱을 랜더링할 최상위 컴포넌트이다.
■ 구글식 컴포넌트 스타일이라 보면 된다.
■ ThemeData 클래스를 통하여 공통으로 사용할 스타일 요소들을 정의한다.
flutter 는 게임엔진처럼 가상엔진 위에서 랜더링 되는 방식이기에 휴대폰 안에 기본 제공 기능들을 사용할 수 없다. 기본 위젯등을 사용해야 한다면 react native를 쓰도록 하자
■ 뽀모도로 랜더링을 담당할 home_screen.dart 파일 및 클래스 만들기
필자는 니꼬쌤과 동일하게 ./lib/screens/home_screen 에 파일을 만들었다.
1. 멤버변수 선언 및 메서드 만들기
■ 여기서 눈여겨볼 점은 클래스가 StatefulWidget을 상속받은 점인데 이 부모요소는 동적 랜더링을 사용할 시 상속받는다.
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 defaultTime = 1500;
int totalSeconds = defaultTime;
bool isRunning = false;
int totalPomodoros = 0;
late Timer timer;
void onTick(Timer timer) {
if (totalSeconds == 0) {
setState(() {
totalSeconds = defaultTime;
++totalPomodoros;
isRunning = false;
});
timer.cancel();
} else {
setState(() {
totalSeconds -= 1;
});
}
}
void onStartPressed() {
timer = Timer.periodic(
const Duration(seconds: 1),
onTick,
);
setState(() {
isRunning = true;
});
}
void onPausePressed() {
timer.cancel();
setState(() {
isRunning = false;
});
}
void onReset() {
timer.cancel();
setState(() {
isRunning = false;
totalSeconds = defaultTime;
});
}
String format(int seconds) {
var duration = Duration(seconds: seconds);
return duration.toString().split(".").first.substring(2, 7);
}
}
■ 우선 뽀모도로의 멤버변수와 동작을 담당할 메서드를 만들어 준다.
■ 다트에도 리액트와 유사한 상태값을 동적으로 업데이트 해주는 setState 함수가 있어서 참 재미 있었다.
■ 각 멤버변수들의 역할
- static const defaultTime : 뽀모도로의 기본 시간 값이다, 변하지 않을 값이기 때문의 정적 변수 및 상수로 선언한다.
- int totalSeconds : 뽀모도로의 시간 동작을 담당할 변수이다
- bool isRunning : 뽀모도로의 시작, 일시정지, 리셋의 판단을 담당할 변수이다.
- int totalPomodoro : 뽀모도로의 완료 횟수이다.
- late Timer timer : 다트에서 제공해주는 타이머 라이브러리, late 키워드를 사용하여 값을 나중에 받도록 한다.
■ 각 메서드들의 역할
- onTick : 뽀모도로의 시간을 틱당 1초씩 줄이고, 타이머가 0이되면 리셋 및 뽀모도로의 완료 횟수를 올려준다.
- onStartPressed : Timer 라이브러리의 periodic 함수와 Duration 클래스를 사용해 1초마다 onTick 메서드를 실행해준다.
- onPausePressed : 타이머를 일시정지 해준다.
- onReset : 타이머를 리셋해준다.
- format
- Duration 클래스를 활용해 초단위로 들어온 시간의 포맷을 25:00 처럼 변환해준다.
- 하지만 그냥 Duration을 사용하여 시간을 변환하였을 시 포멧이 00:25:00.00000 처럼 나온다.
- 그러하여 duration을 string 형식으로 변환하여 필요한 부분만 짤라서 쓴다.
2. 랜더링부 만들기
극악의 피라미드 구조다.. 엄청난 괄호 모음이 나를 두렵게 하지만 Vscode Dart Extension을 통해 구분하기 편하게 해준다.
@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: 99,
fontWeight: FontWeight.w600),
),
),
),
Flexible(
flex: 2,
child: Center(
child: Column(
children: [
IconButton(
onPressed: isRunning ? onPausePressed : onStartPressed,
iconSize: 120,
color: Theme.of(context).cardColor,
icon: Icon(
isRunning
? Icons.pause_circle
: Icons.play_circle_outline,
),
),
IconButton(
onPressed: onReset,
icon: const Icon(Icons.restart_alt_outlined),
iconSize: 80,
color: Theme.of(context).cardColor,
)
],
),
),
),
Flexible(
flex: 1,
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(50),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Pomodoros",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
Text(
"$totalPomodoros",
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
],
),
),
),
],
),
),
],
),
);
}
}
■ Theme.of(context) 를 사용하여 상위요소의 테마를 가져와서 사용할 수 있다.
■ Flexible 위젯을 사용하여 내부 박스를 정갈하게 나눌 수 있다.
■ 삼항연산자를 통하여 동적 랜더링을 해줄 수 있다.
■ home_screen.dart 코드 총합
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 defaultTime = 1500;
int totalSeconds = defaultTime;
bool isRunning = false;
int totalPomodoros = 0;
late Timer timer;
void onTick(Timer timer) {
if (totalSeconds == 0) {
setState(() {
totalSeconds = defaultTime;
++totalPomodoros;
isRunning = false;
});
timer.cancel();
} else {
setState(() {
totalSeconds -= 1;
});
}
}
void onStartPressed() {
timer = Timer.periodic(
const Duration(seconds: 1),
onTick,
);
setState(() {
isRunning = true;
});
}
void onPausePressed() {
timer.cancel();
setState(() {
isRunning = false;
});
}
void onReset() {
timer.cancel();
setState(() {
isRunning = false;
totalSeconds = defaultTime;
});
}
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).scaffoldBackgroundColor,
body: Column(
children: [
Flexible(
flex: 1,
child: Container(
alignment: Alignment.bottomCenter,
child: Text(
format(totalSeconds),
style: TextStyle(
color: Theme.of(context).cardColor,
fontSize: 99,
fontWeight: FontWeight.w600),
),
),
),
Flexible(
flex: 2,
child: Center(
child: Column(
children: [
IconButton(
onPressed: isRunning ? onPausePressed : onStartPressed,
iconSize: 120,
color: Theme.of(context).cardColor,
icon: Icon(
isRunning
? Icons.pause_circle
: Icons.play_circle_outline,
),
),
IconButton(
onPressed: onReset,
icon: const Icon(Icons.restart_alt_outlined),
iconSize: 80,
color: Theme.of(context).cardColor,
)
],
),
),
),
Flexible(
flex: 1,
child: Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(50),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"Pomodoros",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
Text(
"$totalPomodoros",
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
],
),
),
),
],
),
),
],
),
);
}
}
그러하여 귀여운 뽀모도로 완성
■ 마치며
플러터는 가상엔진위에서 동작하는 만큼 브라우저, 맥, 안드로이드 등등 에서 동작하는 만큼 참 매력적인 프레임워크이며 다트 또한 참 재밌는 언어인 것 같다.
물론 코드 구조는 극악같으나, 플러터 개발자 지인을 통해 실무 코드는 조금 덜 하다는 말을 들었으니 나도 더욱더 플러터를 배우면서 좋은 방향으로 코드를 짜는 법을 공부해야겠다.
최근에 개발에 대해서 조금 흥미를 잃나 싶었는데 참 재미있는 놀거리가 생긴 것 같아 기분이 좋다.
앞으로 플러터 실력을 고도화하면서 플러터로 다양한 앱을 만들어 봐야겠다.
LIST