#상태관리 #CupertinoDatePicker #Dialog #DateTime
10.1 사전 지식
10.1.1 setState() 함수
State를 상속하는 모든 클래스에서 사용 가능
StatefulWidget의 렌더링이 끝난 클린 상태의 State ➡️ setSate()로 원하는 속성 변경 ➡️ 위젯이 더티 상태로 설정 ➡️ build() 재실행 ➡️ State가 클린 상태로 돌아옴 |
setState()는 매개변수로 콜백 함수를 입력받고, 이 콜백 함수에 변경하고 싶은 속성들을 입력해주면 해당 코드가 반영된 뒤 build()가 실행된다. 콜백 함수는 비동기로 작성될 수 없다.
10.1.2 showCupertinoDialog() 함수
다이얼로그를 실행하는 함수. 실행 시 모든 애니메이션과 작동이 iOS 스타일로 적용된다. cupertino.dart 패키지를 임포트해 사용한다.
showCupertinoDialog(
context: context, //BuildContext 입력
barrierDismissible: true, //외부(배리어) 탭 시 다이얼로그 닫음
builder: (BuildContext context) { //다이얼로그에 들어갈 위젯
return Text('dialog');
},);
10.2 사전 준비
pubspec.yaml에 이미지와 폰트 추가
assets:
- asset/img/
fonts:
- family: parisienne
fonts:
- asset: asset/font/Parisienne-Regular.ttf
- family: sunflower
fonts:
- asset: asset/font/Sunflower-Bold.ttf
weight: 700
- asset: asset/font/Sunflower-Light.ttf
- asset: asset/font/Sunflower-Medium.ttf
weight: 500
10.4 구현하기
10.4.1 홈 스크린 UI 구현하기
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen ({Key? key}) : super(key: key);
Widget build(BuildContext context){
return Scaffold(
body: SafeArea(
top: true,
bottom: false,
child: Column(
//위아래 끝에 위젯 배치
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_DDay(),
_CoupleImage(),
],
),
),
);
}
}
class _DDay extends StatelessWidget {
Widget build(BuildContext context){
return Text('DDay Widget');
}
}
class _CoupleImage extends StatelessWidget {
Widget build(BuildContext context){
return Text('CoupeImage Widget');
}
}
_CoupleImage에 사진 적용
class _CoupleImage extends StatelessWidget {
Widget build(BuildContext context) {
return Center(
child: Image.asset(
'asset/img/middle_image.png',
height: MediaQuery.of(context).size.height / 2,
),
);
}
}
MediaQuery.of(context) 를 사용해 화면 크기와 관련된 기능 사용.
size 게터를 불러와 화면 전체의 너비와 높이를 가져올 수 있다.
* .of 생성자
.of(context)로 정의된 모든 생성자는 일반적으로 BuildContext를 매개변수로 받고 위젯트리에서 가장 가까이 있는 객체의 값을 찾아낸다.
앱이 실행되면 MaterialApp이 빌드됨과 동시에 MediaQuery가 생성되고, _CoupleImage 위젯에서 MediaQuery.of(context)를 실행하면 위젯트리를 올라가면 가장 가까운 곳에 위치한 MediaQuery (여기서는 MaterialApp에 있는 것) 값을 가져온다.
Theme.of(context), Navigator.of(context) 등에서도 사용
_DDay 위젯에 텍스트, 아이콘 적용
class _DDay extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
children: [
const SizedBox(height: 16),
Text('U&I'),
const SizedBox(height: 16),
Text('우리 처음 만난 날'),
Text('2022.02.02'),
const SizedBox(height: 16),
IconButton(
iconSize: 60,
onPressed: () {},
icon: Icon(
Icons.favorite,
),
),
const SizedBox(height: 16),
Text('D+100'),
],
);
}
}
main.dart 테마 설정
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:u_and_i/screen/home_screen.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(
fontFamily: 'sunflower',
textTheme: TextTheme(
displayLarge: TextStyle(
color: Colors.white,
fontSize: 80.0,
fontWeight: FontWeight.w700,
fontFamily: 'parisienne',
),
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(),
),
);
}
* 흔히 사용되는 ThemeData 매개변수
fontFamily 기본 글씨체 지정
textTheme Text위젯테마 지정
tabBarTheme TabBar 위젯 테마 지정
cardTheme, appBarTheme, floatingActionButtonTheme, .... 등등
위젯이름Theme으로 위젯 테마 지정 가능
_DDay의 Text 위젯에 스타일 적용
class _DDay extends StatelessWidget {
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Column(
children: [
const SizedBox(height: 16),
Text(
'U&I',
style: textTheme.displayLarge,
),
const SizedBox(height: 16),
Text(
'우리 처음 만난 날',
style: textTheme.bodyLarge,
),
Text(
'2022.02.02',
style: textTheme.bodyMedium,
),
const SizedBox(height: 16),
IconButton(
iconSize: 60,
onPressed: () {},
icon: Icon(
Icons.favorite,
color: Colors.red,
),
),
const SizedBox(height: 16),
Text(
'D+100',
style: textTheme.displayMedium,
),
],
);
}
}
Theme.of(context) 로 위젯트리 위 가장 가까운 Theme 값을 가져옴
* 변경한 theme은 MaterialApp의 매개변수에 입력했고, build()에 입력되지 않은 값이므로 핫리로드로는 반영X. 리스타트 필요
이미지 오버플로 해결하기
폰 크기에 따라 이미지가 잘릴 경우 Expanded 위젯 (남는 공간만큼 차지하도록 함)으로 감싸서 해결
10.4.2 상태 관리 연습해보기
_DDay의 하트 버튼 클릭 시 날짜를 변경할 수 있는 기능 구현
하트버튼의 onPressed가 _DDay 위젯에 있어서, _HomeScreenState에서 버튼이 눌렸을 때 콜백을 받을 수 없음. -> _DDay 위젯에 하트 아이콘을 눌렀을 때 실행할 콜백함수를 매개변수로 받아서 _HomeScreenState에서 상태관리를 할 수 있도록 해야 함
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();
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(),
],
),
),
);
}
}
_DDay 위젯에 매개변수로 onHeartPressed 함수를 전달
void onHeartPressed() {
print('pressed');
}
class _DDay extends StatelessWidget {
final GestureTapCallback onHeartPressed; //탭했을 때 실행할 함수
_DDay({
required this.onHeartPressed, //상위에서 함수 입력받기
});
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Column(
children: [
const SizedBox(height: 16),
Text(
'U&I',
style: textTheme.displayLarge,
),
const SizedBox(height: 16),
Text(
'우리 처음 만난 날',
style: textTheme.bodyLarge,
),
Text(
'2022.02.02',
style: textTheme.bodyMedium,
),
const SizedBox(height: 16),
IconButton(
iconSize: 60,
onPressed: onHeartPressed, //눌렀을 때 실행할 함수 설정
icon: Icon(
Icons.favorite,
color: Colors.red,
),
),
const SizedBox(height: 16),
Text(
'D+100',
style: textTheme.displayMedium,
),
],
);
}
}
생성자에 날짜값 추가, 콜백함수 수정해 날짜를 지금 기준으로 설정, 하트 클릭시 디데이 + 되도록 설정
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 = firstDay.subtract(Duration(days: 1));
});
}
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 StatelessWidget {
final GestureTapCallback onHeartPressed; //탭했을 때 실행할 함수
final DateTime firstDay;
_DDay({
required this.onHeartPressed, //상위에서 함수 입력받기
required this.firstDay, //firstDay 입력받기
});
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final now = DateTime.now();
return Column(
children: [
const SizedBox(height: 16),
Text(
'U&I',
style: textTheme.displayLarge,
),
const SizedBox(height: 16),
Text(
'우리 처음 만난 날',
style: textTheme.bodyLarge,
),
Text(
'${firstDay.year}.${firstDay.month}.${firstDay.day}',
style: textTheme.bodyMedium,
),
const SizedBox(height: 16),
IconButton(
iconSize: 60,
onPressed: onHeartPressed, //눌렀을 때 실행할 함수 설정
icon: Icon(
Icons.favorite,
color: Colors.red,
),
),
const SizedBox(height: 16),
Text(
//현재날짜 - firstDay로 디데이 설정
'D+${DateTime(now.year, now.month, now.day).difference(firstDay).inDays + 1}',
style: textTheme.displayMedium,
),
],
);
}
}
class _CoupleImage extends StatelessWidget (...생략)
10.4.3 CupertinoDatePicker로 날짜 선택 구현하기
void onHeartPressed() {
showCupertinoDialog(context: context, builder: (BuildContext context) {
return CupertinoDatePicker(onDateTimeChanged: (DateTime date) {},
mode: CupertinoDatePickerMode.date,);
},);
setState(() {
//firstDay에서 하루 빼기
firstDay = firstDay.subtract(Duration(days: 1));
});
}
builder 매개변수에 입력되는 함수에 다이얼로그에 보여줄 위젯 반환
CupertinoDatePicker : 스크롤을 통해 날짜를 pick. 정해진 값을 onDateTimeChange의 매개변수로 전달
mode는 날짜를 고르는 모드 지정 (date, time, dateAndTime...)
DatePicker 수정, 닫기 구현
barrierDismissible : 외부 탭 시 다이얼로그 닫기 설정
Align : 자식 위젯을 어떻게 위치시킬지 정할 수 있음. alignment 매개변수에 Alignment값 입력해 배치 설정. Alignment의 정렬값은 topRight, topCenter, ceenter, centerRight, bottomRight, .... 등으로 지정
10.4.4 CupertinoDatePicker 변경 값 상태관리에 적용하기
CupertinoDatePicker 값이 변경되면 firstDay 값 변경
-> onDateTimeChanged에 setState 실행
void onHeartPressed() {
showCupertinoDialog(
context: context,
builder: (BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
color: Colors.white,
height: 300,
child: CupertinoDatePicker(
onDateTimeChanged: (DateTime date) {
setState(() {
firstDay = date;
});
},
mode: CupertinoDatePickerMode.date,
),
),
);
},
barrierDismissible: true,
);
}
maximumDate를 현재날짜 + 1 (그냥 now를 쓰면 오류난다)로 설정해 미래 날짜를 설정할 수 없도록 할 수 있다.
'[Must Have 코드팩토리의 플러터 프로그래밍]' 카테고리의 다른 글
11 디지털 주사위 (0) | 2024.07.01 |
---|---|
09 전자액자 (0) | 2024.05.02 |
08 블로그 웹 앱 (1) | 2024.05.02 |
07 앱을 만들려면 알아야 하는 그 밖의 지식 (0) | 2024.04.30 |
06 기본 위젯 알아보기 (1) | 2024.04.30 |