Post

Flutter D-day앱

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,
      ),
    );
  }
}
This post is licensed under CC BY 4.0 by the author.