[플러터] D-Day 앱 만들기 (feat. 폰트 반영하기, Cupertino 위젯)
이전 내용
[플러터] 이미지 롤링 기능 구현하기
이전 내용 [플러터] 웹 앱 만들어보기에러 관련 [플러터] 에러 발생 및 해결1. Gradle build failed to produce an .apk file...import 'package:flutter/material.dart';void main() { runApp( MaterialApp( // 머터리얼 디자인 위젯 h
puppy-foot-it.tistory.com
◆ 에러 관련
[플러터] 에러 발생 및 해결
1. Gradle build failed to produce an .apk file...import 'package:flutter/material.dart';void main() { runApp( MaterialApp( // 머터리얼 디자인 위젯 home: Scaffold( // Scaffold 위젯 body: Center( // 가운데 정렬 child: Text( // Text 위젯 'He
puppy-foot-it.tistory.com
◆ 깃허브 업로드 관련
[플러터] 대용량 파일 깃허브 업로드하기 (안드로이드 스튜디오)
대용량 파일 깃허브 업로드(안드로이드 스튜디오) 플러터 공부를 하다가 만들어둔 작업물들을 깃허브에 올리려고 하는데, 파일 용량이 크고, 파일 갯수도 많아서 GUI로 업로드가 안 된다. 이전에
puppy-foot-it.tistory.com
D-Day 앱 만들기
날짜를 지정하여 해당 날짜로부터 경과된 또는 남은 일자를 알려주는 앱을 만들어 본다.
주요 기능은
- 사용자가 직접 날짜 선택
- 해당 날짜를 기준으로 D-Day 및 경과일 (또는 남은 일자) 업데이트
폰트 추가하기
지난번에 이어 이번에는 폰트를 추가해 본다.
폰트 역시 이미지처럼 asset 디렉터리 내에 font 라는 디렉터리를 만들어 넣어주면 된다.
[폰트 다운로드 방법: 구글 폰트]
먼저 구글 폰트에 접속해서
Browse Fonts - Google Fonts
Making the web more beautiful, fast, and open through great typography
fonts.google.com
필자는 'Jua' 라는 폰트를 다운받아 보려 한다.
오른쪽 상단에 [Get font] 버튼을 누르고
[Download all] 버튼 클릭하여 다운로드
안드로이드 스튜디오로 돌아와서 asset 디렉터리 생성하고, 그 안에 font 디렉터리를 생성하여 폰트 파일(ttf 또는 oft)을 넣어준다.
그리고나서 pubsepc.yaml 파일에 폰트를 추가해주면 된다.
flutter:
uses-material-design: true
# 폰트 추가
fonts:
- family: jua
fonts:
- asset: asset/font/Jua-Regular.ttf # 등록할 폰트 파일 위치
★ 만약 하나의 폰트에 여러 버전이 있다면?
아래의 Sour Gummy 폰트의 경우, 다양한 버전이 있다.
Sour Gummy - Google Fonts
fonts.google.com
이 중에서 사용할 폰트 몇 가지만 골라 font 폴더에 넣어주고, pubspec.yaml에 추가해 준다.
- family: SourGummy
fonts:
- asset: asset/font/SourGummy-Bold.ttf
weight: 700
- asset: asset/font/SourGummy-Light.ttf
weight: 500 # 폰트 두께
- asset: asset/font/SourGummy-Medium.ttf
weight는 폰트 두께를 의미하는데, 폰트의 두께별로 파일이 따로 제공되기 때문에 같은 폰트라도 다른 두께를 표현하는 파일은 weight 값을 따로 표현해줘야 한다.
※ 두께값은 100부터 900까지 100단위 (숫자가 높을수록 두꺼움)
폰트를 적용했으면 터미널에 pub get 명령어를 입력하여 변경 사항을 반영하면 된다.
flutter pub get
프로젝트 구조
프로젝트의 주요 구조는 아래와 같다.
asset 디렉터리: 폰트, 이미지 등 파일이 담겨 있는 폴더
- font: 폰트 파일이 담겨 있는 폴더
- img: 이미지 파일이 담겨 있는 폴더
lib: Flutter 애플리케이션의 핵심 코드와 기능이 담겨 있는 디렉터리
-screen 디렉터리: 화면 구성과 관련된 모든 위젯이 모여있는 디렉터리
- home_screen.dart: 홈 화면 구성
- main.dart: 애플리케이션 진입점
pubspec.yaml: Flutter 프로젝트 관련 설정
D-Day 앱 구현 관련 중요 사항
◆ setState() 함수:
- 상태를 변경하고 build()를 재호출하여 UI를 다시 그릴 때 호출되는 함수로, State를 상속하는 모든 클래스는 setState() 함수를 사용할 수 있다.
- setState() 함수를 시랭해서 원하는 속성들이 변경되면, 위젯의 상태가 dirty로 설정되며 build() 함수가 재실행된다. 그리고나서 State가 다시 클린 상태로 되돌아 온다.
- setState() 함수는 콜백 함수를 매개변수로 입력받는데, 이 콜백 함수에 변경하고 싶은 속성들을 입력해주면 해당 코드가 반영되며 build() 함수가 실행된다. ▶ 콜백 함수는 비동기로 작성되면 안된다.
◆ Cupertino 위젯:
import 'package:flutter/cupertino.dart';
showCupertinoDialog( // 쿠퍼티노 다이얼로그 실행
context: context,
builder: (BuildContext context) {
...
},
barrierDismissible: true, // 외부 탭 시 다이얼로그 닫기
- showCupertinoDialog() 함수: 다이얼로그를 실행하는 함수
- 참고로, Cupertino는 Apple 본사가 있는 곳인데, 이를 통해 해당 패키지의 작동과 애니메이션이 iOS 스타일 일 것으로 짐작할 수 있다.
- Cupertino 위젯을 사용하기 위해서는 Cupertino 패키지를 꼭 import 해야 한다.
- 모든 showDialog() 형태의 함수들은 BuildContext를 반드시 입력해야 한다
- CupertinoDatePicker: Cupertino 패키지에서 기본으로 제공하는 위젯으로, 스크롤을 통해 날짜를 정할 수 있고, 정해진 값을 콜백 함수의 매개변수로 전달
- CupertinoDatePicker의 mode 매개변수는 날짜를 고르는 모드 지정 가능. (date: 날짜 / time: 시간 / dateAndTime: 날짜와 시간)
- 배리어(barrier): 다이얼로그 위젯 위에 흐림 처리가 된 부분.
- barrierDismissible: true : 배리어를 눌렀을 때 다이얼로그가 닫히고 false를 입력하면 닫히지 않는다.
◆ .of 생성자:
- .of 생성자는 상위 위젯에 접근해서 정보를 가져오는 방법으로, 위에서 전달된 정보(상위 위젯)를 하위에서 꺼내 쓰기 위해 사용한다.
- .of(context)로 정의된 모든 생성자는 일반적으로 BuildContext를 매개변수로 받고 위젯 트리에 있는 객체의 값을 찾아 낸다.
- .of(context)는 BuildContext가 생성된 이후에만 사용 가능하므로, initState()에서는 쓸 수 없고, build() 함수에서 사용.
- MediaQuery.of(context): 현재 위젯트리에서 가장 가까이에 있는 MediaQuery 값을 찾아 낸다.
- 자주 사용하는 .of 생성자로는 Theme.of(context), MediaQuery.of(context), Navigator.of(context) 등이 있다.
메서드 | 설명 |
Theme.of(context) | 테마 정보 (글꼴 크기, 색상 등) 가져오기 |
MediaQuery.of(context) | 화면 크기, 방향, 패딩 정보 등 가져오기 |
Navigator.of(context) | 화면 전환 (페이지 이동) 기능 사용하기 |
ScaffoldMessenger.of(context) | 스낵바(Snackbar) 띄우기 |
DefaultTabController.of(context) | 탭(Tab) 상태 가져오기 |
◆ ThemeData:
- ThemeData는 Flutter 앱의 전반적인 디자인 스타일을 설정하는 객체로, ThemeData는 글꼴, 색상, 버튼 스타일 등을 앱 전체에서 통일되게 관리하게 도와주는 디자인 템플릿이다.
- ThemeData는 MaterialApp 위젯의 theme 속성에서 사용하며, 플러터가 기본으로 제공하는 대부분의 위젯의 기본 스타일을 지정할 수 있다.
- 주요 속성
매개변수 | 타입 | 설명 | 예시 |
appBarTheme | AppBarTheme | 앱바 스타일 지정 | AppBarTheme(color: Colors.pink) |
backgroundColor | Color | 앱 전체의 일반 배경색 | Colors.grey[200] |
bottomNavigationBarTheme | BottomNavigationBarThemeData | 하단 네비게이션 바 스타일 | BottomNavigationBarThemeData(...) |
buttonTheme | ButtonThemeData | (구형) 버튼 스타일 지정 | ButtonThemeData(...) |
cardTheme | CardTheme | 카드(Card) 위젯 스타일 | CardTheme(...) |
checkboxTheme | CheckboxThemeData | 체크박스 스타일 | CheckboxThemeData(...) |
colorScheme | ColorScheme | 앱의 색상 체계 전반을 구성 | ColorScheme.fromSwatch(...) |
dialogTheme | DialogTheme | AlertDialog 등 대화창 스타일 | DialogTheme(...) |
dividerColor | Color | Divider(구분선)의 색상 | Colors.grey |
elevatedButtonTheme | ElevatedButtonThemeData | ElevatedButton 스타일 지정 | ElevatedButtonThemeData(style: ElevatedButton.styleFrom(...)) |
floatingActionButtonTheme | FloatingActionButtonThemeData | FAB (둥근 버튼) 스타일 | FloatingActionButtonThemeData(...) |
fontFamily | String | 기본 글씨체 지정 | 'SourGummy', 'NotoSansKR' 등 |
iconTheme | IconThemeData | 아이콘 스타일 (색상, 크기 등) | IconThemeData(color: Colors.red) |
inputDecorationTheme | InputDecorationTheme | 텍스트 입력 필드 스타일 | InputDecorationTheme(...) |
primaryColor | Color | 주요 색상 (앱 전체에 가장 많이 사용) | Colors.blue |
scaffoldBackgroundColor | Color | Scaffold 배경색 (주요 화면 배경) | Colors.white |
sliderTheme | SliderThemeData | 슬라이더 위젯 스타일 | SliderThemeData(...) |
switchTheme | SwitchThemeData | 스위치 토글 버튼 스타일 | SwitchThemeData(...) |
tabBarTheme | TabBarTheme | 탭바(TabBar) 스타일 | TabBarTheme(...) |
textTheme | TextTheme | 전체 텍스트 스타일 정의 | TextTheme(displayLarge: TextStyle(...)) |
◆ Align 위젯
- 자식 위젯을 어떻게 위치시킬지 정할 수 있다.
- alignment 매개변수에는 Alignment 값 입력 가능.
- Alignment.bottomCenter 처럼 Alignment. 뒤에 정렬값을 입력하면 된다.
- topRight, topCenter, topLeft, centerRight, center, centerLeft, bottomRight, bottomLeft, bottomCenter 등
Dart 파일 코드 및 설명
* main.dart *
import 'package:flutter/material.dart';
import 'package:d_day/screen/home_screen.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData( // 테마 지정 클래스
fontFamily: 'SourGummy', // 기본 글씨체
textTheme: TextTheme( // 글자 테마 적용 클래스
displayLarge: TextStyle( // headline1 스타일 정의
color: Colors.white, // 글자 색상
fontSize: 80.0, // 글자 크기
fontWeight: FontWeight.w700, // 글자 두께
fontFamily: 'jua', // 글씨체
),
displayMedium: TextStyle(
color: Colors.white,
fontSize: 50.0,
fontWeight: FontWeight.w700,
),
bodyLarge: TextStyle(
color: Colors.white,
fontSize: 30.0,
),
bodyMedium: TextStyle(
color: Colors.white,
fontSize: 20.0,
),
)
),
home: HomeScreen(),
),
);
}
[코드 구조]
파트 | 설명 |
main() 함수 | 앱 실행 시작점 |
runApp() | 최상위 위젯으로 앱 시작 |
MaterialApp | 앱 전체를 감싸고 테마, 홈화면 등을 설정 |
ThemeData | 전체 테마 스타일 정의 |
TextTheme | 재사용 가능한 글자 스타일 모음 |
HomeScreen | 첫 화면으로 보여줄 위젯 (직접 만든 클래스) |
★ MaterialApp
- theme: 앱 전반의 디자인 테마 설정(글꼴, 색상 등)
- home: 앱 시작 시 첫 화면으로 보여줄 위젯 설정(HomeScreen)
theme: ThemeData( // 테마 지정 클래스
fontFamily: 'SourGummy', // 기본 글씨체
textTheme: TextTheme( // 글자 테마 적용 클래스
displayLarge: TextStyle( // headline1 스타일 정의
color: Colors.white, // 글자 색상
fontSize: 80.0, // 글자 크기
fontWeight: FontWeight.w700, // 글자 두께
fontFamily: 'jua', // 글씨체
),
- theme: 앱 전역 글꼴/스타일 테마 설정
설정 | 설명 |
fontFamily: 'SourGummy' | 전체 앱의 기본 글꼴을 ‘SourGummy’로 설정 |
특정 TextStyle에서 fontFamily: 'jua' | 특정 스타일에서만 다른 글꼴(jua) 적용 |
- TextTheme: 앱에서 자주 쓰이는 텍스트 스타일을 한 곳에 모아 설정하는 클래스
이름 | 실제 용도 | 설정 값 |
displayLarge | 큰 제목 (headline1) | 흰색, 80pt, 굵음, jua 폰트 |
displayMedium | 중간 제목 (headline2) | 흰색, 50pt, 굵음 |
bodyLarge | 본문 큰 글씨 | 흰색, 30pt |
bodyMedium | 본문 일반 크기 | 흰색, 20pt |
* home_screen.dart *
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
DateTime birthday = DateTime.now();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.pink[100], // 배경색
body: SafeArea( // 시스템 UI 피해 UI 그리기
top: true,
bottom: false,
child: Column(
// 위아래 끝에 위젯 배치
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// 반대축 최대 크기로 늘리기
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_DDay(
onHeartPressed: onHeartPressed, // 하트 눌렸을 때 콜백 함수
birthday: birthday, // 생일 전달
),
_FamilyImage(),
],
),
),
);
}
void onHeartPressed() {
showCupertinoDialog( // 쿠퍼티노 다이얼로그 실행
context: context,
builder: (BuildContext context) {
return Align( // 정렬 지정 위젯
alignment: Alignment.bottomCenter, // 하단 중간
child: Container(
color: Colors.white, // 흰색 배경
height: 300, // 높이
child: CupertinoDatePicker(
// 시간 제외
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime date) {
// 상태 변경 시 setState() 함수 실행
setState(() {
birthday = date;
});
},
),
),
);
},
barrierDismissible: true, // 외부 탭 시 다이얼로그 닫기
);
}
}
class _DDay extends StatelessWidget {
// 하트 눌렀을 때 실행 함수
final GestureTapCallback onHeartPressed;
final DateTime birthday; // 출생 (예정)일
_DDay({
required this.onHeartPressed, // 상위에서 함수 입력 받음
required this.birthday, // 날짜 변수 입력 받음
});
@override
Widget build(BuildContext context) {
// 테마 불러오기
final textTheme = Theme.of(context).textTheme;
final now = DateTime.now(); // 현재 날짜 시간
final differenceInDays = now.difference(birthday).inDays;
String dDayText;
if (differenceInDays < 0) {
// 출생 전
dDayText = 'D-${-differenceInDays}';
} else if (differenceInDays > 0) { // 출생 이후
dDayText = 'D+${differenceInDays}';
} else { // 출생 당일
dDayText = 'D-Day';
}
return Column(
children: [
const SizedBox(height: 16.0),
Text ( // 최상단 글자
'My Baby',
style: textTheme.displayLarge, // headline1 스타일 적용
),
Text(
'우리 아가 만난 날',
style: textTheme.bodyLarge, // bodyText1 적용
),
Text ( // 임시 저장 날짜
// DateTime을 년.월.일 형태로 변경
'${birthday.year}.${birthday.month}.${birthday.day}',
style: textTheme.bodyMedium, // bodyText2 적용
),
const SizedBox(height: 16.0),
IconButton( // 하트 아이콘 버튼
iconSize: 60.0,
onPressed: onHeartPressed, // 아이콘 눌렀을 때 실행 함수
icon: Icon(
Icons.favorite,
color: Colors.red, // 색상 빨강
),
),
const SizedBox(height: 16.0),
Text( // 출생 (예정)일
dDayText, // D-Day 계산 문자열 불러옴
style: textTheme.displayMedium, // headline2 적용
),
],
);
}
}
class _FamilyImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Expanded( // 오버플로 해결
child: Center( // 이미지 중앙 정렬
child: Image.asset(
'asset/img/happy_family_without_bg.png',
// 화면의 반만큼 높이 구현 (Expanded 우선 순위)
height: MediaQuery.of(context).size.height / 2,
),
),
);
}
}
파일 구조
- HomeScreen: 앱의 메인 화면
- _DDay: D-Day 정보를 찾아주는 위젯
- _FamilyImage: 하단에 표시되는 (가족) 이미지
- HomeScreen 클래스
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
DateTime birthday = DateTime.now();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.pink[100], // 배경색
body: SafeArea( // 시스템 UI 피해 UI 그리기
top: true,
bottom: false,
child: Column(
// 위아래 끝에 위젯 배치
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// 반대축 최대 크기로 늘리기
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_DDay(
onHeartPressed: onHeartPressed, // 하트 눌렸을 때 콜백 함수
birthday: birthday, // 생일 전달
),
_FamilyImage(),
],
),
),
);
}
void onHeartPressed() {
showCupertinoDialog( // 쿠퍼티노 다이얼로그 실행
context: context,
builder: (BuildContext context) {
return Align( // 정렬 지정 위젯
alignment: Alignment.bottomCenter, // 하단 중간
child: Container(
color: Colors.white, // 흰색 배경
height: 300, // 높이
child: CupertinoDatePicker(
// 시간 제외
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime date) {
// 상태 변경 시 setState() 함수 실행
setState(() {
birthday = date;
});
},
),
),
);
},
barrierDismissible: true, // 외부 탭 시 다이얼로그 닫기
);
}
}
[코드 구조]
구성요소 | 역할 |
HomeScreen | 메인 화면 클래스 |
_HomeScreenState | 상태를 관리하고 UI를 그리는 부분 |
birthday | 사용자가 선택한 날짜 (출생일) |
onHeartPressed() | 하트를 누르면 날짜 선택 창(CupertinoDatePicker)을 띄움 |
- StatefulWidget: 출생일이 변경되면 화면을 다시 그려야 하므로, 동적 UI에 사용할 수 있는 위젯인 StatefulWidget 사용.
- State<HomeScreen>: HomeScreen의 상태를 관리하며, 모든 화면의 내용은 이 클래스의 build() 함수 안에서 결정된다.
- DateTime 타입의 변수인 birthday 에는 기본값으로 현재 날짜(DateTime.now())를 넣어주며, 사용자가 날짜를 바꾸면 setState()를 통해서 업데이트 된다.
- build()는 앱의 화면을 그리는 코드
build() 함수
- Scaffold: 화면 기본 구조 (앱바, 바디, 플로팅 버튼 등 삽입 가능)
- SafeArea: 시스템 UI(노치, 상태바 등)를 피해 콘텐츠 배치
- Column: 세로로 위젯 배치. (위쪽: _DDay / 아래쪽: _FamilyImage)
- mainAxisAlignment.spaceBetween: 맨 위와 맨 아래에 하나씩 배치
- crossAxisAlignment.stretch: 가로로 최대한 늘림
- onHeartPressed() 함수는 하트를 눌렀을 때 실행되는 함수로, HomeScreen에서 받은 생일과 하트 클릭 롤백을 사용
★ onHeartPressed() 함수
- showCupertinoDialog(: iOS 스타일 다이얼로그를 화면에 띄움.
- alignment: Alignment.bottomCenter,: 다이얼로그 정렬 (하단 중앙)
- mode: CupertinoDatePickerMode.date,: 시간없이 날짜만 고르는 선택창
- onDateTimeChanged: 날짜를 바꾸면 setState()가 실행되어 birthday 값을 새로 저장하고 setState()가 호출되면 플러터는 화면 전체를 다시 그려 변경 사항이 반영됨.
- barrierDismissible: true,: 다이얼로그 외부를 누르면 닫힘.
- _DDay 클래스
class _DDay extends StatelessWidget {
// 하트 눌렀을 때 실행 함수
final GestureTapCallback onHeartPressed;
final DateTime birthday; // 출생 (예정)일
_DDay({
required this.onHeartPressed, // 상위에서 함수 입력 받음
required this.birthday, // 날짜 변수 입력 받음
});
@override
Widget build(BuildContext context) {
// 테마 불러오기
final textTheme = Theme.of(context).textTheme;
final now = DateTime.now(); // 현재 날짜 시간
final differenceInDays = now.difference(birthday).inDays;
String dDayText;
if (differenceInDays < 0) {
// 출생 전
dDayText = 'D-${-differenceInDays}';
} else if (differenceInDays > 0) { // 출생 이후
dDayText = 'D+${differenceInDays}';
} else { // 출생 당일
dDayText = 'D-Day';
}
return Column(
children: [
const SizedBox(height: 16.0),
Text ( // 최상단 글자
'My Baby',
style: textTheme.displayLarge, // headline1 스타일 적용
),
Text(
'우리 아가 만난 날',
style: textTheme.bodyLarge, // bodyText1 적용
),
Text ( // 임시 저장 날짜
// DateTime을 년.월.일 형태로 변경
'${birthday.year}.${birthday.month}.${birthday.day}',
style: textTheme.bodyMedium, // bodyText2 적용
),
const SizedBox(height: 16.0),
IconButton( // 하트 아이콘 버튼
iconSize: 60.0,
onPressed: onHeartPressed, // 아이콘 눌렀을 때 실행 함수
icon: Icon(
Icons.favorite,
color: Colors.red, // 색상 빨강
),
),
const SizedBox(height: 16.0),
Text( // 출생 (예정)일
dDayText, // D-Day 계산 문자열 불러옴
style: textTheme.displayMedium, // headline2 적용
),
],
);
}
}
[코드 구조]
구성요소 | 역할 |
StatelessWidget | 상태를 갖지 않는 위젯 |
GuestureTapCallback | Material 패키지에서 기본으로 제공하는 Typedef |
onHeartPressed | 하트 버튼을 누르면 실행되는 함수 (날짜 선택창 띄우기) |
birthday | 기준 날짜 (출생일) |
differenceInDays | 현재 날짜와 birthday의 차이 일수 |
dDayText | D+N, D-N, D-Day 등으로 표시되는 문자열 |
UI 요소 | 텍스트, 하트 아이콘 등 |
- class _DDay extends StatelessWidget {: 화면에 표시되는 D-Day 관련 정보만 보여주는 화면 조각으로, 상태를 바꾸지 않는 StatelessWidget 이다. ▶ 상태(날짜)가 바뀌면 부모 위젯이 다시 만들어준다.
- 생성자와 필드
final GestureTapCallback onHeartPressed;
final DateTime birthday;
_DDay({
required this.onHeartPressed,
required this.birthday,
});
- onHeartPressed: 하트 누를 때 실행할 함수 → 부모가 넘겨줌
- birthday: 기준 날짜 (출생일, 예정일 등) → 부모가 넘겨줌
- build() 함수: 화면에 어떤 UI를 그릴지 정의하는 함수
- final textTheme = Theme.of(context).textTheme;: MaterialApp에서 정의한 테마 (폰트 크기, 스타일 등) 불러오기
- 현재 날짜와 차이 계산
final now = DateTime.now();
final differenceInDays = now.difference(birthday).inDays;
- now: 오늘 날짜
- birthday: 사용자가 선택한 날짜
- differenceInDays: 오늘 - 출생일 = 날짜 차이 (양수면 출생 후, 음수면 출생 전)
- dDayText: D-Day 표시 문자열 함수
String dDayText;
if (differenceInDays < 0) {
dDayText = 'D-${-differenceInDays}';
} else if (differenceInDays > 0) {
dDayText = 'D+${differenceInDays}';
} else {
dDayText = 'D-Day';
}
- 타이틀 텍스트: Text('My Baby', style: textTheme.displayLarge) ▶ 큰 글씨 / Text('우리 아가 만난 날', style: textTheme.bodyLarge), ▶ 부제목 (작게)
- 하트 버튼
IconButton(
iconSize: 60.0,
onPressed: onHeartPressed,
icon: Icon(Icons.favorite, color: Colors.red),
),
하트 아이콘을 누르면 부모에서 받은 onHeartPressed() 함수가 실행되며, 이 함수는 날짜 선택 다이얼로그를 연다.
- D-Day 결과 출력
const SizedBox(height: 16.0),
Text( // 출생 (예정)일
dDayText, // D-Day 계산 문자열 불러옴
style: textTheme.displayMedium, // headline2 적용
),
- _FamilyImage 클래스
class _FamilyImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Expanded( // 오버플로 해결
child: Center( // 이미지 중앙 정렬
child: Image.asset(
'asset/img/happy_family_without_bg.png',
// 화면의 반만큼 높이 구현 (Expanded 우선 순위)
height: MediaQuery.of(context).size.height / 2,
),
),
);
}
}
[코드 구조]
구성 요소 | 설명 |
StatelessWidget | 상태를 가지지 않는 위젯 |
Expanded | 남은 공간을 모두 차지함 |
Center | 자식 위젯을 가운데 정렬 |
Image.asset | 앱 내부에 저장된 이미지 표시 |
MediaQuery | 디바이스 화면 크기를 가져오는 도구 |
- class _FamilyImage extends StatelessWidget {: '_'로 시작하므로 이 위젯은 private 위젯이며, 무상태 위젯으로 항상 같은 UI를 보여줌
- return Expanded(: 부모 Column의 남은 공간을 전부 차지
- Center: 안에 있는 위젯(Image.asset)을 가운데 정렬하고, Image.asset 내부에 있는 이미지 파일 경로를 출력 (단, 해당 경로는 pubspec.yaml 파일에 등록되어 있어야 함)
child: Center( // 이미지 중앙 정렬
child: Image.asset(
'asset/img/happy_family_without_bg.png',
- height: MediaQuery.of(context).size.height / 2
- MediaQuery.of(context).size.height: 현재 기기 화면의 전체 높이 (단위: 픽셀)
- / 2: 화면의 절반 높이만큼 이미지 높이를 설정
▶ 이렇게 하면 화면이 커지거나 작아져도 자동으로 조절 → 반응형 UI
Image.asset: 내부에 저장된 이미지 불러옴.
MediaQuery: 기기의 화면 크기 가져옴.
[화면 예시]
|-------------------------------------|
| My Baby (텍스트) |
| 우리 아가 만난 날 (텍스트) |
| 2025.5.23 (텍스트) |
| ❤️ (하트 버튼) |
| D+3 (텍스트) |
|-------------------------------------|
| [이미지] |
| (화면 절반 높이 정도) |
|-------------------------------------|
완성화면
하트를 클릭하면 다이얼로그가 열리면서 날짜를 선택할 수 있고, 선택된 날짜에 맞춰 D-Day가 변경된다.
[참고]
코드팩토리의 플러터 프로그래밍
다음 내용