# 강의 제목 : 누적다운로드 120만+ 1인 개발자와 함께하는 앱 개발 입문 Online
# 강의 목표 : 기초부터 운영까지 앱 개발의 전체 프로세스를 이해한다.
내 이름으로 된 앱을 최대 10개까지 만들어 출시할 수 있다.
앱 개발자로 성장할 수 있는 초석을 다진다.
반응 얻는 앱의 특징과 노하우를 알아간다.
향후 강의 없이도 나만의 앱을 개발할수 있는 실력을 가진다.
# 강의 요약 : 프로그램 설치부터 기본 문법, 광고 다는 법, 클론코딩을 진행하며 필수 지식을 학습한다.
총 10개의 다른 주제로 실제 사용화 가능한 수준의 앱을 만들어본다.
나의 앱을 세상에 선보이기 위한 개발자 등록 및 배포를 진행한다.
강사님의 리뷰/클레임 대응사례 등 앱 성공 포인트를 참고해 1인 개발자로서의 입지를 다진다.
# 강의 목차 : Flutter 실전 앱 제작
- 앱 기능 및 디자인 설계 및 초기 구조 만들기 (일기앱)
- 일기 작성 화면 만들기
- 일기 작성 화면 만들기 (이모티콘 추가) (15일차)
- 달력 화면 만들기
- 통계, 더보기 화면 만들기
- Splash 스크린 추가하기
# 강의 화면 :
# 강의 내용 : Flutter 실전 앱 제작 (일기 작성 화면 만들기 - 이모티콘 추가)
1. 저장하기 이어서 구현하기
# main.dart
class _MyHomePageState extends State<MyHomePage> {
int selectIndex = 0;
final dbHelper = DatabaseHelper.instance;
Diary todayDiary;
void getTodayDiary() async{
List<Diary> diary = await dbHelper.getDiaryByDate(Utils.getFormatTime(DateTime.now()));
if(diary.isNotEmpty){
todayDiary = diary.first;
}
setState((){
});
}
@override
void initSate(){
super.initState();
getTodayDiary();
}
주의. initState() 함수 안에서 절대로 setState()함수를 호출 불가능하다.
2. 저장 후 화면 그리기
# main.dart
import 'package:diary/write.dart';
import 'package:flutter/material.dart';
import 'data/database.dart';
import 'data/diary.dart';
import 'data/util.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectIndex = 0;
final dbHelper = DatabaseHelper.instance;
Diary todayDiary;
List<String> statusImg = [
"assets/img/icoweather.png",
"assets/img/icoweather_2.png",
"assets/img/icoweather_3.png",
];
void getTodayDiary() async{
List<Diary> diary = await dbHelper.getDiaryByDate(Utils.getFormatTime(DateTime.now()));
if(diary.isNotEmpty){
todayDiary = diary.first;
}
setState((){
});
}
@override
void initSate(){
super.initState();
getTodayDiary();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(""),
),
body: Container(child: getPage()),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await Navigator.of(context).push(MaterialPageRoute(builder: (ctx) => DiaryWritePage(
diary: Diary(
date: Utils.getFormatTime(DateTime.now()),
title: "",
memo: "",
status: 0,
image: "assets/img/b1.jpg"
),
)));
getTodayDiary();
},
tooltip: "Increment",
child:Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.today),
label: "오늘"
),
BottomNavigationBarItem(
icon: Icon(Icons.calendar_today_rounded),
label: "기록"
),
BottomNavigationBarItem(
icon: Icon(Icons.insert_chart),
label: "통계"
),
],
onTap: (idx){
setState(() {
selectIndex = idx;
});
},
),
);
}
Widget getPage(){
if (selectIndex == 0){
return getTodayPage();
}else if(selectIndex == 1){
return getHistoryPage();
}else{
getChartPage();
}
}
Widget getTodayPage(){
if(todayDiary == null){
return Container(
child: Text("일기 작성 해주세요~~~"),
);
}
return Container(
child: Stack(
children: [
Positioned.fill(
child: Image.asset(todayDiary.image, fit: BoxFit.cover,),
),
Positioned.fill(
child: ListView(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${DateTime.now().month}.${DateTime.now().day}",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold,
color: Colors.white),),
Image.asset(statusImg[todayDiary.status], fit: BoxFit.contain,)
],
)
],
)
)
],
),
);
}
Widget getHistoryPage(){
return Container();
}
Widget getChartPage(){
return Container();
}
}
# 실행 결과
3. 일기 작성 후 화면 구현하기
# main.dart
import 'package:diary/write.dart';
import 'package:flutter/material.dart';
import 'data/database.dart';
import 'data/diary.dart';
import 'data/util.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectIndex = 0;
final dbHelper = DatabaseHelper.instance;
Diary todayDiary;
List<String> statusImg = [
"assets/img/icoweather.png",
"assets/img/icoweather_2.png",
"assets/img/icoweather_3.png",
];
void getTodayDiary() async{
List<Diary> diary = await dbHelper.getDiaryByDate(Utils.getFormatTime(DateTime.now()));
if(diary.isNotEmpty){
todayDiary = diary.first;
}
setState((){
});
}
@override
void initSate(){
super.initState();
getTodayDiary();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(""),
),
body: Container(child: getPage()),
floatingActionButton: FloatingActionButton(
onPressed: () async {
Diary _d;
if(todayDiary != null){
_d = todayDiary;
}else{
_d = Diary(
date: Utils.getFormatTime(DateTime.now()),
title: "",
memo: "",
status: 0,
image: "assets/img/b1.jpg"
);
}
await Navigator.of(context).push(MaterialPageRoute(builder: (ctx) => DiaryWritePage(
diary: ,
)));
getTodayDiary();
},
tooltip: "Increment",
child:Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.today),
label: "오늘"
),
BottomNavigationBarItem(
icon: Icon(Icons.calendar_today_rounded),
label: "기록"
),
BottomNavigationBarItem(
icon: Icon(Icons.insert_chart),
label: "통계"
),
],
onTap: (idx){
setState(() {
selectIndex = idx;
});
},
),
);
}
Widget getPage(){
if (selectIndex == 0){
return getTodayPage();
}else if(selectIndex == 1){
return getHistoryPage();
}else{
getChartPage();
}
}
Widget getTodayPage(){
if(todayDiary == null){
return Container(
child: Text("일기 작성 해주세요~~~"),
);
}
print(todayDiary.title);
return Container(
child: Stack(
children: [
Positioned.fill(
child: Image.asset(todayDiary.image, fit: BoxFit.cover,),
),
Positioned.fill(
child: ListView(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${DateTime.now().month}.${DateTime.now().day}",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold,
color: Colors.white),),
Image.asset(statusImg[todayDiary.status], fit: BoxFit.contain,)
],
),
Container(
child: Column(
children: [
Text(todayDiary.title, style: TextStyle(fontSize: 18),),
Container(height: 12,),
Text(todayDiary.memo, style: TextStyle(fontSize: 18),)
]
)
)
],
)
)
],
),
);
}
Widget getHistoryPage(){
return Container();
}
Widget getChartPage(){
return Container();
}
}
# write.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'data/database.dart';
import 'data/diary.dart';
class DiaryWritePage extends StatefulWidget{
final Diary diary;
DiaryWritePage({Key key, this.diary}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _DiaryWritePageState();
}
}
class _DiaryWritePageState extends State<DiaryWritePage>{
List<String> images = [
"assets/img/b1.jpg",
"assets/img/b2.jpg",
"assets/img/b3.jpg",
"assets/img/b4.jpg",
];
List<String> statusImg = [
"assets/img/icoweather.png",
"assets/img/icoweather_2.png",
"assets/img/icoweather_3.png",
];
int imgIndex = 0;
TextEditingController nameController = TextEditingController();
TextEditingController memoController = TextEditingController();
final dbHelper = DatabaseHelper.instance;
@override
void initState(){
super.initState();
nameController.text = widget.diary.title;
memoController.text = widget.diary.memo;
}
@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
actions: [
TextButton(child: Text("저장", style: TextStyle(color: Colors.white),),
onPressed: () async {
widget.diary.title = nameController.text;
widget.diary.memo = memoController.text;
await dbHelper.insertDiary(widget.diary);
Navigator.of(context).pop();
})
],
),
body: ListView.builder(
itemBuilder: (ctx, idx){
if(idx == 0){
return InkWell(child: Container(
margin: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
width: 100,
height: 100,
child: Image.asset(widget.diary.image, fit: BoxFit.cover,),
),
onTap: () {
setState((){
widget.diary.image = images[imgIndex];
imgIndex ++;
imgIndex = imgIndex % images.length;
});
},
);
}else if(idx == 1){
return Container(
margin: EdgeInsets.symmetric(vertical: 22, horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(statusImg.length, (_idx) {
return InkWell(child: Container(
height: 70,
width: 70,
child: Image.asset(statusImg[_idx], fit: BoxFit.contain,),
padding: EdgeInsets.all(6),
decoration: BoxDecoration(
border: Border.all(color: _idx == widget.diary.status ? Colors.blue : Colors.transparent,),
borderRadius: BorderRadius.circular(100)
),
), onTap: () {
setState(() {
widget.diary.status = _idx;
});
},);
}),
),
);
}
else if(idx == 2){
return Container(
margin: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: Text("제목", style: TextStyle(fontSize: 20),),
);
}
else if(idx ==3){
return Container(
margin: EdgeInsets.symmetric(horizontal: 16),
child: TextField(
controller: nameController,
),
);
}
else if(idx == 4){
return Container(
margin: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: Text("내용", style: TextStyle(fontSize: 20),),
);
}
else if(idx == 5){
return Container(
margin: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: TextField(
controller: memoController,
minLines: 10,
maxLines: 20,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black)
)
),
),
);
}
return Container();
},
itemCount: 6,
),
);
}
}
# 실행 결과
4. 화면 투명하게 꾸미기
#main.dart
import 'package:diary/write.dart';
import 'package:flutter/material.dart';
import 'data/database.dart';
import 'data/diary.dart';
import 'data/util.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectIndex = 0;
final dbHelper = DatabaseHelper.instance;
Diary todayDiary;
List<String> statusImg = [
"assets/img/icoweather.png",
"assets/img/icoweather_2.png",
"assets/img/icoweather_3.png",
];
void getTodayDiary() async{
List<Diary> diary = await dbHelper.getDiaryByDate(Utils.getFormatTime(DateTime.now()));
if(diary.isNotEmpty){
todayDiary = diary.first;
}
setState((){
});
}
@override
void initSate(){
super.initState();
getTodayDiary();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(""),
),
body: Container(child: getPage()),
floatingActionButton: FloatingActionButton(
onPressed: () async {
Diary _d;
if(todayDiary != null){
_d = todayDiary;
}else{
_d = Diary(
date: Utils.getFormatTime(DateTime.now()),
title: "",
memo: "",
status: 0,
image: "assets/img/b1.jpg"
);
}
await Navigator.of(context).push(MaterialPageRoute(builder: (ctx) => DiaryWritePage(
diary: _d,
)));
getTodayDiary();
},
tooltip: "Increment",
child:Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.today),
label: "오늘"
),
BottomNavigationBarItem(
icon: Icon(Icons.calendar_today_rounded),
label: "기록"
),
BottomNavigationBarItem(
icon: Icon(Icons.insert_chart),
label: "통계"
),
],
onTap: (idx){
setState(() {
selectIndex = idx;
});
},
),
);
}
Widget getPage(){
if (selectIndex == 0){
return getTodayPage();
}else if(selectIndex == 1){
return getHistoryPage();
}else{
getChartPage();
}
}
Widget getTodayPage(){
if(todayDiary == null){
return Container(
child: Text("일기 작성 해주세요~~~"),
);
}
print(todayDiary.title);
return Container(
child: Stack(
children: [
Positioned.fill(
child: Image.asset(todayDiary.image, fit: BoxFit.cover,),
),
Positioned.fill(
child: ListView(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${DateTime.now().month}.${DateTime.now().day}",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold,
color: Colors.white),),
Image.asset(statusImg[todayDiary.status], fit: BoxFit.contain,)
],
),
Container(
decoration: BoxDecoration(
color: Colors.white54
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(todayDiary.title, style: TextStyle(fontSize: 18),),
Container(height: 12,),
Text(todayDiary.memo, style: TextStyle(fontSize: 18),)
]
)
)
],
)
)
],
),
);
}
Widget getHistoryPage(){
return Container();
}
Widget getChartPage(){
return Container();
}
}
# main.dart
Container(
margin: EdgeInsets.symmetric(vertical: 16, horizontal: 20),
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 20),
decoration: BoxDecoration(
color: Colors.white54,
borderRadius: BorderRadius.circular(16)
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(todayDiary.title, style: TextStyle(fontSize: 18),),
Container(height: 12,),
Text(todayDiary.memo, style: TextStyle(fontSize: 18),)
]
)
)
# 실행 결과
5. AppBar 지우기
# main.dart
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(child: getPage()),
floatingActionButton: FloatingActionButton(
onPressed: () async {
Diary _d;
if(todayDiary != null){
_d = todayDiary;
}else{
_d = Diary(
date: Utils.getFormatTime(DateTime.now()),
title: "",
memo: "",
status: 0,
image: "assets/img/b1.jpg"
);
}
# 실행 결과
상단 앱 표시줄 제거 됨
# 교육 소감
오늘은 실제 일기 작성 앱에서 가장 중요한 일기를 작성하는 페이지를 구현 하였다. 지난번 시간에 이미지를 추가하는데 많은 오류를 뿜어내서 오늘 강의는 수월하게 진행했으면 하는 마음이 있었다. getTodayPage를 만들면서 경우의 수를 생각해야하는데, 아직 일기를 작성하지 않았을 때와 일기를 이미 작성했을 때를 나누어서, 일기를 작성하지 않았을 때를 null 이면(비어있다면), 일기를 작성해달라고 요청하고, 이미 일기를 작성했을 때는 어떻게 본문에 출력하는지 화면을 구성하였다. 화면을 구성하기 위해서는 Stack widget 을 사용하여, 플로터 구조상 바탕화면 위에 이미지를 띄우는 구조로 구현을 해야 한다. 그릴 스택에 전체를 채우기 위해 Posioned.fill 를 통해 fit: BoxFit.cover 를 채워야하고, Positioned.fill 은 ListView 를 통해 Row에 오늘의 날짜를 입력하게 구현하였다. 또한 일기 내용을 적절하게 배치하고 배경을 이쁘게 하기 위해 BoxDecoration 을 추가하여 Colors.white54 의 색감을 추가 하였다. 이로써 일기를 작성하면 메인 화면에 일기 내용이 어떻게 보여지게 하는지 학습할 수 있었다.
# 본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
'코딩 알로하 :: two > 하이브리드앱' 카테고리의 다른 글
패스트캠퍼스 챌린지 17일차 (0) | 2021.11.17 |
---|---|
패스트캠퍼스 챌린지 16일차 (0) | 2021.11.16 |
패스트캠퍼스 챌린지 14일차 (0) | 2021.11.14 |
패스트캠퍼스 챌린지 13일차 (0) | 2021.11.13 |
패스트캠퍼스 챌린지 12일차 (0) | 2021.11.12 |
댓글