[Flutter] ํ๋ฌํฐ ๊ธฐ์ด ๋ด์ฉ ์ ๋ฆฌ - Part 1
๋ฐฐ๊ฒฝ ์ง์
Flutter ๊ตฌ์กฐ
๐ก Dart๋ Just-In-Time(JIT) ์ปดํ์ผ๊ณผ Ahead-Of-Time(AOT) ์ปดํ์ผ์ ๋ชจ๋ ์ง์ํ๋ค. JIT ์ปดํ์ผ์ ๊ฐ๋ฐ ๋ชจ๋์ ์ฌ์ฉ๋ผ์ Hot Reload ๊ฐ์ ๋น ๋ฅธ ํผ๋๋ฐฑ ๋ฃจํ๋ฅผ ์ ๊ณตํ๋ค. AOT ์ปดํ์ผ์ ๋ฐฐํฌ ๋ชจ๋์ ์ฌ์ฉ๋ผ์ ์ปดํ์ผ๋ ์ฝ๋๊ฐ ๊ธฐ๊ธฐ์ ๋ ์ต์ ํ๋ ์ํ๋ก ์คํํ ์ ์๊ฒ ๋ง๋ ๋ค.
- ๋ค์ดํฐ๋ธ ๊ฐ๋ฐ์์ ์ด์์ฒด์ (OS)์ ์ง์ ์ํธ์์ฉํ์ฌ ๋ฒํผ, ํ ์คํธ ๊ฐ์ UI ์์๋ฅผ ์์ฑํ๋ค.
- Flutter๋ ๋ค์ดํฐ๋ธ ํ๋ซํผ์ ์์ ฏ์ ์ฌ์ฉํ์ง ์๊ณ ์์ฒด์ ์ผ๋ก UI๋ฅผ ๋ ๋๋ง ํ๋ค. ์ด๋ ๋น๋์ค ๊ฒ์ ์์ง์ ์๋ ๋ฐฉ์๊ณผ ์ ์ฌํ๋ค.
- Dart๋ก ์์ฑ๋ ์ฝ๋๋ Flutter ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ์ฌ UI๋ฅผ ์ ์ํ๊ณ , ์ค์ ํ๋ฉด์ ๋ ๋๋ง ํ๋ ์์ ์ C++๋ก ์์ฑ๋ Flutter ์์ง(Skia)์ด ๋ด๋นํ๋ค.
- ์ด ์์ง์ด ๋ชจ๋ UI ์์๋ฅผ ๊ทธ๋ฆฌ๊ธฐ ๋๋ฌธ์ ๋ค์ดํฐ๋ธ ์์ ฏ์ ์ฌ์ฉํ์ง ์๋๋ค.
- Dart ์ฝ๋์ Flutter SDK๋ ๋ค์ดํฐ๋ธ ARM ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์ปดํ์ผ๋ผ์ ๊ฐ ํ๋ซํผ์ ์คํ ํ์ผ๋ก ํจํค์ง ๋๋ค.
- ์ด๋ ๊ฒ ๋ง๋ค์ด์ง ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ ํ๋ซํผ์์ ๋์ผํ Flutter ์์ง์ ์คํํ์ฌ UI๋ฅผ ๋ ๋๋ง ํ๋ค.
- ์ฆ, Flutter ์์ ฏ์ ์ด์์ฒด์ ๊ฐ ์ ๊ณตํ๋ ๋ค์ดํฐ๋ธ ์์ ฏ์ด ์๋๋ผ Flutter ์์ง์ด ๊ทธ๋ฆฐ ๊ฒ์ด๋ค.
- ์ด๋ฌํ ํน์ง์ผ๋ก Flutter๋ iOS, Android, Windows ๋ฑ ๋ค์ํ ํ๋ซํผ์์ ๋์ผํ๊ฒ ์๋ํ๋ค.
- ํ์ง๋ง Flutter ์ ํ๋ฆฌ์ผ์ด์ ์ UI๋ ๋ค์ดํฐ๋ธ ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ์์ ํ ๋์ผํ์ง ์์ ์ ์๋ค.
- ์ด๋ก ์ธํด Flutter ์ฑ์ด ์์ฐ์ค๋ฝ์ง ์์ ์ ์๋ ๋จ์ ์ด ์์ง๋ง, ๊ฐ๋ฐ์๊ฐ ๋ชจ๋ ํฝ์ ์ ์ ์ดํ ์ ์๊ฒ ํ์ฌ ๋ค์ํ ์ปค์คํฐ๋ง์ด์ง๊ณผ ์ ๋๋ฉ์ด์ ์ด ๊ฐ๋ฅํ๋ค๋ ์ฅ์ ์ด ์๋ค.
Flutter vs React Native
๐ก Flutter = ์ผ๊ด์ฑ, React Native = ์๋ฐ์คํฌ๋ฆฝํธ ์ํ๊ณ
Flutter
- ์์ง ๋ฐ UI ๋ ๋๋ง: Flutter๋ ๊ฒ์ ์์ง์ฒ๋ผ ์๋ํ์ฌ ๋ชจ๋ UI๋ฅผ ์์ฒด ์์ง์ผ๋ก ๋ ๋๋ง ํ๋ค. ์ฆ, iOS๋ Android์ ๋ค์ดํฐ๋ธ ์ปดํฌ๋ํธ๋ฅผ ์ง์ ์ฌ์ฉํ์ง ์๊ณ , ๋ชจ๋ ๊ทธ๋ํฝ์ ์์ฒด์ ์ผ๋ก ๋ ๋๋ง ํ๋ค. ์ด ๋ฐฉ์์ ๋ชจ๋ ํ๋ซํผ์์ ๋์ผํ UI๋ฅผ ์ ๊ณตํ ์ ์๋ ์ฅ์ ์ด ์์ง๋ง, ๋ค์ดํฐ๋ธ ์์ ฏ์ ์ฌ์ฉํ ์ ์๋ ๋จ์ ์ด ์๋ค.
- ์ฑ๋ฅ: Flutter๋ Dart๋ก ์์ฑ๋ ์ฝ๋๋ฅผ ๋ค์ดํฐ๋ธ ARM ์ฝ๋๋ก ์ง์ ์ปดํ์ผํ์ฌ ์ค๊ฐ ๊ณ์ธต์ด ํ์ํ์ง ์๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ๋ฉด์์ ๋ ๋ง์ ์ด์ ์ ์ ๊ณตํ๋ค.
- ๋์์ธ ์ ์ฐ์ฑ: Flutter๋ ๋ชจ๋ ํฝ์ ์ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์์ด ๋์์ธ ์๊ตฌ์ฌํญ์ด ์ ๊ตํ ์ํฉ์์ ํจ์ฌ ์ ๋ฆฌํ๋ค. ๋ํ, Flutter์ ์์ ฏ ๊ธฐ๋ฐ ์ธํฐํ์ด์ค๋ ์์ ๋ก์ด ๋์์ธ ๊ตฌํ์ ๊ฐ๋ฅํ๊ฒ ํด ์ค๋ค.
React Native
- ๋ค์ดํฐ๋ธ ์ปดํฌ๋ํธ ์ฌ์ฉ: React Native๋ JavaScript๋ฅผ ์ฌ์ฉํ์ฌ ์ด์์ฒด์ ์ ์ํธ์์ฉํ๋ฉฐ, ์ด์์ฒด์ ๊ฐ ์ ๊ณตํ๋ ๋ค์ดํฐ๋ธ ์ปดํฌ๋ํธ์ ์์ ฏ์ ์์ฑํ๋ค. ์ด๋ก ์ธํด iOS์ Android์์ ๊ฐ๊ฐ ๋ค๋ฅธ ๋ชจ์์ UI ์์๊ฐ ์์ฑ๋ ์ ์๋ค. ์ด๋ ์ด์์ฒด์ ์ ์คํ์ผ์ ํ์ฉํ ๋ ์ ๋ฆฌํ์ง๋ง, ๋์์ธ ์ผ๊ด์ฑ ์ ์ง์ ๋ถ๋ฆฌํ ์ ์๋ค.
- ์ฑ๋ฅ: React Native๋ JavaScript๋ก ์์ฑ๋ ์ฝ๋๋ฅผ ๋ค์ดํฐ๋ธ ์ฝ๋์ ์ํธ์์ฉํ๊ธฐ ์ํด ๋ธ๋ฆฌ์ง(Bridge)๋ผ๋ ์ค๊ฐ ๊ณ์ธต์ ์ฌ์ฉํ๋ฉฐ, ์ด ๊ณผ์ ์์ ์ฑ๋ฅ ์ ํ๊ฐ ๋ฐ์ํ ์ ์๋ค.
- ์ปค๋ฎค๋ํฐ ๋ฐ ์ํ๊ณ: React Native๋ JavaScript๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๊ธฐ ๋๋ฌธ์ ํ๋ถํ ์ปค๋ฎค๋ํฐ์ ์ํ๊ณ๋ฅผ ๊ฐ์ง๊ณ ์๋ค. ๋ค์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ ์ ์๋ ์ฅ์ ์ด ์์ง๋ง, ์ ์ง๋ณด์์ ๋ ๋ง์ ๋ ธ๋ ฅ์ด ๋ค์ด๊ฐ๋ ๊ฒ์ ์๋ฏธํ๊ธฐ๋ ํ๋ค.
DevTools
Flutter Inspector
ํฌ๋กฌ ๊ฐ๋ฐ์ ๋๊ตฌ์ ์์ ํญ์ฒ๋ผ Flutter๋ UI๋ฅผ ์๊ฐ์ ์ผ๋ก ๋ถ์ํ๊ณ ์ดํดํ ๋ ์ฌ์ฉํ๋ Flutter Inspector๋ผ๋ ๋๊ตฌ๋ฅผ ์ ๊ณตํ๋ค. ๊ฐ ์์ ฏ์ ์์ฑ, ๋ ์ด์์, ๋ ๋๋ง ๋ฑ์ ์๊ฐ์ ์ผ๋ก ํ์ธํ ์ ์๋ค.
์ผ์ชฝ ๋์ ์๋ Select Widget Mode ๋ฒํผ์ ์ฒดํฌํ๋ฉด ์๋ฎฌ๋ ์ดํฐ์ ํน์ ์์ ฏ์ ํด๋ฆญํด์ ์ ํํ ์ ์๋ค. ํฌ๋กฌ ๊ฐ๋ฐ์ ๋๊ตฌ์ ์์ ์ ํ ๊ธฐ๋ฅ๊ณผ ๋น์ทํ๋ค.
์ฐ์ธก ์๋จ ๋ฉ๋ด ๋ชจ์ 2๋ฒ์งธ ๋ฒํผ์ ์ฒดํฌํ๋ฉด ์๋ฎฌ๋ ์ดํฐ์ ์ค๋ฒ๋ ์ด ๊ฐ์ด๋๋ผ์ธ์ด ํ์๋๋ค. ์ด ๊ธฐ๋ฅ์ ํตํด UI ์์๋ค์ด ๊ทธ๋ฆฌ๋์ ๋ง์ถฐ์ ธ ์๋์ง, ์ ๋ ฌ์ ์ ๋๋ก ๋๋์ง ๋ฑ์ ์๊ฐ์ ์ผ๋ก ํ์ธํ ์ ์๋ค.
์๋ ํฌ๋งคํ
์๋๋ก์ด๋ ์คํ๋์ค Settings - Languages & Frameworks - Flutter ๋ฉ๋ด์์ ์๋ ๋ ํญ๋ชฉ์ ์ฒดํฌํ๋ฉด ์ ์ฅํ ๋๋ง๋ค ์๋์ผ๋ก ํฌ๋งคํ ํด ์ฃผ๊ณ import ๊ตฌ๋ฌธ์ ์ ๋ฆฌํด ์ค๋ค.
์ฝ๋ ์ก์
๋ง์ฐ์ค ์ปค์๋ฅผ ์์ ฏ์ ๋๊ณ โฅ
Enter
๋ฒํผ์ ํด๋ฆญํ๋ฉด ํด๋น ์์ ฏ์ ์ฌ์ฉํ ์ ์๋ ์ฝ๋ ์ก์
๋ชฉ๋ก์ด ํ์๋๋ค. ์ ํํ ์์ ฏ์ ๋ค๋ฅธ ์์ ฏ์ผ๋ก ๊ฐ์ธ๊ฑฐ๋, ์ด๋ฏธ ๊ฐ์ธ์ ธ ์๋ ์์ ฏ์ ์ ๊ฑฐํ๋ ๋ฑ์ ๋ค์ํ ์ก์
์ ์ ๊ณตํ๋ค.
VSCode๋ ์ ์ฅํ ๋ const
์์ ์๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํด ์ฃผ๋ ๊ธฐ๋ฅ์ด ์์ง๋ง ์์ฝ๊ฒ๋ ์๋๋ก์ด๋ ์คํ๋์ค๋ ๊ทธ๋ฐ ๊ธฐ๋ฅ์ด ์๋ค. const
์์ ์ ์ญ์ ์ฝ๋ ์ก์
์ ํตํด์ ์ถ๊ฐํ ์ ์๋ค.
๐ก ์ฝ๋ ์ก์ ๋ชฉ๋ก์ด ํ์๋ ์ํ์์ ํค์๋๋ฅผ ์ ๋ ฅํ๋ฉด ์ํ๋ ์ก์ ์ ์ง์ ๊ฒ์ํ ์ ์๋ค.
์์ ฏ / ๋ฉ์๋ ์ถ์ถ
โฅ
โ
W
๋จ์ถํค๋ฅผ ๋๋ฅด๊ฑฐ๋, ์ถ์ถํ๊ณ ์ถ์ ๊ณณ์์ ๋ง์ฐ์ค ์ฐํด๋ฆญ → Refactor → Extract Flutter Widget ๋ฉ๋ด๋ฅผ ํด๋ฆญํ๋ฉด ํ์ฌ ์ปค์๊ฐ ๊ฐ๋ฆฌํค๋ ์์ ฏ์ ๋ณ๋์ ํด๋์ค๋ก ์ถ์ถํ ์ ์๋ค. Extract Method ๋ฉ๋ด๋ฅผ ํด๋ฆญํ๋ฉด ์ ํ๋ ์ฝ๋๋ฅผ ๋ฉ์๋๋ก ์ถ์ถํ๋ค.
์ฐธ๊ณ ๋ก โ
T
๋จ์ถํค๋ฅผ ๋๋ฅด๋ฉด ๋์ค๋ Refactor This ๋ฉ๋ด์์ ์ํ๋ ๋ฆฌํฉํ ๋ง ํญ๋ชฉ์ ์ง์ ์ ํํ ์๋ ์๋ค.
Flutter ๊ธฐ์ด ๋ด์ฉ ์ ๋ฆฌ
Hello World
// lib/main.dart
import 'package:flutter/material.dart';
void main() {
runApp(App());
}
// StatelessWidget์ ์์๋ฐ์ ์ํ๊ฐ ์๋ Flutter ์ ํ๋ฆฌ์ผ์ด์
์ UI ์์๋ฅผ ์ ์ํ๋ค.
class App extends StatelessWidget {
@override
// build ๋ฉ์๋๋ ์์ ฏ์ UI๋ฅผ ์์ฑํ๋ค. Flutter๊ฐ ์ด ๋ฉ์๋๋ฅผ ํธ์ถํด์ UI๋ฅผ ๊ตฌ์ฑํ๋ค
Widget build(BuildContext context) {
// MaterialApp์ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ฃจํธ ์์ ฏ์ผ๋ก ์ฑ์ ์ ๋ฐ์ ์ธ ๋์์ธ, ํ
๋ง, ๋ผ์ฐํ
๋ฑ์ ๊ด๋ฆฌํ๋ค
return MaterialApp(
// Scaffold ์์ ฏ์ ํ๋ฉด์ ๊ธฐ๋ณธ์ ์ธ ๋ ์ด์์์ ์ ๊ณตํ๋ฉฐ, Material ๋์์ธ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฅธ๋ค
// ์ฑ๋ฐ, ๋ณธ๋ฌธ, ํ๋กํ
์ก์
๋ฒํผ ๋ฑ์ ํฌํจํ ์ ์๋ค.
home: Scaffold(
// AppBar ์์ ฏ์ ์๋จ ํค๋ ์์ญ์ ์ ์ํ๋ค.
appBar: AppBar(
// Text ์์ ฏ์ ์ฌ์ฉํด ํ๋ฉด์ ํ
์คํธ๋ฅผ ํ์ํ๋ค.
title: Text('Hello Flutter'),
),
body: Center(
// Center ์์ ฏ์ ์์ ์์ ฏ์ ์ค์์ ๋ฐฐ์นํ๋ค.
child: Text('Hello World'),
),
),
);
}
}
โ ํ๋ฌํฐ๋ ๋ชจ๋ ๊ฒ์ด ์์ ฏ์ผ๋ก ๊ตฌ์ฑ๋ผ ์๋ค. ๋ ๊ณ ์ฒ๋ผ ์์ ฏ์ ์กฐ๋ฆฝํ์ฌ UI๋ฅผ ๊ตฌ์ถํ๋ค.
โก ์์ ฏ์ Flutter์์ ์ ๊ณตํ๋ ๊ฒ๊ณผ ์ปค๋ฎค๋ํฐ์์ ์ ์๋ ๊ฒ๋ค์ ์ฌ์ฉํ ์ ์๋ค.
โข ์์ ฏ์ ํฌ๊ฒ ์ํ๋ฅผ ๊ฐ์ง์ง ์๋ StatelessWidget๊ณผ ์ํ๋ฅผ ๊ฐ์ง๋ StatefulWidget์ผ๋ก ๋๋๋ค.
โฃ ๋ฃจํธ ์์ ฏ์ ๋ณดํต MaterialApp์ด๋ CupertinoApp์ ์ฌ์ฉํ๋ค.
- MaterialApp: ๊ตฌ๊ธ์ Material Design ๊ธฐ๋ฐ (์ผ๋ฐ์ ์ผ๋ก ๋ง์ด ์ฌ์ฉ)
- CupertinoApp: Apple์ iOS ๋์์ธ ์ธ์ด์ธ Cupertino ์คํ์ผ ๊ธฐ๋ฐ
โค ์์ ฏ ์ข ๋ฅ๊ฐ ์๋ ๋ค์ํด์ ๋งค๋ฒ ๋ฌธ์๋ฅผ ์ฐพ์๋ณด๊ธฐ ๋ฒ๊ฑฐ๋กญ๊ธฐ ๋๋ฌธ์ ํดํ(๋น ๋ฅธ ๋ฌธ์)์ ์ ๊ทน ํ์ฉํ๋ค.
โฅ ์์ฑ ๊ฐ์ ์ ๋ ฅํ ๋ ์ฝ๋ ์ ์ ๋จ์ถํค๋ฅผ ํ์ฉํ๋ฉด ๋ ํธํ๊ฒ ์์ฑํ ์ ์๋ค.
โฆ ํ๋ผ๋ฏธํฐ ๋ชฉ๋ก ๋์ ํธ๋ ์ผ๋ง ์ฝค๋ง๋ฅผ ๋ถ์ด๊ณ ์ ์ฅํ๋ฉด ์๋์ผ๋ก ์ค ๋ฐ๊ฟ ๋ผ์ ๋ค์ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ํธํด์ง๋ค.
Stateless Widgets
Header
// lib/main.dart
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
// ์ปค์คํ
์์์ ํํํ๊ธฐ ์ํด ์ฌ์ฉํ๋ Color ํด๋์ค
backgroundColor: Color(0xFF181818),
// ํ๋ฉด ์์ชฝ ์ฌ๋ฐฑ์ ์ฃผ๋ Padding ์์ ฏ
body: Padding(
// ์ํ, ์์ง ๊ฐ๊ฐ ์ฌ๋ฐฑ์ ์ค ์ ์๋ EdgeInsets.symmetric ์์ฑ์
padding: EdgeInsets.symmetric(horizontal: 40),
child: Column(children: [
// ํน์ ํฌ๊ธฐ์ ๋น ๊ณต๊ฐ์ ๋ง๋๋ SizedBox ์์ ฏ
SizedBox(height: 80),
// ์์๋ค์ ๊ฐ๋ก(์ํ)๋ก ๋ฐฐ์นํ ๋ ์ฌ์ฉํ๋ Row ์์ ฏ
// mainAxisAlignment: ์ํ ์ ๋ ฌ, crossAxisAlignment: ์์ง ์ ๋ ฌ
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
// ์์๋ค์ ์ธ๋ก(์์ง)๋ก ๋ฐฐ์นํ ๋ ์ฌ์ฉํ๋ Column ์์ ฏ
// crossAxisAlignment: ์ํ ์ ๋ ฌ, mainAxisAlignment: ์์ง ์ ๋ ฌ
Column(crossAxisAlignment: CrossAxisAlignment.end, children: [
Text(
'Hey, Selena',
style: TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.w800,
),
),
Text(
'Welcome back',
style: TextStyle(
// ๊ธฐ์กด ์์์ ํฌ๋ช
๋๋ฅผ ์ถ๊ฐํ๋ withOpacity ๋ฉ์๋
color: Colors.white.withOpacity(0.8),
fontSize: 18,
),
),
])
])
]),
),
),
);
}
}
Color
: ์์์ ํํํ๊ธฐ ์ํ ํด๋์ค. 32๋นํธ ์ ์๋ฅผ ์ฌ์ฉํ์ฌ ์์ ์ ์.Color.fromARGB(int a, int r, int g, int b)
Alpha, Red, Green, Blue ๊ฐ์ ๊ฐ๋ณ์ ์ผ๋ก ์ง์ ํ์ฌ ์์ ์์ฑColor.fromRGBO(int r, int g, int b, double opacity)
RGB ๊ฐ๊ณผ ํฌ๋ช ๋๋ฅผ ์ง์ ํ์ฌ ์์ ์์ฑ
Padding
: ์์ชฝ ์ฌ๋ฐฑ์ ์ถ๊ฐํ๋ ์์ ฏ.padding
์์ฑ์EdgeInsets
๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ฐฑ ๊ฐ ์ง์ EdgeInsets
: ์ฌ๋ฐฑ์ ์ ์ํ๋ ๋ฐ ์ฌ์ฉํ๋ ํด๋์คEdgeInsets.all(double value)
์ํ์ข์ฐ ๋์ผํ ํฌ๊ธฐ์ ์ฌ๋ฐฑ ์ ์ฉEdgeInsets.symmetric({double vertical, double horizontal}):
์ํ ๋ฐ ์์ง ๋ฐฉํฅ์ ๊ฐ๊ฐ ๋ค๋ฅธ ํฌ๊ธฐ์ ์ฌ๋ฐฑ ์ ์ฉEdgeInsets.only({double left, double top, double right, double bottom})
๊ฐ ๋ฐฉํฅ์ ๊ฐ๋ณ์ ์ผ๋ก ์ฌ๋ฐฑ ์ ์ฉEdgeInsets.zero
๋ชจ๋ ๋ฐฉํฅ์ 0 ํฌ๊ธฐ์ ์ฌ๋ฐฑ ์ ์ฉ
SizedBox
: ํน์ ํฌ๊ธฐ์ ๋น ๊ณต๊ฐ์ ๋ง๋๋ ์์ ฏRow
: ์์๋ค์ ๊ฐ๋ก(์ํ)๋ก ๋ฐฐ์นํ ๋ ์ฌ์ฉํ๋ ์์ ฏmainAxisAlignment
์์ฑ : ์ํ ์ ๋ ฌ ๋ฐฉ์ ์ง์ crossAxisAlignment
์์ฑ : ์์ง ์ ๋ ฌ ๋ฐฉ์ ์ง์
Column
: ์์๋ค์ ์ธ๋ก(์์ง)๋ก ๋ฐฐ์นํ ๋ ์ฌ์ฉํ๋ ์์ ฏmainAxisAlignment
์์ฑ : ์์ง ์ ๋ ฌ ๋ฐฉ์ ์ง์ crossAxisAlignment
์์ฑ : ์ํ ์ ๋ ฌ ๋ฐฉ์ ์ง์
Text
: ํ ์คํธ๋ฅผ ํ๋ฉด์ ํ์ํ ๋ ์ฌ์ฉํ๋ ๊ธฐ๋ณธ์ ์ธ ์์ ฏdata
(์ฒซ ๋ฒ์งธ ์ธ์) : ํ๊ธฐํ ํ ์คํธ ๋ฌธ์์ดstyle
์์ฑ : ํ ์คํธ ์คํ์ผ ์ ์. ์คํ์ผ ์ ์์TextStyle
๊ฐ์ฒด ์ฌ์ฉcolor
์์ฑ : ํ ์คํธ ์์ ์ง์ e.g.,Colors.black
fontWeight
์์ฑ : ํ ์คํธ ๊ตต๊ธฐ ์ง์ e.g.,FontWeight.bold
fontSize
์์ฑ : ํ ์คํธ ํฌ๊ธฐ ์ง์
textAlign
์์ฑ : ํ ์คํธ ์ ๋ ฌ ์ง์ e.g.,TextAlign.left
overflow
์์ฑ : ์์ญ ์ด๊ณผ ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง ๊ฒฐ์ e.g.,TextOverflow.ellipsis
withOpacity
: ๊ธฐ์กด ์์์ ํฌ๋ช ๋๋ฅผ ์ถ๊ฐ/์์ ํ๋ ๋ฉ์๋. 0.0~1.0 ์ฌ์ด์ ๊ฐ ์ฌ์ฉ
Button
// lib/main.dart
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Color(0xFF181818),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 40),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
// ...์๋ต
Row(children: [
// div ํ๊ทธ์ฒ๋ผ ์ฝํ
์ธ ๋ฅผ ๊ฐ์ธ๋ Container ์์ ฏ
// decoration ์์ฑ์ ํตํด Container๋ฅผ ์คํ์ผ๋งํ ์ ์๋ค
Container(
// ์ปจํ
์ด๋ ์์ ฏ์ ๋์์ธํ ๋ ์ฌ์ฉํ๋ ๋ฐ์ฝ๋ ์ด์
ํด๋์ค BoxDecoration
decoration: BoxDecoration(
color: Colors.amber,
// ์์ ฏ์ ๋ชจ์๋ฆฌ๋ฅผ ๋ฅ๊ธ๊ฒ ๋ง๋ค ๋ ์ฌ์ฉํ๋ BorderRadius ํด๋์ค
borderRadius: BorderRadius.circular(45),
),
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 15,
horizontal: 50,
),
child: Text('Transfer', style: TextStyle(fontSize: 22)),
),
)
])
]),
),
),
);
}
}
Container
: ์ฝํ ์ธ ๋ฅผ ๊ฐ์ธ์ ๋ ์ด์์๊ณผ ์คํ์ผ๋ง์ ๋์์ ์ฒ๋ฆฌํ ๋ ์ฌ์ฉํ๋ ์์ ฏ(div ํ๊ทธ์ ์ ์ฌ)padding
์์ฑ : ์์ชฝ ์ฌ๋ฐฑ ์ง์ .EdgeInsets
ํด๋์ค๋ก ๊ฐ ์ค์ decoration
์์ฑ : ์ปจํ ์ด๋ ์คํ์ผ ์ง์ .BoxDecoration
ํด๋์ค๋ก ๊ฐ ์ค์ clipBehavior
์์ฑ : ๋ถ๋ชจ ๊ฒฝ๊ณ๋ฅผ ๋์ด์ ์์ ์์ ฏ์ ๋์น ๋ถ๋ถ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง ์ง์
BoxDecoration
:Container
์์ ฏ์ ์คํ์ผ๋งํ ๋ ์ฌ์ฉํ๋ ๋ฐ์ฝ๋ ์ด์ ํด๋์คBorderRadius
: ์์ ฏ์ ๋ชจ์๋ฆฌ๋ฅผ ๋ฅ๊ธ๊ฒ ๋ง๋ค ๋ ์ฌ์ฉํ๋ ํด๋์คBorderRadius.circular(double radius)
- ๋ชจ๋ ๋ชจ์๋ฆฌ๋ฅผ ๋์ผํ ๋ฐ์ง๋ฆ์ผ๋ก ๋ฅ๊ธ๊ฒ ์ค์
BorderRadius.all(Radius radius)
- ๋ชจ๋ ๋ชจ์๋ฆฌ๋ฅผ ๋์ผํ Radius ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ๋ฅ๊ธ๊ฒ ์ค์
BorderRadius.only({Radius topLeft, Radius topRight, Radius bottomLeft, Radius bottomRight})
- ๊ฐ ๋ชจ์๋ฆฌ๋ฅผ ๊ฐ๋ณ์ ์ผ๋ก ๋ฅ๊ธ๊ฒ ์ค์
BorderRadius.horizontal({Radius left, Radius right})
- ์ํ ๋ฐฉํฅ(์ข/์ฐ)์ผ๋ก ๋ฅ๊ธ๊ฒ ์ค์
BorderRadius.vertical({Radius top, Radius bottom})
- ์์ง ๋ฐฉํฅ(์/ํ)์ผ๋ก ๋ฅ๊ธ๊ฒ ์ค์
const ํค์๋
const
ํค์๋๋ฅผ ์ฌ์ฉํ๋ฉด ํด๋น ๊ฐ์ฒด๊ฐ ์ปดํ์ผ ํ์์ ์์๋ก ํ๊ฐ๋๋ค. ์ด๋ ํด๋น ๊ฐ์ฒด๊ฐ ๋ฉ๋ชจ๋ฆฌ์ ํ ๋ฒ๋ง ํ ๋น๋๊ณ , ํ๋ก๊ทธ๋จ ์คํ ์ค์๋ ์ ๋ ๋ณ๊ฒฝ๋์ง ์๋ ๊ฒ์ ์๋ฏธํ๋ค. Flutter์์ ์์ ฏ์ ์ฃผ๋ก ๋ถ๋ณ์ฑ์ ๊ฐ์ง๊ธฐ ๋๋ฌธ์ const
ํค์๋๋ฅผ ํ์ฉํด ์ฑ๋ฅ์ ์ต์ ํํ ์ ์๋ค.
const Text('Hello World');
์ ์ฝ๋์์ Text
์์ ฏ์ const
ํค์๋๋ก ์ ์ธํด์ ๋ฉ๋ชจ๋ฆฌ์ ํ ๋ฒ๋ง ์์ฑ๋๋ค. ๋์ผํ ๋ด์ฉ์ ๊ฐ์ง ๋ ๋ค๋ฅธ const Text('Hello World')
์์ ฏ์ด ์๋ค๋ฉด ์ด์ ์ ์์ฑ๋ ์์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฌ์ฉํ๋ค. ์ํ ๋ณํ๋ก ์์ ฏ ํธ๋ฆฌ๊ฐ ์ฌ๊ตฌ์ฑ๋ ๋(build ๋ฉ์๋ ํธ์ถ)๋ ์ด์ ์ ์์ฑํ๋ ๋ด์ฉ์ ์ฌ์ฌ์ฉํ๋ค.
ํ์ง๋ง ์ฌ์ฉ์ ์
๋ ฅ๊ฐ, ์ํ ๋ณํ, withOpacity
๋ฉ์๋์ฒ๋ผ ๋ฐํ์์ ๊ฒฐ์ ๋๋ ๊ฐ์ด ํฌํจ๋์ด ์์ผ๋ฉด const
ํค์๋๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
const
ํค์๋๊ฐ ์ ์ฉ๋ ์ ์๋ ๊ณณ์ ์ผ์ผ์ด ํ์
ํ๊ธฐ ํ๋ค๊ธฐ ๋๋ฌธ์ IDE์์ ์ ๊ณตํ๋ ์ฝ๋ ์ก์
์ด๋ ๋ฆฌํฉํ ๋ง ๋๊ตฌ๋ฅผ ์ฌ์ฉํด์ ์๋์ผ๋ก ์ถ๊ฐํ ์ ์๋ค.
๐ก ์์ ฏ ํธ๋ฆฌ์์ ์์ ์์ ฏ์ const
ํค์๋๋ฅผ ๋ถ์ด๋ฉด, ํ์์ ์๋ ์์ ฏ๋ค ์ค const
์์ฑ์๋ฅผ ๊ฐ์ง ์์ ฏ๋ค์(Text, Container ์์ ฏ ๋ฑ) ์๋์ผ๋ก const ์ฒ๋ฆฌ๋๋ค.
Reusable Button
๋ฆฌ์กํธ์์ ๊ณตํต ์ปดํฌ๋ํธ๋ฅผ ์ ์ํด์ ์ฌ์ฌ์ฉํ๋ ๊ฒ์ฒ๋ผ, Flutter์์๋ ์์ฃผ ์ฌ์ฉํ๋ UI ์์๋ฅผ ์ฌ์ฌ์ฉํ ์ ์๋ ์์ ฏ์ผ๋ก ๋ง๋ค์ด์ ๊ด๋ฆฌํ ์ ์๋ค.
// lib/widgets/button.dart
class Button extends StatelessWidget {
final String text;
final Color bgColor, textColor;
// ์์ฑ์ ์ด๊ธฐํ
const Button({
super.key, // ๋ถ๋ชจ ํด๋์ค์ธ StatelessWidget์ key ์ ๋ฌ
required this.text,
required this.bgColor,
required this.textColor,
});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: bgColor, // ์ง์ ๋ ๋ฐฐ๊ฒฝ์ ์ฌ์ฉ
borderRadius: BorderRadius.circular(45),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 20,
horizontal: 45,
),
child: Text(text, // ์ง์ ๋ ๋ผ๋ฒจ ํ
์คํธ ์ฌ์ฉ
style: TextStyle(
fontSize: 20,
color: textColor, // ์ง์ ๋ ํ
์คํธ ์์ ์ฌ์ฉ
)),
),
);
}
}
๊ณตํต ์์ ฏ ์ฌ์ฉ ์์ โผ
// lib/main.dart
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: const Color(0xFF181818),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
// ...์๋ต
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// ๊ณตํต ์์ ฏ ์ฌ์ฌ์ฉ
Button(
text: 'Transfer',
bgColor: Color(0xFFF1B33B),
textColor: Colors.black,
),
Button(
text: 'Request',
bgColor: Color(0xFF1F2123),
textColor: Colors.white,
),
])
]),
),
),
);
}
}
Reusable Card
๐ก Dart/Flutter ๋ค์ด๋ฐ ๊ท์น
- ํ์ผ/ํด๋๋ช : snake_case
- ํด๋์ค : PascalCase
- ๋ณ์/ํจ์๋ช : camelCase
// lib/widgets/currency_card.dart
class CurrencyCard extends StatelessWidget {
final String name, code, amount;
final IconData icon;
final bool isInverted;
final double order;
final _blackColor = const Color(0xFF1F2123);
final _baseOffset = -20;
const CurrencyCard({
super.key,
required this.name,
required this.code,
required this.amount,
required this.icon,
required this.isInverted,
required this.order,
});
@override
Widget build(BuildContext context) {
// ์์ ฏ์ ์์น๋ฅผ ํน์ ๋ฐฉํฅ์ผ๋ก ์ด๋์ํฌ ๋ ์ฌ์ฉํ๋ Transform.translate
// ๋ถ๋ชจ ์์ ฏ์ ํฌ๊ธฐ๋ ๋ ์ด์์์ ์ํฅ์ ์ฃผ์ง ์์
return Transform.translate(
// ์ขํ๋ ๊ฑฐ๋ฆฌ ๋ฑ์ ๋ํ๋ด๊ธฐ ์ํด ์ฌ์ฉํ๋ Offset ํด๋์ค
offset: Offset(0, (order - 1) * _baseOffset),
child: Container(
// ์์ ฏ์ ์๋ฆผ ๋ฐฉ์์ ์ ์ํ ๋ ์ฌ์ฉํ๋ clipBehavior ์์ฑ
// Clip.hardEdge๋ ์์ ฏ์ ๊ฒฝ๊ณ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ฆ์ ์๋ผ๋ด๋ ๋ฐฉ์
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: isInverted ? Colors.white : _blackColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20), topRight: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(30),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// ...์๋ต
// ์์ ์์ ฏ์ ํฌ๊ธฐ๋ฅผ ํ๋/์ถ์ํ ๋ ์ฌ์ฉํ๋ Transform.scale
// ์์ ์์ ฏ์ ํฌ๊ธฐ๋ง ๋ณ๊ฒฝํ๊ณ ๋ ์ด์์์๋ ์ํฅ ์์ค
Transform.scale(
scale: 2.2,
child: Transform.translate(
offset: const Offset(-5, 12),
// ์์ด์ฝ์ ํ์ํ๊ธฐ ์ํด ์ฌ์ฉํ๋ Icon ์์ ฏ
child: Icon(
icon,
color: isInverted ? _blackColor : Colors.white,
size: 88,
),
))
]),
)),
);
}
}
// lib/widgets/currency_card.dart
class CurrencyCard extends StatelessWidget {
final String name, code, amount;
final IconData icon;
final bool isInverted;
final double order;
final _blackColor = const Color(0xFF1F2123);
final _baseOffset = -20;
const CurrencyCard({
super.key,
required this.name,
required this.code,
required this.amount,
required this.icon,
required this.isInverted,
required this.order,
});
@override
Widget build(BuildContext context) {
// ์์ ฏ์ ์์น๋ฅผ ํน์ ๋ฐฉํฅ์ผ๋ก ์ด๋์ํฌ ๋ ์ฌ์ฉํ๋ Transform.translate
// ๋ถ๋ชจ ์์ ฏ์ ํฌ๊ธฐ๋ ๋ ์ด์์์ ์ํฅ์ ์ฃผ์ง ์์
return Transform.translate(
// ์ขํ๋ ๊ฑฐ๋ฆฌ ๋ฑ์ ๋ํ๋ด๊ธฐ ์ํด ์ฌ์ฉํ๋ Offset ํด๋์ค
offset: Offset(0, (order - 1) * _baseOffset),
child: Container(
// ์์ ฏ์ ์๋ฆผ ๋ฐฉ์์ ์ ์ํ ๋ ์ฌ์ฉํ๋ clipBehavior ์์ฑ
// Clip.hardEdge๋ ์์ ฏ์ ๊ฒฝ๊ณ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ฆ์ ์๋ผ๋ด๋ ๋ฐฉ์
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: isInverted ? Colors.white : _blackColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20), topRight: Radius.circular(20)),
),
child: Padding(
padding: const EdgeInsets.all(30),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name,
style: TextStyle(
color: isInverted ? _blackColor : Colors.white,
fontSize: 32,
fontWeight: FontWeight.w600,
)),
const SizedBox(height: 10),
Row(
children: [
Text(amount,
style: TextStyle(
color:
isInverted ? _blackColor : Colors.white,
fontSize: 20,
)),
const SizedBox(width: 5),
Text(code,
style: TextStyle(
color: isInverted
? _blackColor
: Colors.white.withOpacity(0.8),
fontSize: 20,
)),
],
)
]),
// ์์ ์์ ฏ์ ํฌ๊ธฐ๋ฅผ ํ๋/์ถ์ํ ๋ ์ฌ์ฉํ๋ Transform.scale
// ์์ ์์ ฏ์ ํฌ๊ธฐ๋ง ๋ณ๊ฒฝํ๊ณ ๋ ์ด์์์๋ ์ํฅ ์์ค
Transform.scale(
scale: 2.2,
child: Transform.translate(
offset: const Offset(-5, 12),
// ์์ด์ฝ์ ํ์ํ๊ธฐ ์ํด ์ฌ์ฉํ๋ Icon ์์ ฏ
child: Icon(
icon,
color: isInverted ? _blackColor : Colors.white,
size: 88,
),
))
]),
)),
);
}
}
Transform.translate
: ์์ ์์ ฏ์ ์์น๋ฅผ ํน์ ๋ฐฉํฅ์ผ๋ก ์ด๋์ํฌ ๋ ์ฌ์ฉํ๋ ์์ ฏoffset
์์ฑ์Offset
ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ํ x/y์ถ ๊ฐ ์ง์ - ์์ ฏ์ ์ค์ ์์น๋ ๋ณ๊ฒฝ๋์ง ์์ง๋ง, ์๊ฐ์ ์ผ๋ก ์์น๊ฐ ๋ณ๊ฒฝ๋ ๊ฒ์ฒ๋ผ ๋ณด์
- ์ฆ, ์์ ์์ ฏ์ ์๊ฐ์ ์์น๋ง ๋ณ๊ฒฝํ๊ธฐ ๋๋ฌธ์ ๋ถ๋ชจ ์์ ฏ์ ํฌ๊ธฐ๋ ๋ ์ด์์์ ์ํฅ ์ ์ค
Offset(double dx, double dy)
: ์ขํ๋ ๊ฑฐ๋ฆฌ ๋ฑ์ ๋ํ๋ด๊ธฐ ์ํด ์ฌ์ฉํ๋ ํด๋์คClip
: ๋์น ๋ถ๋ถ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง ์ ์ํ ์ด๊ฑฐํ. ์์ ฏ์clipBehavior
์์ฑ์์ ์ฌ์ฉ.Clip.none
: ์๋ฅด๊ธฐ ์ ํจClip.hardEdge
: ๋์น ๋ถ๋ถ์ ์๋ผ๋Clip.antiAlias
: ๋์น ๋ถ๋ถ์ ์๋ผ๋ด๊ณ ๊ฐ์ฅ์๋ฆฌ๋ฅผ ๋ถ๋๋ฝ๊ฒ(์ํฐ ์จ๋ฆฌ์ด์ฑ) ์ฒ๋ฆฌClip.antiAliasWithSaveLayer
: ์๋ฆฐ ๋ถ๋ถ์ ์ ๋ ์ด์ด์ ์ ์ฅํ์ฌ ๋ ๋์ ํ์ง ๋ณด์ฅ
Transform.scale
: ์์ ์์ ฏ์ ํฌ๊ธฐ๋ฅผ ์๊ฐ์ ์ผ๋ก ํ๋/์ถ์ํ ๋ ์ฌ์ฉscale
์์ฑ์ ํตํด ์์ ฏ์ ํฌ๊ธฐ๋ฅผ ๋ช ๋ฐฐ๋ก ํ๋/์ถ์ํ ์ง ์ง์ - ์์ ์์ ฏ์ ์๊ฐ์ ํฌ๊ธฐ๋ง ๋ณ๊ฒฝํ๊ธฐ ๋๋ฌธ์ ๋ถ๋ชจ ์์ ฏ์ ํฌ๊ธฐ๋ ๋ ์ด์์์ ์ํฅ ์ ์ค
Icon
: ์์ด์ฝ์ ํ์ํ ๋ ์ฌ์ฉํ๋ ์์ ฏ.IconData
๊ฐ์ฒด๋ฅผ ๋ฐ์ ํด๋นํ๋ ์์ด์ฝ์ ํ์ํ๋ฉฐ, ์ผ๋ฐ์ ์ผ๋กIcons
ํด๋์ค์ ์ ์๋ ์์ด์ฝ๋ค์ ์ฌ์ฉํจ.
Stateful Widgets
Stateless Widget์ build ๋ฉ์๋๋ฅผ ํตํด ๋จ์ํ UI๋ฅผ ์ถ๋ ฅํ๋ ์ญํ ์ ํ๋ค. ์ํ๊ฐ ๋ณํ์ง ์์ผ๋ฉฐ, ์ธ๋ถ์์ ์ ๊ณต๋ ๋ฐ์ดํฐ์ ๋ฐ๋ผ UI๋ฅผ ๋ ๋๋ง ํ๋ค. ์ํ ๋ณํ๊ฐ ์๊ธฐ ๋๋ฌธ์ build ๋ฉ์๋๊ฐ ํ ๋ฒ ํธ์ถ๋ผ์ ์์ ฏ์ด ๋ ๋๋ง ๋๋ฉด ๊ทธ ์ดํ๋ก๋ ๋ณ๊ฒฝ๋์ง ์๋๋ค.
๋ฐ๋ฉด Stateful ์์ ฏ์ ์ด๋ฆ ๊ทธ๋๋ก ์ํ(state)๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ์ํ์ ๋ฐ๋ผ UI๋ฅผ ๋์ ์ผ๋ก ์ ๋ฐ์ดํธํ ์ ์๋ค. Stateful Widget์ Stateful Widget ํด๋์ค์ State ํด๋์ค๊ฐ ๊ฒฐํฉ๋ ํํ๋ก ๊ตฌ์ฑ๋๋ค. Stateful Widget ํด๋์ค๋ ์์ ฏ์ ์ธํ์ ์ ์ํ๊ณ , State ํด๋์ค๋ ์์ ฏ์ ์ํ์ ์ํ ๋ณํ๋ฅผ ์ฒ๋ฆฌํ๋ค.
State
์ํ๋ฅผ ๊ฐ์ง ํด๋์ค๋ ์๋ ๊ตฌ์กฐ๋ก ์ ์ํ๋ค. State ํด๋์ค๋ ์ํ์ UI๋ฅผ ๋ชจ๋ ๊ด๋ฆฌํ ์ ์์ด์ ์ํ ๋ณํ์ ๋ฐ๋ผ UI๋ฅผ ๋์ ์ผ๋ก ์
๋ฐ์ดํธํ ์ ์๋ค. setState
ํจ์๊ฐ ํธ์ถ๋๋ฉด build
๋ฉ์๋๋ฅผ ๋ค์ ์คํ์์ผ์ ๋ฆฌ๋ ๋๋ง ๋๋ค. setState
๋ง ์ ์์ ์ผ๋ก ํธ์ถ๋๋ค๋ฉด, ์ํ ์
๋ฐ์ดํธ ๋ก์ง์ setState
์ธ๋ถ์ ์ ์ํด๋ ๋ฌด๋ฐฉํ๋ค.
// StatefulWidget์ ์์๋ฐ์ ์ํ๋ฅผ ๊ฐ์ง ์ ์๋ App ํด๋์ค ์ ์
class App extends StatefulWidget {
const App({super.key});
// createState ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ์ฌ App ์์ ฏ์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ State ๊ฐ์ฒด ์์ฑ/๋ฐํ
@override
State<App> createState() => _AppState();
}
// State<App>์ ํ์ฅํ์ฌ App ์์ ฏ์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ _AppState ํด๋์ค ์ ์
class _AppState extends State<App> {
// ์ํ๋ก ์ฌ์ฉํ counter ๋ณ์ ์ ์. ์ํ๋ ํด๋์ค ํ๋๋ก ์ ์ํ๋ค.
int counter = 0;
// ์นด์ดํฐ ์ฆ๊ฐ ํธ๋ค๋ฌ
void onClicked() {
// State ํด๋์ค์๊ฒ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋๋ค๊ณ ์๋ฆฌ๋ ํจ์
// setState ํจ์๊ฐ ํธ์ถ๋๋ฉด build ๋ฉ์๋๋ฅผ ๋ค์ ์คํ์์ผ์ ๋ฆฌ๋ ๋๋ง
setState(() => counter++);
}
@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)),
// counter ๊ฐ์ ์ถ๋ ฅํ๋ ํ
์คํธ ์์ ฏ
Text('$counter', style: const TextStyle(fontSize: 30)),
IconButton(
iconSize: 60,
onPressed: onClicked, // ํธ๋ค๋ฌ ํ ๋น
icon: const Icon(Icons.add_box_rounded),
)
],
),
),
),
);
}
}
๐ก ์ฝ๋ ์ก์ ์ ํตํด ๊ธฐ์กด StatelessWidget์ StatefulWidget์ผ๋ก ๋ณํํ ์๋ ์๋ค.
BuildContext
BuildContext๋ Flutter ์์ ฏ ํธ๋ฆฌ ๋ด์์ ์์ ฏ์ ์์น ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๊ฐ์ฒด๋ค. ๋ชจ๋ ์์ ฏ์ build ๋ฉ์๋๋ฅผ ํตํด ์์ ์ UI๋ฅผ ๊ตฌ์ฑํ๋ฉฐ, ์ด ๋ฉ์๋๋ BuildContext๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๋ฐ์ ํ์ฌ ์์ ฏ์ด ํธ๋ฆฌ์์ ์ด๋ ์์น์ ์๋์ง ํ์ ํ๋ค.
BuildContext๋ฅผ ์ฌ์ฉํ๋ฉด ์์ ฏ ํธ๋ฆฌ ์์์ ์๋ ๋ค๋ฅธ ์์ ฏ๋ค์ ๋ฐ์ดํฐ์ ์ ๊ทผํ ์ ์์ผ๋ฉฐ, Flutter ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ ์ ์ญ์ ์ธ ๋ฐ์ดํฐ(Theme, MediaQuery)์๋ ์ ๊ทผํ ์ ์๋ค. ์ด๋ฌํ ๊ธฐ๋ฅ์ ์์ ฏ ํธ๋ฆฌ ๋ด์์ ์ํ ๊ด๋ฆฌ๋ ์คํ์ผ๋ง, ํ๋ฉด ํฌ๊ธฐ ๋ฑ์ ์ ์ฐํ๊ฒ ๋์ํ ์ ์๋๋ก ๋์์ค๋ค.
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
@override
Widget build(BuildContext context) {
return MaterialApp(
// theme ์์ฑ์ MaterialApp์์ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์
์ ํ
๋ง๋ฅผ ์ ์ํ ๋ ์ฌ์ฉํ๋ค
// ThemeData ๊ฐ์ฒด๋ฅผ ํตํด ์ฑ์์ ๊ณตํต์ ์ผ๋ก ์ ์ฉํ ํ
๋ง ์์ฑ์ ์ค์ ํ๋ค
theme: ThemeData(
// TextTheme ํด๋์ค๋ฅผ ์ฌ์ฉํด ์ฑ์์ ์ฌ์ฉํ ํ
์คํธ ์คํ์ผ์ ์ ์ํ๋ค
textTheme: const TextTheme(
// titleLarge ์์ฑ์ ํฐ ํ์ดํ์ ์ฌ์ฉํ ํ
์คํธ ์คํ์ผ์ ์ง์ ํ๋ค
titleLarge: TextStyle(color: Colors.blueAccent),
),
),
home: const Scaffold(
backgroundColor: Color(0xFFF4EDDB),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [LargeTitle()],
),
),
),
);
}
}
// ์ํ๋ฅผ ๊ฐ์ง์ง ์๋ StatelessWidget
class LargeTitle extends StatelessWidget {
const LargeTitle({super.key});
@override
Widget build(BuildContext context) {
return Text(
'Large Title',
style: TextStyle(
fontSize: 30,
// Theme.of๋ฅผ ํตํด ํ์ฌ ์์ ฏ ํธ๋ฆฌ์ ์์์ ์ ์๋ ThemeData ๊ฐ์ฒด์ ์ ๊ทผํ ์ ์๋ค
// ์๋์์ MaterialApp ํ
๋ง์ ์ ๊ทผํ์ฌ titleLarge ์์์ ์ฌ์ฉํ๊ณ ์๋ค
color: Theme.of(context).textTheme.titleLarge?.color),
);
}
}
Widget Lifecycle
Flutter ์์ ฏ๋ React ์ปดํฌ๋ํธ์ฒ๋ผ ์์ฑ๋ถํฐ ์๋ฉธ๊น์ง์ ์๋ช
์ฃผ๊ธฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ค. StatelessWidget์ ์ํ๋ฅผ ๊ฐ์ง์ง ์๊ธฐ ๋๋ฌธ์ ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋๊ฐ ๋ฐ๋ก ์๊ณ , ํ๋ฉด์ ๊ทธ๋ ค์ง ๋ ํธ์ถ๋๋ build()
๋ฉ์๋๋ง ์กด์ฌํ๋ค. ๋ฐ๋ฉด, StatefulWidget์ ์ํ ๋ณ๊ฒฝ์ ๋ฐ๋ฅธ ๋ค์ํ ์๋ช
์ฃผ๊ธฐ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค.
createState()
- StatefulWidget์ด ์ฒ์ ์์ฑ๋ ๋ ํธ์ถ.
- ์ด ๋จ๊ณ์์ ์์ ฏ์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ State ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
initState()
- State ๊ฐ์ฒด๊ฐ ์ฒ์ ์์ฑ๋ ํ ์์ ฏ์ด ํธ๋ฆฌ์ ์ฝ์ ๋์ ๋ ํธ์ถ.
- ์์ ฏ์ด ์ฒ์ ๋น๋ ๋ ๋ ํ ๋ฒ๋ง ํธ์ถ๋๋ฉฐ, ์ด ๋จ๊ณ์์ ์ด๊ธฐํ ์์ ์ ์ํํ ์ ์๋ค.
- ๋ถ๋ชจ ํด๋์ค ์ด๊ธฐํ๋ฅผ ์ํด ๋ฐ๋์
super.initState()
๋ฅผ ํธ์ถํด์ผ ํ๋ค.
didChangeDependencies()
- ์์ ฏ์ด ์์กดํ๋ inheritedWidget ํน์ ๋ค๋ฅธ ์ข ์์ฑ์ด ๋ณ๊ฒฝ๋ ๋ ํธ์ถ.
initState()
ํธ์ถ ์งํ์ ํ ๋ฒ ํธ์ถ๋๊ณ , ์ดํ๋ถํด ์์ ฏ์ ์์กด์ฑ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ํธ์ถ๋๋ค.
build()
- ์์ ฏ์ UI๋ฅผ ๋ ๋๋ง ํ๋ ๋ฉ์๋.
- ์์ ฏ์ ์ฒ์ ๋น๋ํ ๋์, ์ํ ๋ณ๊ฒฝ ๋ฑ์ผ๋ก ์์ ฏ์ด ๋ค์ ๋น๋๋ ๋๋ง๋ค ํธ์ถ๋๋ค.
didUpdateWidget()
- ๋ถ๋ชจ ์์ ฏ์ด ์ฌ๊ตฌ์ฑ๋์ด ํ์ฌ ์์ ฏ์ ์ค์ ์ด ๋ณ๊ฒฝ๋์ ๋ ํธ์ถ.
- ์ฆ, ๋ถ๋ชจ ์์ ฏ์์ ์ ๋ฌ๋ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์ ๋ ํธ์ถ๋๋ค.
deactivate()
- ์์ ฏ์ด ํธ๋ฆฌ์์ ์ ๊ฑฐ๋๊ธฐ ์ง์ ์ ํธ์ถ.
- ์ด ๋ฉ์๋๊ฐ ํธ์ถ๋ผ๋ State ๊ฐ์ฒด๋ ์ฌ์ ํ ๋ฉ๋ชจ๋ฆฌ์ ๋จ์ ์๋ค.
dispose()
- State ๊ฐ์ฒด๊ฐ ์์ ํ ์๋ฉธ๋๊ธฐ ์ง์ ์ ํธ์ถ.
- ์ด ๋จ๊ณ์์ ๋คํธ์ํฌ ์์ฒญ ์ทจ์, ์ด๋ฒคํธ ๊ตฌ๋ ํด์ ๋ฑ ์ ๋ฆฌ ์์ ์ ์ํํ ์ ์๋ค.
- ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง๋ฅผ ์ํด ๋ฐ๋์
super.dispose()
๋ฅผ ํธ์ถํด์ผ ํ๋ค.
class _LargeTitleState extends State<LargeTitle> {
// ์์ ฏ์ด ํธ๋ฆฌ์ ์ฒ์ ์ฝ์
๋ ๋ ํ ๋ฒ๋ง ํธ์ถ๋๋ค(build ์ด์ ์ ํธ์ถ).
// ์ํ ๋ณ์๊ฐ ์์ ฏ์ ์ปจํ
์คํธ์ ์์กดํ๊ฑฐ๋, ์ ๋๋ฉ์ด์
์ปจํธ๋กค๋ฌ ์ด๊ธฐํ,
// Stream ๊ตฌ๋
๊ณผ ๊ฐ์ ์ด๊ธฐํ ์์
์ด ํ์ํ ๋ ์ฌ์ฉํ๋ค.
@override
void initState() {
// ํญ์ super.initState()๋ฅผ ํธ์ถํ์ฌ ๋ถ๋ชจ ํด๋์ค์ ์ด๊ธฐํ ์์
์ด ๋จผ์ ์ด๋ค์ ธ์ผ ํ๋ค.
super.initState();
print('initState');
}
// dispose ๋ฉ์๋๋ ์์ ฏ์ด ์์ ํ ์ ๊ฑฐ๋๊ธฐ ์ง์ ์ ํธ์ถ๋๋ค.
// API ํธ์ถ ์ทจ์, ์ด๋ฒคํธ ๊ตฌ๋
ํด์ , ์ ๋๋ฉ์ด์
์ปจํธ๋กค๋ฌ ํด์ ๋ฑ ์ ๋ฆฌ ์์
์ ์ฌ์ฉํ๋ค.
@override
void dispose() {
// ๋ฐ๋์ super.dispose()๋ฅผ ํธ์ถํ์ฌ ๋ถ๋ชจ ํด๋์ค์์ ํ์ํ ์ ๋ฆฌ ์์
์ด ์ํ๋๋๋ก ํด์ผ ํ๋ค.
super.dispose();
print('dispose!');
}
// build ๋ฉ์๋๋ ์์ ฏ์ UI๋ฅผ ๋ ๋๋งํ๋ฉฐ, ์์ ฏ ํธ๋ฆฌ์ ์์ ์์๋ค์ ๋ฐํํ๋ค.
// build๋ initState ์ดํ์ ํธ์ถ๋๊ณ , ์ํ๊ฐ ๋ณ๊ฒฝ(setState ํธ์ถ ๋ฑ)๋ ๋๋ง๋ค ํธ์ถ๋๋ค.
// build ๋ฉ์๋๋ ๋ถ์ ํจ๊ณผ ์์ด ์
๋ ฅ๊ฐ์๋ง ์์กดํ๋ ์์ ํจ์์ฒ๋ผ ๋์ํด์ผ ํ๋ค.
@override
Widget build(BuildContext context) {
print('build');
return Text(
'Large Title',
style: TextStyle(
fontSize: 30,
color: Theme.of(context).textTheme.titleLarge?.color,
),
);
}
}
// ์ฝ์ ์ถ๋ ฅ ๊ฒฐ๊ณผ
// flutter: initState
// flutter: build
// flutter: dispose! (LargeTitle ์์ ฏ์ด ์ ๊ฑฐ๋์ ๋)
import 'package:flutter/material.dart';
class LifeCycle extends StatefulWidget {
const LifeCycle({super.key});
@override
State<LifeCycle> createState() => _LifeCycleState();
}
// State
class _LifeCycleState extends State<LifeCycle> {
bool showTitle = true;
List<int> numbers = [];
void toggleTitle() {
setState(() => showTitle = !showTitle);
}
void onClicked() {
// State ํด๋์ค์๊ฒ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋๋ค๊ณ ์๋ฆฌ๋ ํจ์
// setState ํจ์๊ฐ ํธ์ถ๋๋ฉด build ๋ฉ์๋๋ฅผ ๋ค์ ์คํ์์ผ์ ๋ฆฌ๋ ๋๋ง
// ์๊ฐ๋ณด๋ค ํ๋ฌํฐ์์ State๋ฅผ ๋ง์ด ์ฌ์ฉํ์ง๋ ์์(๋ฆฌ์กํธ์ ๋นํ๋ฉด)
setState(() => numbers.add(numbers.length));
}
@override
Widget build(BuildContext context) {
return MaterialApp(
// theme ์์ฑ์ MaterialApp์์ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์
์ ํ
๋ง๋ฅผ ์ ์ํ ๋ ์ฌ์ฉํ๋ค
// ThemeData ๊ฐ์ฒด๋ฅผ ํตํด ์ฑ์์ ๊ณตํต์ ์ผ๋ก ์ ์ฉํ ํ
๋ง ์์ฑ์ ์ค์ ํ๋ค
theme: ThemeData(
// TextTheme ํด๋์ค๋ฅผ ์ฌ์ฉํด ์ฑ์์ ์ฌ์ฉํ ํ
์คํธ ์คํ์ผ์ ์ ์ํ๋ค
textTheme: const TextTheme(
// titleLarge ์์ฑ์ ํฐ ํ์ดํ์ ์ฌ์ฉํ ํ
์คํธ ์คํ์ผ์ ์ง์ ํ๋ค
titleLarge: TextStyle(color: Colors.red),
),
),
home: Scaffold(
backgroundColor: const Color(0xFFF4EDDB),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
showTitle ? const LargeTitle() : const Text('nothing to see'),
IconButton(
iconSize: 100,
onPressed: toggleTitle,
icon: const Icon(Icons.remove_red_eye))
],
),
),
),
);
}
}
class LargeTitle extends StatefulWidget {
const LargeTitle({super.key});
@override
State<LargeTitle> createState() => _LargeTitleState();
}
class _LargeTitleState extends State<LargeTitle> {
// ์์ ฏ์ด ํธ๋ฆฌ์ ์ฒ์ ์ฝ์
๋ ๋ ํ ๋ฒ๋ง ํธ์ถ๋๋ค(build ์ด์ ์ ํธ์ถ).
// ์ํ ๋ณ์๊ฐ ์์ ฏ์ ์ปจํ
์คํธ์ ์์กดํ๊ฑฐ๋, ์ ๋๋ฉ์ด์
์ปจํธ๋กค๋ฌ ์ด๊ธฐํ,
// Stream ๊ตฌ๋
๊ณผ ๊ฐ์ ์ด๊ธฐํ ์์
์ด ํ์ํ ๋ ์ฌ์ฉํ๋ค.
@override
void initState() {
// ํญ์ super.initState()๋ฅผ ํธ์ถํ์ฌ ๋ถ๋ชจ ํด๋์ค์ ์ด๊ธฐํ ์์
์ด ๋จผ์ ์ด๋ค์ ธ์ผ ํ๋ค.
super.initState();
print('initState');
}
// dispose ๋ฉ์๋๋ ์์ ฏ์ด ์์ ํ ์ ๊ฑฐ๋๊ธฐ ์ง์ ์ ํธ์ถ๋๋ค.
// API ํธ์ถ ์ทจ์, ์ด๋ฒคํธ ๊ตฌ๋
ํด์ , ์ ๋๋ฉ์ด์
์ปจํธ๋กค๋ฌ ํด์ ๋ฑ ์ ๋ฆฌ ์์
์ ์ฌ์ฉํ๋ค.
@override
void dispose() {
// ๋ฐ๋์ super.dispose()๋ฅผ ํธ์ถํ์ฌ ๋ถ๋ชจ ํด๋์ค์์ ํ์ํ ์ ๋ฆฌ ์์
์ด ์ํ๋๋๋ก ํด์ผ ํ๋ค.
super.dispose();
print('dispose!');
}
// build ๋ฉ์๋๋ ์์ ฏ์ UI๋ฅผ ๋ ๋๋งํ๋ฉฐ, ์์ ฏ ํธ๋ฆฌ์ ์์ ์์๋ค์ ๋ฐํํ๋ค.
// build๋ initState ์ดํ์ ํธ์ถ๋๊ณ , ์ํ๊ฐ ๋ณ๊ฒฝ(setState ํธ์ถ ๋ฑ)๋ ๋๋ง๋ค ํธ์ถ๋๋ค.
// build ๋ฉ์๋๋ ๋ถ์ ํจ๊ณผ ์์ด ์
๋ ฅ๊ฐ์๋ง ์์กดํ๋ ์์ ํจ์์ฒ๋ผ ๋์ํด์ผ ํ๋ค.
@override
Widget build(BuildContext context) {
print('build');
return Text(
'Large Title',
style: TextStyle(
fontSize: 30,
// Theme.of๋ฅผ ํตํด ํ์ฌ ์์ ฏ ํธ๋ฆฌ์ ์์์ ์ ์๋ ThemeData ๊ฐ์ฒด์ ์ ๊ทผํ ์ ์๋ค
// ์๋์์ MaterialApp ํ
๋ง์ ์ ๊ทผํ์ฌ titleLarge ์์์ ์ฌ์ฉํ๊ณ ์๋ค
color: Theme
.of(context)
.textTheme
.titleLarge
?.color,
),
);
}
}
// ์ฝ์ ์ถ๋ ฅ ๊ฒฐ๊ณผ
// flutter: initState
// flutter: build
// flutter: dispose! (LargeTitle ์์ ฏ์ด ์ ๊ฑฐ๋์ ๋)
Flexible / Expanded
CSS์์ ํ๋ฉด ๋ ์ด์์์ ๊ตฌ์ฑํ ๋ ์ ์ฐ์ฑ์ ์ ๊ณตํ๋ flex์ฒ๋ผ ํ๋ฌํฐ๋ ์ด์ ๋น์ทํ Flexible
์์ ฏ์ ์ ๊ณตํ๋ค. Flexible
์์ ฏ์ ์ฃผ๋ก Row
, Column
์์ ฏ ๋ด์์ ์ฌ์ฉํ์ฌ ์์ ์์ ฏ์ด ์ฐจ์งํ๋ ๊ณต๊ฐ์ ์กฐ์ ํ๋ค.
class _HomeScreenState extends State<HomeScreen> {
// ...
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: Column(
children: [
Flexible(
flex: 1, // ์ ์ฒด ๋จ์ ๊ณต๊ฐ์์ 1 ๋น์จ๋งํผ ์ฐจ์ง
child: Container(
decoration: const BoxDecoration(color: Colors.red),
),
),
Flexible(
flex: 2, // ์ ์ฒด ๋จ์ ๊ณต๊ฐ์์ 2 ๋น์จ๋งํผ ์ฐจ์ง
child: Container(
decoration: const BoxDecoration(color: Colors.green),
),
),
Flexible(
flex: 1, // ์ ์ฒด ๋จ์ ๊ณต๊ฐ์์ 1 ๋น์จ๋งํผ ์ฐจ์ง
child: Container(
decoration: const BoxDecoration(color: Colors.blue),
),
),
],
),
);
}
}
Flexible
์์ ฏ fit
์์ฑ์ FlexFit.tight
๊ฐ์ ์ฌ์ฉํ๋ฉด ์์ ์์ ฏ์ด ๋จ์ ๊ณต๊ฐ์ ๋ชจ๋ ์ฑ์ฐ๋๋ก ๋ง๋ ๋ค. Expanded
์์ ฏ์ FlexFit.tight
๋ก ์ค์ ๋ Flexible
์ ๋จ์ถ ํํ๋ก ๊ฐ๋ฅํ ๋ชจ๋ ๊ณต๊ฐ์ ์ฑ์ด๋ค.
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: Column(
children: [
// ...์๋ต
// ์๋๋ถํด ๊ฐ์ฅ ์๋์ ์๋ ํฐ์ ๋ฐฐ๊ฒฝ ์นด๋ ๋ถ๋ถ
Flexible(
flex: 1,
child: Row(
children: [
// Expanded ์์ ฏ์ ์ฌ์ฉํ์ผ๋ฏ๋ก ์ํ์ผ๋ก ๊ฐ๋ฅํ ๊ณต๊ฐ์ ๋ชจ๋ ์ฑ์ด๋ค.
// Flexible ์์ ฏ์ ์ฌ์ฉํ๊ณ fit: FlexFit.tight ์์ฑ์ ์ถ๊ฐํด๋ ๊ฒฐ๊ณผ๋ ๊ฐ๋ค.
Expanded(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).cardColor, // ํฐ์ ๋ฐฐ๊ฒฝ
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Pomodoros', style: TextStyle(/* ... */)),
Text('0', style: TextStyle(/* ... */)),
],
),
),
),
],
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:practice_flutter/screens/home.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(0xffE7626C),
textTheme: const TextTheme(
headlineLarge: TextStyle(
color: Color(0xff232B55),
),
),
cardColor: const Color(0xffF4EDDB),
),
home: const HomeScreen(),
);
}
}
Timer
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<StatefulWidget> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int totalSeconds = 1500; // 25๋ถ
bool isRunning = false; // ํ์ด๋จธ ์งํ์ค ์ฌ๋ถ
late Timer timer; // ํ์ฌ ์คํ ์ค์ธ ํ์ด๋จธ ๊ฐ์ฒด
void onTick(Timer timer) {
setState(() => totalSeconds--);
}
// isRunning์ด false์ผ ๋ ์คํ๋๋ ํธ๋ค๋ฌ
void onStartPressed() {
// 1์ด ๊ฐ๊ฒฉ์ผ๋ก onTick์ ์คํ์์ผ์ totalSeconds 1์ฉ ๊ฐ์
timer = Timer.periodic(const Duration(seconds: 1), onTick);
setState(() => isRunning = true);
}
// isRunning์ด true์ผ ๋ ์คํ๋๋ ํธ๋ค๋ฌ
void onPausePressed() {
timer.cancel(); // ํ์ด๋จธ ์ค๋จ
setState(() => isRunning = false);
}
// ...
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: Column(
children: [
Flexible(/* ... */),
Flexible(
flex: 2,
child: Center(
child: IconButton(
iconSize: 120,
color: Theme.of(context).cardColor,
icon: Icon(isRunning
? Icons.pause_circle_outline
: Icons.play_circle_outline),
// isRunning ์ํ์ ๋ฐ๋ผ ์ด๋ฒคํธ ํธ๋ค๋ฌ ์กฐ๊ฑด๋ถ ์คํ
onPressed: isRunning ? onPausePressed : onStartPressed,
),
),
),
Flexible(/* ... */),
],
),
);
}
}
import 'dart:async';
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<StatefulWidget> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
static const twentyFiveMinutes = 1500;
int totalSeconds = twentyFiveMinutes;
bool isRunning = false;
int totalPomodoros = 0;
late Timer timer;
void onTick(Timer timer) {
setState(() {
if (totalSeconds == 0) {
totalPomodoros++;
isRunning = false;
totalSeconds = twentyFiveMinutes;
timer.cancel();
} else {
totalSeconds--;
}
});
}
void onStartPressed() {
if (isRunning) return;
timer = Timer.periodic(const Duration(seconds: 1), onTick);
setState(() => isRunning = true);
}
void onPausePressed() {
timer.cancel();
setState(() => isRunning = false);
}
void onResetPressed() {
onPausePressed();
totalSeconds = twentyFiveMinutes;
}
String format(int sec) {
// 0:24:59.000000, 0:24:58.000000, ...
var duration = Duration(seconds: sec);
var formatted = duration.toString().substring(2, 7);
return formatted;
}
@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.w700),
),
),
),
Flexible(
flex: 2,
fit: FlexFit.tight,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
iconSize: 120,
color: Theme
.of(context)
.cardColor,
icon: Icon(isRunning
? Icons.pause_circle_outline
: Icons.play_circle_outline),
onPressed: isRunning ? onPausePressed : onStartPressed,
),
IconButton(
iconSize: 120,
color: Theme
.of(context)
.cardColor,
icon: const Icon(Icons.restart_alt),
onPressed: onResetPressed,
),
],
),
),
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: [
Text(
'Pomodoros',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Theme
.of(context)
.textTheme
.headlineLarge
?.color,
),
),
Text(
'$totalPomodoros',
style: TextStyle(
fontSize: 58,
fontWeight: FontWeight.w600,
color: Theme
.of(context)
.textTheme
.headlineLarge
?.color,
),
),
],
),
),
),
],
),
),
],
),
);
}
}
Timer.periodic
์์ฑ์๋ ์์
์ ์ฃผ๊ธฐ์ ์ผ๋ก ์คํํ๊ธฐ ์ํ ํ์ด๋จธ๋ฅผ ์์ฑํ๋ค. ๋ธ๋ผ์ฐ์ WebAPI์์ ์ ๊ณตํ๋ setInterval
๋ฉ์๋์ ๋น์ทํ๋ค๊ณ ๋ณด๋ฉด ๋๋ค.
Timer.periodic(Duration duration, void callback(Timer timer))
Timer.periodic
์ ํธ์ถํ๋ฉด Timer
๊ฐ์ฒด๋ฅผ ๋ฐํํ๊ณ , cancel()
๋ฉ์๋๋ก ํ์ด๋จธ๋ฅผ ์ค์งํ ์ ์๋ค. ์ฒซ ๋ฒ์งธ ์ธ์์ธ Duration
์ ์๊ฐ ๊ฐ๊ฒฉ์ ๋ํ๋ด๋ ํด๋์ค๋ก ๋ฐ๋ฆฌ์ด, ์ด, ๋ถ, ์๊ฐ ๋๋ ์ผ(days) ๋จ์๋ฅผ ์ง์ํ๋ค.
Duration duration = Duration(minutes: 2, seconds: 30);
print(duration); // 0:02:30.000000
// 2๋ถ 30์ด ๊ฐ๊ฒฉ์ผ๋ก ์ฝ๋ฐฑ ์คํ
Timer timer = Timer.periodic(duration, (Timer timer) { /* ... */ });
timer.cancel(); // ํ์ด๋จธ ์ค์ง
duration
๊ฐ์ฒด์ ์๊ฐ์ ๋ค์ํ ๋จ์๋ก ๋ณํํ ์ ์๊ณ , toString()
์ ์ด์ฉํด ๋ฌธ์์ด๋ก ๋ณํํ ์๋ ์๋ค.
Duration duration = Duration(hours: 1, minutes: 2, seconds: 30);
// ์ ์ฒด ์๊ฐ ์ค '์๊ฐ' ๋จ์ ๊ฐ๋ง ๋ฐํ, ๋จ์ ๋ถ/์ด๋ ๋ฒ๋ฆผ. ์ถ๋ ฅ: 1
print(duration.inHours);
// ์ ์ฒด ์๊ฐ ์ค '๋ถ' ๋จ์ ๊ฐ๋ง ๋ฐํ, ๋จ์ ์ด๋ ๋ฒ๋ฆผ. ์ถ๋ ฅ: 62
print(duration.inMinutes);
// ์ ์ฒด ์๊ฐ ์ค '์ด' ๋จ์ ๊ฐ๋ง ๋ฐํ, ๋จ์ ๋ฐ๋ฆฌ์ด๋ ๋ฒ๋ฆผ. ์ถ๋ ฅ: 3750
print(duration.inSeconds);
// toString() ๋ฉ์๋๋ฅผ ์ด์ฉํด ๋ฌธ์์ด๋ก ๋ณํํ ์๋ ์๋ค. ์ถ๋ ฅ: 02:30
print(duration.toString().substring(2, 7));
Timer
๊ฐ์ฒด๋ cancel()
๋ฉ์๋ ์ธ์๋ ํ์ด๋จธ์ ์คํ ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ isActive
์์ฑ๊ณผ, ์ฃผ๊ธฐ์ ์ผ๋ก ์คํ๋๋ ํ์ด๋จธ์์ ํ์ฌ ๋ช ๋ฒ์งธ ํธ์ถ(tick ์)์ธ์ง ๋ํ๋ด๋ tick
์์ฑ์ ๊ฐ์ง๋ค.
Timer.periodic(Duration(seconds: 1), (Timer timer) {
print(timer.tick); // 1 2 3 4 ...
});
์ผ์ ์๊ฐ ํ์ ํ ๋ฒ๋ง ์คํํ๋ ค๋ฉด Timer
ํด๋์ค์ ์์ฑ์๋ฅผ ์ฌ์ฉํ๋ค.
Timer(Duration(seconds: 5), () {
print('hello'); // 5์ด ๋ค hello 1๋ฒ ์ถ๋ ฅ
});
๊ธ ์์ ์ฌํญ์ ๋ ธ์ ํ์ด์ง์ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๋ฐ์๋ฉ๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[React] ๋ฆฌ์กํธ ์ฝ๋๋ฅผ ๊ฐ์ ํ ์ ์๋ 4๊ฐ์ง ํ (0) | 2024.10.28 |
---|---|
[Flutter] ํ๋ฌํฐ ๊ธฐ์ด ๋ด์ฉ ์ ๋ฆฌ - Part 2 (1) | 2024.10.13 |
[TS] ํ์ ์คํฌ๋ฆฝํธ ๋ธ๋๋๋ ํ์ (0) | 2024.09.26 |
๋์ปค(Docker)์ ์ฟ ๋ฒ๋คํฐ์ค(Kubernetes) ๊ธฐ๋ณธ ๊ฐ๋ (0) | 2024.09.17 |
[Flutter] ํ๋ฌํฐ ๊ฐ๋ฐ ํ๊ฒฝ ๊ตฌ์ถ for macOS (2) | 2024.09.03 |
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[React] ๋ฆฌ์กํธ ์ฝ๋๋ฅผ ๊ฐ์ ํ ์ ์๋ 4๊ฐ์ง ํ
[React] ๋ฆฌ์กํธ ์ฝ๋๋ฅผ ๊ฐ์ ํ ์ ์๋ 4๊ฐ์ง ํ
2024.10.28 -
[Flutter] ํ๋ฌํฐ ๊ธฐ์ด ๋ด์ฉ ์ ๋ฆฌ - Part 2
[Flutter] ํ๋ฌํฐ ๊ธฐ์ด ๋ด์ฉ ์ ๋ฆฌ - Part 2
2024.10.13 -
[TS] ํ์ ์คํฌ๋ฆฝํธ ๋ธ๋๋๋ ํ์
[TS] ํ์ ์คํฌ๋ฆฝํธ ๋ธ๋๋๋ ํ์
2024.09.26 -
๋์ปค(Docker)์ ์ฟ ๋ฒ๋คํฐ์ค(Kubernetes) ๊ธฐ๋ณธ ๊ฐ๋
๋์ปค(Docker)์ ์ฟ ๋ฒ๋คํฐ์ค(Kubernetes) ๊ธฐ๋ณธ ๊ฐ๋
2024.09.17