MediaQuery의 경우 화면 크기를 얻을 수 있는데, 이는 보통 앱을 사용하는 디바이스의 가로나 세로 크기가 다르기 때문에 디바이스에 맞게 앱을 구성하는 '반응형'을 위해 사용하는 앱 내의 패키지이다.
아래의 코드를 사용해 디바이스 가로크기에 해당하는 부분을 모두 사용한다.
width: MediaQuery.of(context).size.width,
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Text('I Love You'),
Text('우리의 사랑이 시작된'),
Text('2022.12.22'),
IconButton(onPressed: () {}, icon: Icon(Icons.favorite)),
Text('D+365')
],
),
),
);
}
}
그러나 중앙에 배치 했음에도 위에 상단 노치에 가려서 해당 컨텐츠가 보이지 않는다. 이때 SafeArea를 사용한다.
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Text('I Love You'),
Text('우리의 사랑이 시작된'),
Text('2022.12.22'),
IconButton(onPressed: () {}, icon: Icon(Icons.favorite)),
Text('D+365')
],
),
),
),
);
}
}
backgroundColor: Color(0xfff59db5),
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xffffa3bc),
body: SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Text(
'I Love You',
style: TextStyle(
color: Colors.white,
fontFamily: 'Cookie',
fontSize: 70,
),
),
Text(
'우리의 사랑이 시작된',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 30,
),
),
Text(
'2022.12.22',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 30,
),
),
IconButton(
iconSize: 50,
color: Colors.red,
onPressed: () {},
icon: Icon(Icons.favorite),
),
Text(
'D+365',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 60,
),
),
],
),
),
),
);
}
}
코드를 작성하다보니 main.dart에서 사용하는 HomeScreen 클래스의 코드가 너무 길어져서 관리하고 코드를 읽기가 어려워졌다. 그래서 위에서 다룬 디데이 화면을 따로 클래스를 분리하고 해당 클래스를 사용하는 방식을 해보도록 하겠다.
길어진 코드를 분리한 클래스는 main.dart에 사용되지 않고, 해당 파일에서만 사용하기 때문에 이름에 ' _ '를 붙여 구분을 해준다.
'_dayPart()'를 새로 생성해 작성해주었다.
import "package:flutter/material.dart";
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xffffa3bc),
body: SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
child: _dayPart(),
),
),
);
}
}
class _dayPart extends StatelessWidget {
const _dayPart({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'I Love You',
style: TextStyle(
color: Colors.white,
fontFamily: 'Cookie',
fontSize: 70,
),
),
Text(
'우리의 사랑이 시작된',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 30,
),
),
Text(
'2022.12.22',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 30,
),
),
IconButton(
iconSize: 50,
color: Colors.red,
onPressed: () {},
icon: Icon(Icons.favorite),
),
Text(
'D+365',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 60,
),
),
],
);
}
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xffffa3bc),
body: SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
_dayPart(),
Image.asset('asset/img/couple_picture.jpg'),
],
),
),
),
);
}
}
아래와 같이 에러 메시지가 뜨는데, 403 픽셀만큼 디바이스 화면에서 넘어갔다는 것을 알려준다.
이는 개발자 모드이기 때문에 나타나는 것이지, 실제로 배포했을 때는 노란 줄무늬는 뜨지 않는다.
A RenderFlex overflowed by 403 pixels on the bottom.
해당 에러를 해결하기 위해서는 위젯에서 나머지 부분을 차지하라는 위젯인 'Expanded'를 사용해줘야 한다.
Expanded(child: Image.asset('asset/img/couple_picture.jpg')),
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xffffa3bc),
body: SafeArea(
child: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
_dayPart(),
Expanded(child: Image.asset('asset/img/couple_picture.jpg')),
],
),
),
),
);
}
}
children: [
_dayPart(),
_dayPicturePart(),
],
class _dayPicturePart extends StatelessWidget {
const _dayPicturePart({super.key});
@override
Widget build(BuildContext context) {
return Expanded(child: Image.asset('asset/img/couple_picture.jpg'));
}
}
우선 동일하게 Expanded로 묶어 2개의 위젯이 화면의 절반씩 차지하도록 한다.
mainAxisAlignment
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
Column(
children: [
Text(
'우리의 사랑이 시작된',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 30,
),
),
Text(
'2022.12.22',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 30,
),
),
],
),
- **CupertinoDatePicker()**를 사용하고, mode를 CupertinoDatePickerMode.date로 설정해 시간이 아닌 날짜를 선택하도록한다.
- onDateTimeChanged: 는 날짜가 변경되었을 때 실행할 함수를 넣으면 되는데, DateTime 값을 반환한다.
디데이 앱 만들기 프로젝트
- selectedDate라는 변수를 선언한다.
- onDateTimeChanged 부분에서 setState() 함수 안에 받아온 date 값을 넣어준다.
DateTime selectedDate = DateTime.now();
onDateTimeChanged: (DateTime date) {
setState(() {
selectedDate = date;
});
},
- 위의 링크에서 알려준 날짜를 가져오는 방법을 참고해, Text 위젯 부분을 받아온 날짜로 변경해준다.
Text(
'${selectedDate.year}.${selectedDate.month}.${selectedDate.day}',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 30,
),
),
- 아래의 화면에서 보이는 바와 같이 달력의 숫자를 변경하면 선택한 날짜와 디데이가 변경된다.
- 그러나 현재의 날짜보다 앞서간 날짜를 선택했을 때 **'-숫자'**가 나타나면서 디데이 앱에서는 허용되면 안 되는 값이 나타난다.
- 해당 부분의 최대 허용 범위 날짜를 설정하고, 초기의 날짜를 지정해줘야 한다.
디데이 앱 만들기 - 날짜 지정하기
- DataTime.now() 가 자주 쓰이므로 변수로 선언해 사용한다.
- maximumDate: 최대 허용 범위를 현재의 날짜의 년, 월, 일로 지정한다.
- 그러나 maximumDate만을 사용해 최대 허용 날짜를 지정하면 아래와 같은 오류가 뜬다.
- 그 이유는 CupertinoDatePicker는 앱을 실행할 때 현재의 날짜를 초기 날짜로 설정해서 앱이 실행되는데, 최대 날짜를 현재의 년, 월, 일로 설정하니까 분, 초에 해당하는 범위는 벗어나게 된다. 그렇기 때문에 초기 날짜를 지정해주면 된다.
final now = DateTime.now();
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
maximumDate: DateTime(
now.year,
now.month,
now.day,
),
onDateTimeChanged: (DateTime date) {
setState(() {
selectedDate = date;
});
},
)),
초기의 날짜를 설정함으로써 maximumDate를 사용할 수 있게 한다.
initialDateTime: selectedDate,
그러면 아래와 같이 미래의 날짜를 설정하려고 할 때 넘어가지 않고 다시 원래의 날짜로 되돌아오는 것을 확인할 수 있다.
디데이 앱 만들기 - 날짜 지정하기
- 데이터를 공유해야 하는 부분을 데이터 흐름에 맞게 관리하기 위해 코드 최적화를 해준다.
- 부모 클래스 => 자식클래스로의 데이터 흐름
- onPressed에 StatefulWidget을 사용해야 하는 코드를 상위 위젯으로 빼서 모두 함께 관리할 수 있도록 한다.
- selectedDate와 onPressed에 해당하는 부분을 생성자로 작성해 상위 위젯에서 받아오도록 한다.
- 생성자에 들어갈 데이터를 상위 위젯에서 _dayPart()를 부를 때 매개변수로 넣어준다.
- 상위 위젯에서는 데이터를 공유하는 동작을 onHeartPressed()라는 따로 함수를 만들어 코드를 보기 쉽게 작성한다.
// ignore_for_file: prefer_const_constructors
import 'package:flutter/cupertino.dart';
import "package:flutter/material.dart";
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
DateTime selectedDate = DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xffffa3bc),
body: SafeArea(
bottom: false,
child: Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
_dayPart(
selectedDate: selectedDate,
onPressed: onHeartPressed, // 아래에 함수 따로 정의해서 한눈에 보이도록
),
_dayPicturePart(),
],
),
),
),
);
}
onHeartPressed() {
final DateTime now = DateTime.now();
showCupertinoDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
color: Colors.white,
height: 300,
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: selectedDate,
maximumDate: DateTime(
now.year,
now.month,
now.day,
),
onDateTimeChanged: (DateTime date) {
setState(() {
selectedDate = date;
});
},
)),
);
},
);
}
}
class _dayPart extends StatelessWidget {
final DateTime selectedDate;
final VoidCallback onPressed; //onPressed 부분 외부의 상위위젯에서 받아오기
_dayPart({
required this.selectedDate,
required this.onPressed, //onPressed 부분 받아오기
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final now = DateTime.now();
return Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
'I Love You',
style: TextStyle(
color: Colors.white,
fontFamily: 'Cookie',
fontSize: 70,
),
),
Column(
children: [
Text(
'우리의 사랑이 시작된',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 30,
),
),
Text(
'${selectedDate.year}.${selectedDate.month}.${selectedDate.day}',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 30,
),
),
],
),
IconButton(
iconSize: 50,
color: Colors.red,
onPressed: onPressed, //외부에서 onPressed 받아오기
icon: Icon(Icons.favorite),
),
Text(
'D+${DateTime(
now.year,
now.month,
now.day,
).difference(selectedDate).inDays + 1}',
style: TextStyle(
color: Colors.white,
fontFamily: 'HiMelody',
fontSize: 60,
),
),
],
),
);
}
}
class _dayPicturePart extends StatelessWidget {
const _dayPicturePart({super.key});
@override
Widget build(BuildContext context) {
return Expanded(child: Image.asset('asset/img/couple_picture.jpg'));
}
}