외로운 Nova의 작업실
Flutter 프로그래밍 - 6(만난지 며칠 앱 만들기) 본문
이번 장에서는 만난지 며칠지난지 알려주는 앱을 만들어보겠습니다. 이미지, 폰트등을 써서 예쁘게 만들어볼 예정입니다. 그리고 처음 만난날을 지정할때는 다이얼로그를 사용해서 만들어보겠습니다.
- 사전지식
다이얼로그중에서 IOS 스타일로된 showCupertinoDialog()함수를 사용할 예정입니다. 이 함수는 IOS 스타일로 실행되며 실행 시 모든 애니메이션과 작동이 iOS 스타일로 적용됩니다.
- 사전 준비
프로젝트 이름 : u_and_i
네이티브 언어 : 코틀린, 스위프트
<이미지 폰트추가>
<pubspec.yaml 설정>
이미지와 폰트를 pubspec.yaml 파일에 추가하겠습니다.
<프로젝트 초기화>
아래는 home_screen.dart 파일입니다.
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget{
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context){
return Scaffold(
body: Text('HomeScreen'),
);
}
}
아래는 main.dart 파일입니다.
import 'package:flutter/material.dart';
import 'package:u_and_i/screen/home_screen.dart';
void main() {
runApp(
MaterialApp(
home: HomeScreen(),
)
);
}
- 홈스크린 UI 구현
분홍색 배경에다가 화면을 반절로 나누어 위쪽에는 _DDay 클래스로 날짜를 표시하고 아래에는 _CoupleImage 클래스로 예쁜 이미지를 넣어보도록 하겠습니다.
먼저 배경을 먼저 핑크색으로 넣고 간단하게 클래스를 표현해보겠습니다.
아래는 home_screen.dart 파일입니다.
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget{
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context){
return Scaffold(
backgroundColor: Colors.pink[100], //배경색 핑크색 적용
body: SafeArea(
top: true,
bottom: false,
child: Column(
//위아래 끝에 위젯 배치
mainAxisAlignment: MainAxisAlignment.spaceBetween,
//반대축 최대 크기로 늘리기
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_DDay(),
_CoupleImage()
],
),
),
);
}
}
class _DDay extends StatelessWidget{
@override
Widget build(BuildContext context){
return Text('DDay Widget');
}
}
class _CoupleImage extends StatelessWidget{
@override
Widget build(BuildContext context){
return Text('Couple Tmage widget');
}
}
이제 글자랑 이미지를 넣어보도록 하겠습니다.
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget{
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context){
return Scaffold(
backgroundColor: Colors.pink[100], //배경색 핑크색 적용
body: SafeArea(
top: true,
bottom: false,
child: Column(
//위아래 끝에 위젯 배치
mainAxisAlignment: MainAxisAlignment.spaceBetween,
//반대축 최대 크기로 늘리기
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_DDay(),
_CoupleImage()
],
),
),
);
}
}
class _DDay extends StatelessWidget{
@override
Widget build(BuildContext context){
return Column(
children: [
const SizedBox(height: 16.0,),
Text(//최상단 UI 글자
'U&I'
),
const SizedBox(height: 16.0,),
Text(//두번쨰 글자//
'우리 처음 만난 날'
),
//임시로 만난날
Text('2021.11.23'),
const SizedBox(height: 16.0,),
IconButton(onPressed: () {},
icon: Icon(Icons.favorite),
iconSize: 60.0,),
const SizedBox(height: 16.0,),
Text(// 임시 만난 후
'D+465'
)
],
);
}
}
class _CoupleImage extends StatelessWidget{
@override
Widget build(BuildContext context){
return Center(
child: Image.asset(
'asset/img/middle_image.png',
//화면의 반만큼 높이 구현
height: MediaQuery.of(context).size.height/2,
),
);
}
}
글씨가 안예쁘기떄문에 main.dart파일에 텍스트와 IconButton 테마를 다운로드 받은 글씨체로 정의하고 사용해보겠습니다. 아래는 main.dart 파일입니다.
import 'package:flutter/material.dart';
import 'package:u_and_i/screen/home_screen.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(),
),
);
}
아래는 home_screen.dart 파일입니다.
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget{
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context){
return Scaffold(
backgroundColor: Colors.pink[100], //배경색 핑크색 적용
body: SafeArea(
top: true,
bottom: false,
child: Column(
//위아래 끝에 위젯 배치
mainAxisAlignment: MainAxisAlignment.spaceBetween,
//반대축 최대 크기로 늘리기
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_DDay(),
_CoupleImage()
],
),
),
);
}
}
class _DDay extends StatelessWidget{
@override
Widget build(BuildContext context){
final textTheme = Theme.of(context).textTheme;
return Column(
children: [
const SizedBox(height: 16.0,),
Text(//최상단 UI 글자
'U&I',
style: textTheme.headline1,
),
const SizedBox(height: 16.0,),
Text(//두번쨰 글자//
'우리 처음 만난 날',
style: textTheme.bodyText1,
),
//임시로 만난날
Text('2021.11.23',
style: textTheme.bodyText2,
),
const SizedBox(height: 16.0,),
IconButton(onPressed: () {},
icon: Icon(Icons.favorite,
color: Colors.red,),
iconSize: 60.0,),
const SizedBox(height: 16.0,),
Text(// 임시 만난 후
'D+465',
style: textTheme.headline2,
)
],
);
}
}
class _CoupleImage extends StatelessWidget{
@override
Widget build(BuildContext context){
return Center(
child: Image.asset(
'asset/img/middle_image.png',
//화면의 반만큼 높이 구현
height: MediaQuery.of(context).size.height/2,
),
);
}
}
예쁘게 잘나왔습니다.
<화면의 비율과 해상도에 따른 오버플로 해결>
핸드폰 화면의 비율과 해상도가 모두 달라서 이미지가 위쪽으로 올라와 오버플로 되는 상황이 나옵니다. 이러한 상황을 오버플로라고 합니다. 이때 Expanded 위젯을 사용하면 해결됩니다.
class _CoupleImage extends StatelessWidget{
@override
Widget build(BuildContext context){
return Expanded(
child:Center(
child: Image.asset(
'asset/img/middle_image.png',
//화면의 반만큼 높이 구현
height: MediaQuery.of(context).size.height/2,
),
)
);
}
}
- 상태 관리 연습해보기
이제 기능을 추가해보겠습니다. StatefulWidet에서 setState() 함수를 사용해서 상태관리를 하는 방법을 알아보겠습니다. 먼저 HomeScreen을 StatefulWidget으로 변경하고 하트를 누르면 "클릭"이 프린트되게 해보겠습니다.
import 'package:flutter/material.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(
top: true,
bottom: false,
child: Column(
//위아래 끝에 위젯 배치
mainAxisAlignment: MainAxisAlignment.spaceBetween,
//반대축 최대 크기로 늘리기
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_DDay(
//하트 눌렀을때 실행할 함수 전달
onHeartPressed: onHeartPressed,
),
_CoupleImage()
],
),
),
);
}
}
void onHeartPressed(){
//하트 눌럿을떄 함수
print('클릭');
}
class _DDay extends StatelessWidget {
final GestureTapCallback onHeartPressed;
_DDay({
required this.onHeartPressed,
}); // <- 세미콜론 빠진 부분
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Column(
children: [
const SizedBox(height: 16.0),
Text(
'U&I',
style: textTheme.headline1,
),
const SizedBox(height: 16.0),
Text(
'우리 처음 만난 날',
style: textTheme.bodyText1,
),
Text(
'2021.11.23',
style: textTheme.bodyText2,
),
const SizedBox(height: 16.0),
IconButton(
onPressed: onHeartPressed,
icon: Icon(
Icons.favorite,
color: Colors.red,
),
iconSize: 60.0,
),
const SizedBox(height: 16.0),
Text(
'D+465',
style: textTheme.headline2,
),
],
);
}
}
class _CoupleImage extends StatelessWidget{
@override
Widget build(BuildContext context){
return Expanded(
child:Center(
child: Image.asset(
'asset/img/middle_image.png',
//화면의 반만큼 높이 구현
height: MediaQuery.of(context).size.height/2,
),
)
);
}
}
잘 되는 것을 확인할 수 있습니다.
이제 처음만난날에 first_day 변수를 넣어놓고 first_day 변수를 오늘로 초기화해놓고 하트버튼을 누르면 first_day가 하루씩 줄어들고 D+ 부분에 얼마나 시간이 지났는지 계산하는 코드를 넣어보겠습니다.
import 'package:flutter/material.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();
void onHeartPressed(){
//하트 눌럿을떄 함수
setState((){
firstday = firstday.subtract(Duration(days: 1));
});
}
@override
Widget build(BuildContext context){
return Scaffold(
backgroundColor: Colors.pink[100], //배경색 핑크색 적용
body: SafeArea(
top: true,
bottom: false,
child: Column(
//위아래 끝에 위젯 배치
mainAxisAlignment: MainAxisAlignment.spaceBetween,
//반대축 최대 크기로 늘리기
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_DDay(
//하트 눌렀을때 실행할 함수 전달
onHeartPressed: onHeartPressed,
//오늘 날짜 전달
firstday: firstday,
),
_CoupleImage()
],
),
),
);
}
}
class _DDay extends StatefulWidget {
final GestureTapCallback onHeartPressed;
final DateTime firstday;
_DDay({
required this.onHeartPressed,
required this.firstday,
});
@override
__DDayState createState() => __DDayState();
}
class __DDayState extends State<_DDay> {
int dDay = 0;
@override
void initState() {
super.initState();
calculateDDay();
}
void calculateDDay() {
final now = DateTime.now();
final difference = now.difference(widget.firstday).inDays + 1;
setState(() {
dDay = difference < 0 ? 0 : difference;
});
}
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Column(
children: [
const SizedBox(height: 16.0),
Text(
'U&I',
style: textTheme.headline1,
),
const SizedBox(height: 16.0),
Text(
'우리 처음 만난 날',
style: textTheme.bodyText1,
),
Text(
'${widget.firstday.year}.${widget.firstday.month}.${widget.firstday.day}',
style: textTheme.bodyText2,
),
const SizedBox(height: 16.0),
IconButton(
onPressed: () {
widget.onHeartPressed();
calculateDDay();
},
icon: Icon(
Icons.favorite,
color: Colors.red,
),
iconSize: 60.0,
),
const SizedBox(height: 16.0),
Text(
'D+$dDay',
style: textTheme.headline2,
),
],
);
}
}
class _CoupleImage extends StatelessWidget{
@override
Widget build(BuildContext context){
return Expanded(
child:Center(
child: Image.asset(
'asset/img/middle_image.png',
//화면의 반만큼 높이 구현
height: MediaQuery.of(context).size.height/2,
),
)
);
}
}
잘되는 것을 확인할 수 있습니다.
- 하트누르면 showCupertinoDialog 띄우기
이제 하트를 누르면 CupertinoDialog를 띄워서 처음 만난날을 정해보도록 하겠습니다. 아래는 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,
);
}
}
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,
),
],
);
}
}
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,
),
),
);
}
}
잘 되는 것을 확인할 수 있습니다.
'Programming > Flutter' 카테고리의 다른 글
Flutter 프로그래밍 - 8(동영상 플레이어) (0) | 2024.01.14 |
---|---|
Flutter 프로그래밍 - 7(디지털 주사위) (1) | 2024.01.13 |
Flutter 프로그래밍 - 5(전자액자 만들기) (0) | 2024.01.07 |
Flutter 프로그래밍 - 4(블로그 웹 앱 만들기) (0) | 2024.01.06 |
Flutter 프로그래밍 - 3(스플래시 스크린 만들기) (0) | 2024.01.04 |