Flutter D-day앱
목표한 날까지 얼마나 남았는지 알려주는 앱이다 이전 imageview에서는 StatefulWidget의 생명주기를 사용했지만 setState( )를 이용한 상태관리는 아직 안해봤다 이번 d-day앱에서 setState()함수를 사용하고 Cupertino 위젯을 사용해서 datepicker를 구현한다
setState( ) 함수
setState( )함수가 실행되는 과정은 아래와 같다
StatefulWidget의 렌더링이 끝나면 clean 상태인데 여기서 상태 변경을 해줘야 재 랜더링이 가능하다 그래서 setState( )를 실행해서 원하는 속성을 변경 그러면 위젯 상태가 dirty 상태로 변경되기 때문에 build가 재실행되고 clean 상태가 된다
setState( ) 함수 사용법
매개변수 하나를 입력받는다 이 매개변수는 콜백함수리고 콜백함수에 변경하고 싶은 속성을 입력해주면 된다 주의 콜백 함수가 비동기로 작성되면 안된다
1
2
3
setState(() {
number ++;
}); //number를 +1하고 다시 build
1. 프로젝트 초기 설정
배경으로 사용할 이미지와 폰트를 추가한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# pubspec.yaml
flutter:
uses-material-design: true
assets:
- asset/img/
fonts:
- family: parisienne
fonts:
- asset: asset/font/Parisienne-Ragular.ttf
- family: sunflowr
fonts:
- asset: asset/font/Sunflower-Light.ttf
- asset: asset/font/Sunflower-Medium.ttf
weight: 500
- asset: asset/font/Sunflower-Bold.ttf
weight: 700
위 경로에 ttf파일을 넣으면 폰트를 앱에서 적용할 수 있다
2. 메인화면 위젯 생성
lib폴더에 screen 폴더를 생성해서 홈화면으로 사용할 HomeScreen위젯을 생성한다
1
2
3
4
5
6
7
8
9
10
11
// main.dart
import 'package:flutter/material.dart';
import 'package:d_day/screen/HomeScreen.dart';
void main() {
runApp(
MaterialApp(
home: HomeScreen(),
)
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//HomeScreen.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key:key);
@override
Widget build(BuildContext) {
return Scaffold(
body: Text('home')
);
}
}
3. 위젯 구현하기
이번에는 HomeScreen 위젯 하나로만 구현하지 않고 추가적으로 두가지 위젯을 이용해서 구현한다
1
2
3
4
5
6
7
8
9
10
11
12
13
class _DDay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('dday widget');
}
}
class _BackImage extends StatelessWidget {
@override
Widget build(BuildContext) {
return Text('back imgae');
}
}
위 두가지 위젯을 일단 같은 파일에 추가적으로 작성한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key:key);
@override
Widget build(BuildContext) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: true,
bottom: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,// 위아래 끝에 배치한다
crossAxisAlignment: CrossAxisAlignment.stretch, // 반대축은 늘림
children: [
_DDay(),
_BackImage(),
],
),
)
);
}
}
class _DDay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('dday widget');
}
}
class _BackImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Image.asset(
'asset/img/image.jpeg',
height: MediaQuery.of(context).size.height/2, //화면의 반만 차지
),
);
}
}
of(context)
위 코드에서 of(context)가 사용되었는데 .of(context)로 정의된 모든 생성자는 일반적으로 BuildContext를 매개변수로 받고 위젯트리에서 가장 가까운 객체의 값을 찾아낸다 위 코드에서는 MediaQuery의 값을 찾아낸다
앱이 실행되면 MaterialApp이 빌드되는 동시에 MediaQuery가 생성된다 위젯 트리 아래에서 MediaQuery.of(context)를 실행하면 가장가까운 MediaQuery값을 가져온다
4. _DDay위젯 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreen();
}
class _HomeScreen extends State<HomeScreen> {
DateTime lastDay = DateTime.now(); // 목표 날짜
@override
Widget build(BuildContext) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: true,
bottom: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,// 위아래 끝에 배치한다
crossAxisAlignment: CrossAxisAlignment.stretch, // 반대축은 늘림
children: [
_DDay(
onStarPressed: onStarPressed,
),
_BackImage(),
],
),
)
);
}
void onStarPressed() { //함수 정의
print('클릭');
}
}
class _DDay extends StatelessWidget {
final GestureTapCallback onStarPressed; //스타를 눌렀을때 실행할 함수
_DDay({
required this.onStarPressed, // 상위 함수에서 받는다
});
@override
Widget build(BuildContext context) {
return Column(
children: [
const SizedBox(height: 16,),
Text('목표'),
const SizedBox(height: 16,),
Text('목표 날짜'),
const SizedBox(height: 16,),
IconButton(
onPressed: onStarPressed, //누른경우 실행할 함수
icon: Icon(
Icons.star,
color: Colors.amber,
),
),
const SizedBox(height: 16,),
Text('남은 날짜'),
],
);
}
}
class _BackImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Image.asset(
'asset/img/image.jpeg',
height: MediaQuery.of(context).size.height/2,
),
);
}
}
HomeScreen 위젯을 StatefulWidget으로 변경하고 스타 버튼을 누르면 함수를 실행하도록 변경했다
날짜 연동하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreen();
}
class _HomeScreen extends State<HomeScreen> {
DateTime lastDay = DateTime.now(); // 목표 날짜
@override
Widget build(BuildContext) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: true,
bottom: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,// 위아래 끝에 배치한다
crossAxisAlignment: CrossAxisAlignment.stretch, // 반대축은 늘림
children: [
_DDay(
onStarPressed: onStarPressed,
lastDay: lastDay,
),
_BackImage(),
],
),
)
);
}
void onStarPressed() {
print('클릭');
}
}
class _DDay extends StatelessWidget {
final GestureTapCallback onStarPressed; //스타를 눌렀을때 실행할 함수
final DateTime lastDay; // 목표 날짜
_DDay({
required this.onStarPressed, // 상위 함수에서 받는다
required this.lastDay,
});
@override
Widget build(BuildContext context) {
final now = DateTime.now(); //현재 날짜
final day = DateTime(now.year,now.month,now.day).difference(lastDay).inDays;
return Column(
children: [
const SizedBox(height: 16,),
Text('D - Day'),
const SizedBox(height: 16,),
Text('${lastDay.year}.${lastDay.month}.${lastDay.day}'),
const SizedBox(height: 16,),
IconButton(
onPressed: onStarPressed, //누른경우 실행할 함수
icon: Icon(
Icons.star,
color: Colors.amber,
),
),
const SizedBox(height: 16,),
Text('D - ${day}'),
],
);
}
}
class _BackImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Image.asset(
'asset/img/image.jpeg',
height: MediaQuery.of(context).size.height/2,
),
);
}
}
여기서 목표날짜를 지정하지 못해서 현재 날짜 - 현재 날짜가 되어서 d - 0이다 목표 날짜를 변경할 수 있도록 코드를 수정해야한다
CupertinoDatePicker로 날짜 선택 구현
위 별모양을 눌러서 데이트 피커를 출력해서 날짜를 변경한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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() => _HomeScreen();
}
class _HomeScreen extends State<HomeScreen> {
DateTime lastDay = DateTime.now(); // 목표 날짜
@override
Widget build(BuildContext) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: true,
bottom: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,// 위아래 끝에 배치한다
crossAxisAlignment: CrossAxisAlignment.stretch, // 반대축은 늘림
children: [
_DDay(
onStarPressed: onStarPressed,
lastDay: lastDay,
),
_BackImage(),
],
),
)
);
}
void onStarPressed() { //날짜 선택 다이얼로그
showCupertinoDialog(
context: context,
builder: (BuildContext context) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
color: Colors.grey,
height: 300,
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
onDateTimeChanged: (DateTime date) {
setState(() { //날짜 변경
lastDay = date;
});
},
),
),
);
},
barrierDismissible: true, //외부를 탭할 경우 다이얼로그 닫기
);
}
}
class _DDay extends StatelessWidget {
final GestureTapCallback onStarPressed; //스타를 눌렀을때 실행할 함수
final DateTime lastDay; // 목표 날짜
_DDay({
required this.onStarPressed, // 상위 함수에서 받는다
required this.lastDay,
});
@override
Widget build(BuildContext context) {
final now = DateTime.now(); //현재 날짜
final day = DateTime(now.year,now.month,now.day).difference(lastDay).inDays;
return Column(
children: [
const SizedBox(height: 16,),
Text('D - Day'),
const SizedBox(height: 16,),
Text('${lastDay.year}.${lastDay.month}.${lastDay.day}'),
const SizedBox(height: 16,),
IconButton(
onPressed: onStarPressed, //누른경우 실행할 함수
icon: Icon(
Icons.star,
color: Colors.amber,
),
),
const SizedBox(height: 16,),
Text('D ${day}'),
],
);
}
}
class _BackImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Image.asset(
'asset/img/image.jpeg',
height: MediaQuery.of(context).size.height/2,
),
);
}
}