D-Day 앱 만들기
D-Day 앱 레이아웃
- 화면의 상/하단으로 나눕니다.
- 화면의 상단에는 D-Day 시작 날짜를 선택 할 수 있는 이미지 버튼과 D-Day 를 계산하여 표시합니다.
- 화면의 하단에는 이미지를 넣어줍니다.
사전 준비
- 배경으로 사용될 이미지를 준비합니다.
- 폰트 파일을 준비합니다.
- pubspec.yaml 에서 프로젝트에서 사용할 이미지와 폰트를 설정합니다.
....
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
assets:
- asset/img/
fonts:
- family: parisienne # ➊ family 키에 폰트 이름을 지정할 수 있습니다.
fonts:
- asset: asset/font/Parisienne-Regular.ttf # ➋ 등록할 폰트 파일의 위치
- family: sunflower
fonts:
- asset: asset/font/Sunflower-Light.ttf
- asset: asset/font/Sunflower-Medium.ttf
weight: 500 # ➌ 폰트의 두께. FontWeight 클래스의 값과 같습니다.
- asset: asset/font/Sunflower-Bold.ttf
weight: 700
....
Weight 는 폰트의 두께를 의미합니다. 폰트의 두께별로 파일이 따로 존재하기 때문에 같은 폰트라도 다른 두께를 표현하는 파일은 Weight 값을 따로 표현해야 합니다.
두께 값은 100~900까지 100단위로 사용할 수 있으면 숫자가 높을수록 두껍다는 의미입니다. 코드에서 사용시에는 FontWeight.w500 처럼 사용하면 됩니다.
pubspec.yaml 파일을 수정했다면 [pub get] 을 실행하여 변경 사항을 반영해줍니다.
첫 화면
그럼 이제 본격적으로 D-Day 앱을 만들어 보겠습니다.
- main.dart 파일에 위젯에서 사용할 텍스트와 IconButton 테마를 정의합니다.
import 'package:[프로젝트 명]/screen/home_screen.dart';
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData( // ➊ 테마를 지정할 수 있는 클래스
fontFamily: 'sunflower', // 기본 글씨체
textTheme: TextTheme( // ➋ 글짜 테마를 적용할 수 있는 클래스
headline1: TextStyle( // headline1 스타일 정의
color: Colors.white, // 글 색상
fontSize: 80.0, // 글 크기
fontWeight: FontWeight.w700, // 글 두께
fontFamily: 'parisienne', // 글씨체
),
headline2: TextStyle(
color: Colors.white,
fontSize: 50.0,
fontWeight: FontWeight.w700,
),
bodyText1: TextStyle(
color: Colors.white,
fontSize: 30.0,
),
bodyText2: TextStyle(
color: Colors.white,
fontSize: 20.0,
),
)
),
home: HomeScreen(),
),
);
}
MaterialApp 에는 theme 라는 매개변수가 있습니다. 이 매개변수에는 ThemeData 를 입력할 수 있습니다.
ThemeData 에서는 플러터가 기본으로 제공하는 대부분의 위젯의 기본 스타일을 지정할 수 있습니다.
위에서 사용한 textTheme 는 글자의 테마를 지정할 수 있는 매개변수입니다.
앱 화면(HomeScreen 위젯)
D-Day 앱 화면에 표시할 위젯을 작성합니다.
- 화면을 _DDay 위젯과 _CoupleImage 위젯, 두 위젯을 위 아래로 배치하여 구현합니다.
- lib/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 firstDay = 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,
firstDay: firstDay,
),
_CoupleImage(),
],
),
),
);
}
void onHeartPressed(){ // ➍ 하트 눌렀을때 실행할 함수
showCupertinoDialog( // ➋ 쿠퍼티노 다이얼로그 실행
context: context,
builder: (BuildContext context) {
return Align( // ➊ 정렬을 지정하는 위젯
alignment: Alignment.bottomCenter, // ➋ 아래 중간으로 정렬
child: Container(
color: Colors.white, // 배경색 흰색 지정
height: 300, // 높이 300 지정
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime date) {
setState(() {
firstDay = date;
});
},
),
),
);
},
barrierDismissible: true,
);
}
}
.....
SafeArea : 아이폰의 노치 디스플레이등에 대비에서 위쪽에 SafeArea 를 적용해준다. 하단에는 미적용한다.
mainAxisAlignment: MainAxisAlignment.spaceBetween
- 위 아래 각각 끝에 _DDay, _CoupleImage 위젯을 위치시킵니다.
상태 관리
- 처음 만난 날은 변경 할 수 있는 값입니다.
- 이 날짜를 변수 값으로 저장하고 변경하면서 사용합니다.
- 하트 아이콘(ImageButtion)를 누르면 날짜를 고를 수 있는 UI 가 나오며, 날짜를 선택하면 firstDay 변수를 변경시킵니다.
- _DDay 위젯에 하트 아이콘을 눌렀을 때 실행되는 콜백함수를 매개변수로 전달해서 _HomeScreen 에서 상태를 관리합니다.
Align 위젯
- 자식 위젯의 위치를 지정합니다.
- bottomCenter 는 자식 위젯을 중간 아래에 배치합니다.
onDateTimeChanged 의 콜백 함수
- CupertinoDatePicker 위젯에서 날짜가 변경될 때마다 실행합니다.
- 콜백함수가 실행될 때마다 firstDay 변수에 선택된 date 값이 저장됩니다.
_DDay 위젯
- 여러 Text 위젯과 하트 아이콘(ImageButton)으로 이루어져 있습니다.
- 만나기 시작할 날짜와 며칠이 지났는지 표시하는 글자는 날짜를 변경할 때마다 자동으로 바뀌게 됩니다.
- main.dart 에서 정의한 스타일을 Text 위젯의 style 에 적용합니다.
- lib/home_screen.dart
class _HomeScreenState extends State<HomeScreen> {
....
}
class _DDay extends StatelessWidget {
final GestureTapCallback onHeartPressed;
final DateTime firstDay;
_DDay({
required this.onHeartPressed, // ➋ 상위에서 함수 입력받기
required this.firstDay,
});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final now = DateTime.now();
return Column(
children: [
const SizedBox(height: 16.0),
Text(
// 최상단 U&I 글자
'U&I',
style: textTheme.headline1,
),
const SizedBox(height: 16.0),
Text(
// 두번째 글자
'우리 처음 만난 날',
style: textTheme.bodyText1,
),
Text(
// 임시로 지정한 만난 날짜
'${firstDay.year}.${firstDay.month}.${firstDay.day}',
style: textTheme.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(
// 만난 후 DDay
'D+${DateTime(now.year, now.month, now.day).difference(firstDay).inDays + 1}',
style: textTheme.headline2,
),
],
);
}
}
Theme.of(context).textTheme
- 위젯트리 위 가장 가까운 Theme 값을 가져옵니다.
- Icon 의 경우 색상이 각각 다른 경우가 많기 때문에 theme 를 사용하지 않고 직접 지정합니다.
GestureTapCallback
- Material 패키지에서 기본으로 제공하는 typedef 로, 버튼의 onPressed 및 onTap 콜백 함수들의 GestureTapCallback 타입으로 정의되어 있습니다.
_CoupleImage 위젯
- lib/home_screen.dart
class _HomeScreenState extends State<HomeScreen> {
....
}
class _DDay extends StatelessWidget {
....
}
class _CoupleImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Expanded(
// Expanded 추가
child: Center(
// ➊ 이미지 중앙정렬
child: Image.asset(
'asset/img/middle_image.png',
// ➋ 화면의 반만큼 높이 구현
height: MediaQuery.of(context).size.height / 2,
),
),
);
}
}
Expanded
- 핸드폰은 화면의 비율과 해상도가 모두 다릅니다. 따라서 하나의 화면을 기준으로 UI 작업을 하다보면 다른 크기의 핸드포에서는 같은 UI 배치가 나오지 않을 수 있습니다. 이미지를 화면의 반만큼 차지하도록 설정하였는데 만약 화면의 크기가 작아서 상단의 글자들이 화면의 반 이상을 차지하면 아래쪽 이미지는 남은 공간보다 더 많은 높이를 차지합니다. 그래서 일반적으로 이미지가 남는 공간만큼 차지하도록 Expanded 위젯을 사용합니다.
height: MediaQuery.of(context).size.height / 2
- 이미지를 적용하고 높이를 화면 높이의 반으로 설정합니다.
- MediaQuery 를 사용하면 화면의 크기와 관련된 각종 기능을 사용할 수 있습니다.
- size getter 를 사용하여 전체의 너비와 높이를 사용하였습니다. 화면의 전체 높이 / 2 를 사용하여 이미지가 화면 높이 반만큼만 차지할 수 있도록 설정하였습니다.
Center 를 이용해서 이미지를 중앙에 배치시킵니다.
자!! 이제 코드 작성은 완성하였습니다.
앱을 실행해보자.
D-Day 앱. 짜잔~~~
'프로그래밍 > 플러터 Flutter' 카테고리의 다른 글
[플러터] 22. 주사위 앱 만들기 (0) | 2023.05.24 |
---|---|
[플러터] 20. setState() 상태 관리 (0) | 2023.05.12 |
[플러터] 19. 빌드 에러(FAILURE: Build failed with an exception. You need Java 11) (1) | 2023.05.10 |
[플러터] 18. 전자 액자 앱 만들기 (1) | 2023.05.10 |
[플러터] 17. 위젯의 생명주기(Life Cycle) (0) | 2023.05.08 |