Flutter
1장 플러터란?
플러터란 ?
고성능,고품질의 iOS, Android앱과 web을 단일 코드베이스로 개발할 수 있는 구글의 모바일 UI프레임 워크이며 구글이 제공하는 무료 오픈소스이며 네이티브 수준의 성능을 자랑한다.
#UI : user interface → 쉽게 생각하면 화면(그림)
- 기본 코드베이스 - dart언어
iOS → swift필요
Android → Java 필요
하지만 플러더를 사용하면 안번에 해결가능
Skia 엔진 → 네이티브 수준의 성능을 낼 수 있는 이유
구글이 인수한 2D 그래픽 라이브러리(엔진)이며 다음과 같은 특징이 있다.
- 리액트 네이티드(브릿지) 방식과 다르게 Skia엔진에 바로 원하는 그림을 그릴 수 있다.
→ IOS만든 그림을 Android에도 그릴 수있다.
- AOT(프로덕션) 실제 서비스 환경
Ahead of time을 지원한다. Dart언어로 개발할 때 핸드폰에 빌드하기 전 사전 컴파일하여 코드를 빌드할 수 있다.
- JIT(개발모드)
Just in time을 지원한다. Dart코드로 개발하고 →Dart가상머신이 이해하는 중간언어로 번환 후 실행한다.
이후 핸드폰에 환경에 맞게 실행하며 부분 컴파일로 빠른 실행이 가능하다.
2장 플러터 설치
1.플러터 설치
Mac을 기준으로 설치
위 링크에서 자신의 mac에 맞는 zip 파일 다운로드
- 플러터를 설치할 경로에 develope 폴더 생성
- 생성한 폴더에 다운받은 플러터를 압축해제
- 환경변수 설정
cd
vi .zshrc
vi 텍스트 편집기가 열리면 아래 코드를 붙혀놓고 저장
export PATH="$HOME/development/flutter/bin:$PATH"
#-- export PATH="$HOME/"자신이 생성한 폴더 경로"/flutter/bin:$PATH"
- 설치 확인
다음 명령어로 플러터 설치 확인
flutter doctor
위와 같이 나온다면 설치 완료.
2. IOS 개발을 위한 Xcode 설치
- app store → Xcode 설치(설치 시간이 조금 걸린다)
- Xcode를 한번 실행 한 후 터미널 실행
- 다음 코드 입력
sudo gem install cocoapods
pod setup
- 설치 확인
flutter doctor
Xcode가 잘 설치되었으면 성공적으로 설치완료!
3. Android 개발을 위한 Android Studio 설치
- Java 설치
- 해당 링크에서 안드로이드 스튜디오 설치 안드로이드 스튜디오 설치
- 다운받은 dmg파일 실행 후 안드로이드 스튜디오 실행
이후 계속 Next를 눌러서 설치를 완료한다.
이후 Dart 플러그인 설치
Flutter 플러그인 설치
- 설치된 안드로이드 스튜디오 확인
flutter doctor
만약 오류가 난다면 다음 과정을 따라한다.
- 화면 왼쪽 상단의 Android Studio를 클릭하고 Preferences를 클릭
- Appearance & Behavior > System Settings > Android SDK
- Hide Obsolete Pacakges 체크를 해제한 후
SDK Tools 탭에서 아래 의 세 가지를 찾아 체크된 상태로 만든 후 OK
- Android SDK Command-line Tools (latest)
- Android SDK Platform-Tools
- Android SDK Tools (Obsolete)
없는 체크항목은 넘어가도 된다.
- 이후 터미널 실행 후 다음 명령어 실행
flutter doctor --android-licenses
무언가 묻는 창이 나오면 계속 y를 입력하고 엔터
- 설치 확인
flutter doctor
위 처럼 나온다면 설치 완료!
4. VScode 확장자 설치
-
플러터 확장자 설치
-
Dart 확장자 설치
5. 에뮬레이터 설치
-
빨간색 원 모양 클릭
-
Virtual device 클릭
-
Phone → Pixel 3a 클릭 후 Next
-
R버전 다운로드
-
에뮬레이터 실행
최초 실행 시 꽤나 오랜 시간이 걸린다.
- 잘 동작하는지 확인
3장 Dart 문법
Dart Pad에서 실습 진행
New Pad를 눌러 새로운 패드 생성 이 때 HTML은 체크하지 않는다.
String? name;
int? number;
1. 자료형 및 출력문
void main() {
int number =10;
double double1 = 10.1;
bool check = false;
String str = "Name";
// print("정수" + number);
// 위와 같은 출력은 할 수없다. 문자열과 변수를 함께 출력하려면 $를 이용하면 된다.
print("정수 : $number");
print("실수 : $double1");
print("논리 : $check");
print("문자열 : $str");
}
2. 타입 추론
var 키워드를 이용하면 값에 따라 자동으로 타입을 추론해준다.
이 때 한번 정해진 자료형을 다시 바꾸는건 불가능하다.
void main() {
var number =10;
var double1 = 10.1;
var check = false;
var str = "Name";
print("정수 : $number");
print("실수 : $double1");
print("논리 : $check");
print("문자열 : $str");
print(number.runtimeType);
number = 10;
// number = 10.1; 오류
}
3. Dynamic 타입
dynamic 키워드로 사용이 가능하며 모든 타입의 자료형을 받을 수 있다. 무적이다.
void main() {
dynamic Dynamic_type = 1;
print(Dynamic_type); //정수형으로 받음
Dynamic_type = 10.5; //실수형으로 바꿔도 상관이 없다.
print(Dynamic_type);
print(Dynamic_type.runtimeType);
}
4. 연산자
void main() {
// 산술 연산자
print(1+2);
print(1-2);
print(2*3);
print(3/2);
print(3%2);
print(5~/2); //몫 연산
// 비교 연산자 (참,거짓)
print(2==3);
print(2!=3);
print(2<3);
print(2>3);
print(2<=3);
print(2>=3);
// 논리 연산자 (참, 거짓)
// ture = 1 , false = 0
print(!true);
print(true && false); // 둘 다 참이면 참 아니면 거짓
print(true || false); // 둘 중 하나라도 참이면 참
}
5. 조건문
void main() {
int Score = 80;
if(Score>=90){
print("A");
}
else if (Score>=80){
print("B");
}
else if( Score>=70){
print("C");
}
else if (Score>=60){
print("D");
}
else{
print("F");
}
// 삼항 연산자
int Score = 80;
print(Score>=60 ?"합격" :"불합격");
// Null 객체 연산자
String? name;
print(name ?? "이름없음");
}
6. 함수
자주 사용하는 코드는 함수를 만들어서 사용하면 편하다.
void funtion(int N){
print("$N번째 Funtion 호출");
}
void main() {
funtion(1);
funtion(2);
funtion(3);
funtion(4);
}
7. 익명함수와 람다식
- 익명함수 : 매개변수로 Function 을 입력받는 함수를 호출할 때 사용하며 인자로 아무것도 주지 않는다 함수호출(){실행문} 형태이다.
- 람다식 : 매개변수로 Function 을 입력받는 함수를 호출할 때 사용하며 ⇒을 이용하여 리턴값을 줄 수 있다.
finction((){
//실행문
}); //익명함수
finction(()=> //실행문); //람다식
//하루 루틴을 결정해주는 함수
void routine(Function start){ //함수를 담을 수 있는 Function타입
String result = start();
print(result);
}
void main() {
routine((){
return "농구 하기";
});
//익명 함수
// 2줄이상 표현이 가능한 함수를 사용할 때
routine(()=> "축구 하기");
// 람다식
// 1줄로만 표현이 가능한 함수를 사용할 때
// 두 함수 모두 일회성으로 사용한다.
}
8. 클래스
객체지향 언어
클래스에 대한 개념이 부족하다면 Java Part5. 클래스와 객체 숙지
- [Part.5 클래스와 객체]
- [Part.5] 클래스 선언
### 객체지향 언어
- 프로그램을 구성하는 요소는 객체이며 이것이 상호작용 하도록 프로그래밍
- 클래스 : 객체를 만들기 위한 틀
ex) 객체 : 붕어빵
클래스 : 붕어빵 틀
public class Car{ } public class CarEx{ public static void main(String [] args){ Car c1 = new Car(); // new를 사용하여 객체를 만들어야 함 } }
- [Part.5] 참조 타입
### 자바에는 2가지 타입이 존재한다.
- 기본형 타입
- 논리형, 문자형, 정수형, 실수형
- 참조형 타입
- 기본형을 제외한 모든 타입
int i = 4; //기본형 타입 String str = new String("HELLO"); //참조형 타입
new라는 키워드는 메모리에 올려달라는 의미이다 c에서 동적할당과 같은 개념이며 이렇게 메모리에 올라간 클래스를 인스턴스라고 말한다.
메모리에 올라간 인스턴스를 가리키는 변수 = 참조하는 변수 = 레퍼런스하는 변수 모두 같은 말이다.
- 인스턴스를 가지고 있는게 아니라 가리키고 있다는 의미이다 즉 포인터
### 클래스는 모두 참조형이다
- [Part.5] String 클래스
String은 자바에서 가장 많이 사용하는 클래스이다.
### 특징 1. String은 예외적은 new연산자 없이도 생성이 가능하지만 약간의 차이가 있다.
String str1 = "Hello"; // ->상수영역에있는 Hello를 가르키고 있다. String str2 = "Hello"; // ->상수영역에있는 Hello를 가르키고 있다. String str3 = new String("Hello"); //상수영역에 있는걸 참조하는게 아니라 새롭게 힙영역에 생성한다. ////////////////////// 차이점 비교 //////////////////// if(str1==str2) --> true 둘은 상수영역에 있는 같은 레퍼런스를 참조하고 있다 if(str1 == str3) --> false str1은 상수영역 str3은 힙영역에 새롭게 생성된 인스턴스이다.
사람이 보기에는 같은 Hello이지만 자바는 new로 생성된 string과 그냥 생성된 string을 다르게 생각한다.
### 특징 2. String은 다른 클래스와 다르게 한 번 생성된 클래스는 변하지 않는다.
// str1.을 이용하여 메서드 확인 System.out.println(str1.substring(3)); //3번 인덱스부터 잘라져서 보여짐 System.out.println(str1); // 내부의 값은 변하지 않음 // 즉 수행하기 전에 새로운 스트링을 만들어서 반환한다고 생각하면 된다.
- [Part.5] 필드(field)선언
### 클래스의 구성요소 : 필드
ex)
객체 : 자동차
필드 : 자동차의 구성요소 (속성)
- 차 이름
- 차량번호
객체 : 학생
필드 : 학생의 구성요소(속성)
- 이름
- 번호
public class Car{ String name; int number; } //자동차 클래스 생성 public static void main(String[] args){ Car c1 = new Car(); Car c2 = new Car(); c1.name = "소방차"; c1.number = 1234; c2.name = "구급차"; c2.number = 1111; // 자동차 객체를 생성한 후 속성 값 삽입 System.out.println(c1.name); System.out.println(c1.number); // c1 객체 확인 System.out.println(c2.name); System.out.println(c2.number); // c2 객체 확인 }
각각의 자동차 객체 생성되었고 각자 다른값이 들어있는걸 확인할 수 있다.
- [Part.5] 메소드란?
### 객체 지향 언어 : 하나의 사물을 하나의 클래스로 설명
- 사물
- 상태 → 필드
- 이름, 차량번호
- 행동 → 메소드
- 전진,후진
- 상태 → 필드
- 메소드 : 함수와 같다 입력값 —> 결과값
- 입력값 : 매개변수(인자)
- 결과값 : 리턴값 (반환값)
- [Part.5] 메소드 선언
- 메소드 : 클래스가 가지고 있는 기능
public 리턴타입(ex int) 메소드 이름(매개변수){ 구현 }
### 다양한 메소드 선언
public void method1(){ //리턴값이 없다면 void를 사용 System.out.println("mthod1이 실행됨"); } public void method2(int value){ //정수형 인자를 받음 System.out.println(value + "method2가 실행됨"); } public int method3(){ System.out.println("method3이 실행됨"); return 10; } // 리턴값을 설정했으니 리턴값을 줘야함 public void method4(int x, int y){ //여러개의 인자를 받음 System.out.println(x+y + "method4가 실행됨"); } public int method5(int x){ //정수형 인자를 받음 System.out.println(x + "method5가 실행됨"); return x*2; } // 받은 인자를 이용하여 리턴
- [Part.5] 메소드 사용해보기
선언한 메소드 사용
- 위에 클래스를 생성했다고 가정하고 진행(Myclass)
- 실행 시 선언했던 조건을 맞춰줘야 한다.
public static void main(String [] args){ Myclass myclass = new Myclass(); // myclass.을 이용하여 메소드 접근가능 myclass.method1(); myclass.method2(10); //정수형을 무조건 넣어줘야 한다. int value = myclass.method3(); //리턴값을 받아낼 변수가 필요 System.out.println(value); //받은 값 확인 myclass.method4(3,4); //2개의 정수값을 인자로 int value1 = myclass.method5(10); //정수 인자를 이용하여 리턴값 받음 System.out.println(value1); //확인 }
- [Part.5] String 클래스의 메소드
### 필요한 클래스를 구현하는 방법도 있지만 이미 만들어진 클래스들을 이용할 수 있다.
자주 사용하는 String 클래스의 메소드 확인
public static void main(String[] args){ String str = "Hello"; str.length(); // 문자열의 길이를 반환해주며 공백도 하나의 문자로 인식한다. str.concat(" World"); // 문자열을 더해준다 -> Hello World /* 이때 str을 확인해보면 Hello World가 아닌 Hello로 나온다. 즉 concat을 사용하면 새롭게 생성한 String Hello World를 반환하다. */ str = str.concat(" World"); // 이 처럼 사용해야 str값이 변환된다. str.substring(3); //3번 인덱스부터 잘라준다. str.substring(3,6); // 3번부터 6번까지 인덱스를 잘라준다. }
- [Part.5] 변수의 scope와 static
### 변수의 사용범위 : 변수가 선언된 블록
public class VariableScopeExam{ int globalscope = 10; public void scopeType1(int value){ int localscope =20; globalscope = value; //가능 localscope = 40; //가능 } public void scopeType2(int value){ globalscope = value; //가능 localscope = 40; //불가능 } public static void main(String[] args){ globalscope = 100; //불가능 localscope = value; //불가능 } }
### 모든 클래스는 인스턴스화 하지 않은 채로 사용할 수 없다.
- 붕어빵틀 ≠ 붕어빵
### static 키워드를 사용하면 인스턴스화(객체를 생성) 하지않아도 사용이 가능하다.
public class VariableScopeExam{ int globalscope = 10; static int staticValue = 10; public void scopeType1(int value){ int localscope =20; globalscope = value; //가능 localscope = 40; //가능 } public void scopeType2(int value){ globalscope = value; //가능 localscope = 40; //불가능 } public static void main(String[] args){ globalscope = 100; //불가능 localscope = value; //불가능 staticValue = 20 // 가능 VariableScopeExam v1 =new VariableScopeExam(); VariableScopeExam v2 =new VariableScopeExam(); v1.globalscope = 100; v2.globalscope = 200; // 위처럼 객체를 생성해서 사용해야 하며 각각 다른객체 이므로 다른값이 들어간다. v1.staticValue = 100; v2.staticValue = 200; // static 필드는 값을 공유하므로 두 객체는 같은값을 가지고 있다. } }
### 클래스 변수
- static한 변수, 값을 저장할 수 있는 공간이 하나뿐이여서 값을 공유한다.
- 클래스 이름을 직접 사용하는 것이 가능하다.
- 클래스이름.클래스변수명
ex) VariableScopeExam.staticValue
### 글로벌 변수를 선언할 때 static을 사용하면 되는것인가?!
- [Part.5] 열거형
### JDK5에서 추가된 문법이다 (enum)
- 기존 사용방식
public class EnumEx{ public static final String MALE ="MALE"; public static final String FEMALE ="FEMALE"; public static void main(String [] args){ String gender1; //MALE 과 FEMAL 둘 중 하나의 값을 넣고싶음 gender1 = EnumEx.MALE; gender1 = EnumEx.FEMALE; gender1 = "boy"; //하지만 다른 string 값이 들어와도 오류를 발생시키지 않는다. Gender gender2; gender2 = Gender.MALE; gender2 = Gender.FEMALE; gender2 = "boy"; //에러 } enum Gender{ MALE,FEMALE; } }
위처럼 특정 값만 사용할 때는 열거형을 사용하면 좋다
- 다른값이 들어왔을 때 오류가 생길 수 있을때 사용하면 좋아보인다.
// 특징 : 메모리에 로드가 안되있음
// 메모리 로드 : 객체 생성
class Dog{
String name = "Toto";
int age =10;
String color = "Black";
int hungry = 100; //배고픔 지수
// 필드 생성
void eatFood(){
hungry = hungry - 20;
}
// 메소드 생성
}
class Food{
int beef = 10;
void eat(){
beef --;
}
}
void main() {
Dog myDog = new Dog(); //new 연산자 생략가능
// 객체 생성
print(myDog.name);
print(myDog.age);
print(myDog.color);
print(myDog.hungry);
Food F = new Food();
if(myDog.hungry>50){
myDog.eatFood();
F.eat();
print("배고픔 지수 : ${myDog.hungry}");
print("남은 고기의 양 ${F.beef}");
}
}
9. 생성자와 선택적 매개변수
선택적 매개변수
- 생성자 ({매개변수 }); 형태로 사용이 가능하며 생성자 호출 시 key - value의 형태로 인자를 넣어준다 key-value의 형태이므로 순서에 상관이없다.
// 특징 : 메모리에 로드가 안되있음
// 메모리 로드 : 객체 생성
class Dog{
String? name;
int? age;
String? color;
int? hungry;
Dog({this.name, this.age, this.color, this.hungry});
//생성자
// this 키워드를 이용하여 현재 들어오는 인자를 자기자신 필드에 대입
// 선택적 매개변수 선언
}
void main() {
Dog myDog1 = Dog(name:"Toto", color:"white", age:1, hungry:100);
// Dog myDog2 = new Dog("Rab", 1 , "Black ", 100);
}
10. cascade 연산자
함수를 호출 할 때 ..함수명() 을 이용하여 객체를 넘기면서 함수를 같이 실행 할 수 있다.
class Chef {
void cook(){
print("요리를 시작합니다.");
}
void handWash(){
print("손을 씻습니다.");
}
}
// 아래 함수는 내가 수정할 수 없는 함수라고 가정
void goCompany(Chef chef){
print("회사에 갑니다.");
}
void main() {
goCompany(Chef()..handWash()..cook());
//객체를 넘기면서 함수를 실행할 수 있다.
// goCompany 함수가 실행되면서 handWash -> cook 순으로 함수가 먼저 실행된다.
// 손을 씻습니다. -> 요리를 시작합니다. -> 회사에 갑니다. 순으로 출력된다.
}
11. 상속 및 이니셜라이즈 키워드
상속이 되려면 다형성이 성립 되어야 한다.
BMW ≠ 엔진 따라서 다형성이 성립되지 않는다.
치즈햄버거 == 햄버거 따라서 다형성이 성립이 된다
이니셜라이즈 키워드는 부모생성자를 실행할 때 인자로 넘겨주는 값을 말한다.
CheeseBurger(String name) : super(name)
// 부모에게 값을 넘겨주는 방법
class Burger{
String? name;
Burger(this.name){
print("버거");
print(name);
} //Burger 생성자
}
class CheeseBurger extends Burger{
CheeseBurger(String name) : super(name){
super.name = name;
// name 필드는 부모의 필드이므로 super키워드를 이용하여 호출해야 한다.
print("치즈버거");
} ///CheeseBurger 생성자
}
void main() {
CheeseBurger Cb = CheeseBurger("치즈햄버거");
// Burger Cb = CheeseBurger(); //이거 또한 가능하다.
print(Cb.name);
}
12. mixin
다형성이 성립하지 않은 객체에 코드를 재사용할 때 사용하는 방법이다.
class Engine{
int power = 5000;
}
// 코드를 재사용할 때 사용하는 방법이다 extends와는 다르다.
class BMW with Engine{
}
void main() {
BMW bmw = BMW();
print(bmw.power);
}
13. 추상클래스
객체들의 타입 통일성을 유지하기 위한 추상클래스
이 때 추상클래스는 추상메소드를 생성할 수 있고 추상클래스의 implements된 클래스들은 이 메소드를 override해서 부모의 함수를 무효화시키고 재정의 해준다.
abstract class Animal{
void sound();
} //추상클래스로 통일된 메소드를 생성
class Dog implements Animal{
@override //부모의 함수를 무효화시킨다.
void sound(){
print("멍멍");
}
}
class Cat implements Animal{
@override //부모의 함수를 무효화시킨다.
void sound(){
print("야옹");
}
}
class Fish implements Animal{
@override //부모의 함수를 무효화시킨다.
void sound(){
print("뻐금뻐금"); // 재정의
}
}
void main() {
Animal dog = Dog();
Cat cat = Cat();
dog.sound();
cat.sound();
Fish fish = Fish();
fish.sound();
//새로운 클래스를 추가할 때 메소드의 이름이 달라질 수 있다. 이 때 혼란이 생길 수 있음
}
14. 컬렉션(List, Map)
수집된 물품들 (컬렉션) → 여러가지 데이터를 담을 수 있는 자료형
- LIST
void main() {
List<int>list = [1,2,3,4];
print(list[0]);
var list2 = [5,6,7];
print(list2[2]);
}
- MAP
클래스와 비슷하다.
Key - value 형식이며 dynamic을 사용하면 value에는 어떠한 값도 올 수 있다.
class User{
int id =1;
String username ="홍길동";
}
void main() {
Map<String, dynamic> user ={
"id" : 1,
"username" : "홍길동"
};
print(user["id"]);
print(user["username"]);
// map으로 접근
User u = User();
print(u.id);
print(u.username);
// class로 접근
}
15. 반복문
- for(초기화식, 조건식, 증감식)
void main() {
var list =[1,2,3,4];
print(list[0]);
print(list[1]);
print(list[2]);
print(list[3]);
for(int i=0; i<4; i++){
print(list[i]);
}
}
- map
값을 하나씩 변형시켜 리턴할 때 사용하며 iterator값이 반환되므로 .toList()를 이용하여 list로 바꿔준다.
void main() {
var coffee = ["아메리카노", "카페라떼","아이스티"];
var coffeeChange = coffee.map((i)=> "Venti_"+i).toList(); //for문과 다르게 리턴값이 있다.
print(coffeeChange);
// 값을 변형할 때 사용한다.
}
- where
필터링을 하거나 필요한 값을 찾을 때 사용한다. map과 마찬가지로 iterator값이 반환된다.
void main() {
var coffee = ["아메리카노", "카페라떼","아이스티"];
var coffeeChange = coffee.where((i)=> i!="카페라떼").toList();
print(coffeeChange);
}
16. 스프레드 연산자(중요)
스프레드 == 흩뿌리다
List의 깊은복사 와 map의 깊은복사는 차이가 있다.
void main() {
var list = [1,2,3];
var newList1 = [...list]; // Call by Value // 깊은 복사
var newList2 = list ; // Call by reference // 얕은 복사
print(list);
print(newList1);
print(newList2);
list[0] = 10;// 리스트의 값 변경
print(list);
print(newList1);
print(newList2);
//--Map에서의 깊은 복사는 제대로 되지 않는다.
var listMap = [{"id" :1}, {"id":2}];
var submap = listMap;
// var newmap = [...map]; //이 때 주소를 반환하므로 얕은 복사가 된다.
var newmap = listMap.map((i)=>{...i}).toList();
// 위처럼 iterator를 흩뿌려 줘야한다.
print(listMap);
print(submap);
print(newmap);
listMap[0]["id"] = 10; //맵의 값을 변경
print(listMap);
print(submap);
print(newmap);
print(listMap.hashCode);
print(submap.hashCode);
print(newmap.hashCode);
}
void main() {
var users =[
{"id":1, "username" :"홍길동" ,"passwor":12345},
{"id":2, "username" :"홍길순" ,"passwor":6789}
]; // 이름을 추가 및 변경하는 방법
var newUsers = users.map((i)=>i["id"]==2? {...i,"username":"아메리카노"}:i).toList();
print(newUsers);
}
17. const와 final
const와 fianl 모두 값을 한 번 넣으면 변경이 불가능하다.
- fianl
- final은 데이터의 타입을 추론해주므로 자료형을 적을 필요는 없다.
- fianl은 무조건 선언 시 초기화 시켜줘야 한다.
- 생성자 호출 시 값을 초기화 시켜줄 수 있다.
class Animal{ final name; Animal(this.name); }
- const
- 컴파일 시 초기화 되야한다.
class Animal{ const name; Animal(this.name); } // ERROR!!!!!
- const는 동일한 객체에는 동일한 해쉬코드를 사용한다.
class Animal{ final name; const Animal(this.name); } void main() { Animal animal1 = const Animal("강아지"); Animal animal2 = const Animal("강아지"); print(animal1.hashCode); print(animal2.hashCode); // 동일한 해쉬코드 Animal animal3 = const Animal("강아지"); Animal animal4 = const Animal("고양이"); print(animal3.hashCode); print(animal4.hashCode); // 다른 해쉬코드 }
18. Null Safety
Null을 받기위해서는 ?를 이용해줘야 한다.
class Person{
final String? name;
final int? age;
// Person(this.name, this.age);
Person({this.name, this.age});
}
void main() {
// Person person1 = Person("홍길동",10);
Person person1 = Person(name :"홍길동", age : 10);
//선택적 매개변수로 데이터를 넣을 시 모든 값을 안 넣어줄 수 있으므로 Null safety가 적용된다.
print(person1.name);
print(person1.age);
}
required를 쓰면 무조건 매개변수를 넣어줘야한다.
class Person{
final String? name;
final int? age;
// Person(this.name, this.age);
Person({required this.name, required this.age});
}
void main() {
// Person person1 = Person("홍길동",10);
Person person1 = Person(name :"홍길동", age : 10);
//선택적 매개변수로 데이터를 넣을 시 모든 값을 안 넣어줄 수 있으므로 Null safety가 적용된다.
print(person1.name);
print(person1.age);
}
4장 Flutter로 스토어 앱 만들기
모든 것은 위젯
→ 플러터의 모든 요소 하나 하나는 위젯이다.
1. 앱의 기본구조
- Material 설정
- 안드로이드 : Material
- IOS : Cpertino
- Scaffold 설정
만들어져 있는 Scaffold를 이용
- 위젯을 이용하여 그림 그리기.
2. Column, Row 위젯
해당 이미지 2개 다운로드
- 새로운 프로젝트 생성
이 때 SDK경로는 그대로 두고 프로젝트 이름은 flutter_store로 해준 뒤 workspace를 원하는 곳에 잡아준다.
- main 아래에 모든 코드를 삭제 한 후
main 함수 밑에 stless
를 입력한 후 아래와 같이 class MyApp extends StatelessWidget
을 입력
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
}
}
return Container()
을 안드로이드를 위한 MaterialApp 과 Scaffold로 바꿔준다.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(),
);
}
}
- 에뮬레이터를 실행 후 Hello world 확인
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Text("Hello world!"),
),
);
}
}
위와 같이 잘 나온다면 된다.
- assets 폴더 생성
작업중인 flutter 프로젝트를 클릭해서 새로운 assets 폴더 생성 후 이미지 2장을 넣어준다.
- yaml 파일 수정
들여쓰기에 따라 코드가 수행되지 않을 수 있으므로 잘 맞춰줘야 한다.
- 상단에 원하는 텍스트 삽입
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp( //안드로이드 앱이므로
home: StorePage(),
);
}
}
class StorePage extends StatelessWidget {
const StorePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column( // 컬럼과 로우를 이용하여 원하는 곳에 텍스트 삽입
children: [
Row(
children: [
Text("Women"),
Text("Kids"),
Text("Shoes"),
Text("Bag")
],
)
],
),
);
}
}
3. SafeArea, Spacer, Padding 위젯
- SafeArea
2 번과정에서 텍스트가 상태창을 침범하였다 이를 방지하기 위하여 SafeArea를 사용할 수 있다.
option + Enter 키를 입력하여 컬럼을 위젯으로 감싸준 뒤 위젯을 → SafeArea로 바꿔준다.
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Row(
children: [
Text("Women"),
Text("Kids"),
Text("Shoes"),
Text("Bag")
],
)
],
),
),
);
}
}
- Text Style 변경
- FontWeight.bold → 글자가 진해짐
Text("text", style: TextStyle(fontWeight: FontWeight.bold),
- Spacer()를 이용하여 Text간의 공간 확보
Row(
children: [
Text(
"Women",
style: TextStyle(fontWeight: FontWeight.bold),
),
Spacer(),
Text(
"Kids",
style: TextStyle(fontWeight: FontWeight.bold),
),
Spacer(),
Text(
"Shoes",
style: TextStyle(fontWeight: FontWeight.bold),
),
Spacer(),
Text(
"Bag",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
)
- Padding 설정
-
위 아래로 여백을 주기 위한 Padding 설정하며 Row에 감싸준다.
Row클릭 후 option + Enter키로 Padding은 25로 설정
Padding(
padding: const EdgeInsets.all(25),
child: Row(
children: [
Text(
"Women",
style: TextStyle(fontWeight: FontWeight.bold),
),
Spacer(),
Text(
"Kids",
style: TextStyle(fontWeight: FontWeight.bold),
),
Spacer(),
Text(
"Shoes",
style: TextStyle(fontWeight: FontWeight.bold),
),
Spacer(),
Text(
"Bag",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
)
debug 표시를 없애주는 코드 debugShowCheckedModeBanner: false
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: StorePage(),
);
}
}
완성된 모습
4. mage, Expanded, SizeBox 위젯
아래 모양을 클릭 후 웹에서 현재 app의 범위에 대한 정보를 볼 수 있음 (크롬에서 실행)
위에서 만든 Text밑에 이미지를 추가해야 하므로 padding 밑에 코딩 진행
- Image.asset()을 이용하여 이미지 추가
Image.asset("assets/bag.jpeg"),
// 저장해뒀던 이미지 경로
- Expanded를 이용하여 여백 제거
Image를 위젯으로 감싼 뒤 Expanded로 바꿔 준다.
Expanded(
flex : 1 // 1:1비율로 맞춰준다.
child: Image.asset("assets/bag.jpeg")),
이 때 이미지의 여백을 완전히 제거하려면
Image.asset(
"assets/bag.jpeg"
fit : BoxFit.cover,
),
// 위 코드를 이용하면 비율에 맞게 이미지가 확장되며 넘어간 부분은 잘린다.
- SizeBox
- Spacer와 다르게 빈공간이 없어도 공간을 잡아주며 추가적인 마진을 줄 때 사용한다.
- 두 개의 붙여진 이미지가 너무 붙어있으므로 약간의 여백을 주기위해 사용
- Expanded 사이에 아래의 코드를 삽입하여 2의 여백의 공간을 만들어준다.
SizedBox(
height: 2,
),
총 완성 코드
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: StorePage(),
);
}
}
class StorePage extends StatelessWidget {
const StorePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(25),
child: Row(
children: [
Text(
"Women",
style: TextStyle(fontWeight: FontWeight.bold),
),
Spacer(),
Text(
"Kids",
style: TextStyle(fontWeight: FontWeight.bold),
),
Spacer(),
Text(
"Shoes",
style: TextStyle(fontWeight: FontWeight.bold),
),
Spacer(),
Text(
"Bag",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
),
Expanded(
flex: 1,
child: Image.asset(
"assets/bag.jpeg",
fit: BoxFit.cover,
),
),
SizedBox(
height: 2,
),
Expanded(
flex: 1,
child: Image.asset(
"assets/cloth.jpeg",
fit: BoxFit.cover,
),
),
],
),
),
);
}
}
완성 이미지
- 앱은 Material(안드로이드) Scaffold 로 구조를 생성 이 두가지는 기본이다.
- 상태창에 침범을 막기 위하여 SafeArea로 감싸준다.
- 전체 레이어는 컬럼이다.
- Spacer를 이용하여 각각의 여백을 만들 수 있으며
- BoxFit.cover를 이용하여 이미지의 크기를 확장시킬 수 있다.
5장 레시피 앱 만들기
이미지 3장 다운로드
1장 레시피 앱 구조 보기
- flutter_recipe라는 새로운 프로젝트 생성
2장 앱 뼈대 구성
작업순서
- flutter_recipe/asserts 폴더 생성
- flutter_recipe/asserts/fonts 폴더 생성
- flutter_recipe/asserts/images 폴더 생성
- flutter_recipe/asserts/fonts 폰트 추가
- flutter_recipe/asserts/images 이미지 추가
- lib/components 폴더 생성
- 자주 사용하는 디자인을 컴포넌트로 만들면 재사용하기 용이하다.
- lib/components/recipe_title.dart 파일 추가
- lib/components/recipe_menu.dart 파일 추가
- lib/components/recipe_list_item.dart 파일 추가
- lib/pages 폴더 생성
- lib/pages/recipe_page.dart 파일 추가
- pubspec.yaml에서 이미지 파일과 폰트를 인식을 위한 자원 폴더 위치 설정
- Pub get을 누르고 애뮬레이터를 키고 컴파일
3장 기본 코드 작성
- lib/components/recipe_title.dart 파일 수정
// 커스텀 위젯 만들기 //
import 'package:flutter/material.dart';
// android 앱을 만들기 위해서 material import
class RecipeTitle extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}
- lib/components/recipe_menu.dart 파일 수정
// 커스텀 위젯 만들기 //
import 'package:flutter/material.dart';
// android 앱을 만들기 위해서 material import
class RecipeMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
children: [
Container()
],
);
}
}
- lib/components/recipe_list_item.dart 파일 수정
final은 값을 초기화 시켜줘야 한다
이 때 생성자를 이용하여 초기화 가능
final 변수 위에서 option + enter -> Create constructor for final fields 클릭
// 커스텀 위젯 만들기 //
import 'package:flutter/material.dart';
// android 앱을 만들기 위해서 material import
class RecipelistItem extends StatelessWidget {
// final은 값을 초기화 시켜줘야 한다
// 이 때 생성자를 이용하여 초기화 가능
// option + enter -> Create constructor for final fields 클릭
final String imagename;
final String title;
const RecipelistItem(this.imagename, this.title);
@override
Widget build(BuildContext context) {
return Container();
}
}
- lib/pages/recipe_page.dart 파일 수정
// 커스텀 위젯 만들기 //
import 'package:flutter/material.dart';
// android 앱을 만들기 위해서 material import
class RecipePage extends StatelessWidget {
const RecipePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
- lib/main.dart 파일 수정
import 'package:flutter/material.dart';
import 'package:flutter_recipe/pages/recipe_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: RecipePage(),
);
}
}
Hello World가 잘 나오면 main파일과 연동이 성공한 것이다. 이제 page파일을 재수정하면 된다.
- lib/pages/recipe_page.dart 파일 재수정
return Scaffold()
- Background 컬러 변경
backgroundColor: Colors.*white*,
- AppBar 함수 생성
appBar: AppBar(),
명령어를 실행 한 후 AppBar() 커서 위에서 ctrl+ alt + m 으로 메소드 만들기
위와 같이 입력 후 Refactor
- 앱 구조에 맞게 body태그 안에 Column 과 그 안에서 사용할 컴포넌트 추가
body: Column(
children: [
RecipeTitle(),
RecipeMenu(),
RecipelistItem("coffee", "Made Coffee"),
RecipelistItem("burger", "Made Burger"),
RecipelistItem("pizza", "Made Pizza")
],
));
총 완성 코드
// 커스텀 위젯 만들기 //
import 'package:flutter/material.dart';
import 'package:flutter_recipe/components/recipe_menu.dart';
import 'package:flutter_recipe/components/recipe_title.dart';
import 'package:flutter_recipe/components/recipte_list_item.dart';
// android 앱을 만들기 위해서 material import
class RecipePage extends StatelessWidget {
const RecipePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: _buildRecipeAppBar(),
body: Column(
children: [
RecipeTitle(),
RecipeMenu(),
RecipelistItem("coffee", "Made Coffee"),
RecipelistItem("burger", "Made Burger"),
RecipelistItem("pizza", "Made Pizza")
],
));
}
AppBar _buildRecipeAppBar() => AppBar();
}
저장 후 확인해보면 아무것도 보이지는 않지만 오류가 안난다면 정상적으로 코드를 작성한 것이다.
4장 AppBar (Page 수정)
- AppBar의 기본 구조
icon 입력 후 원하는 아이콘 라이브러리 사용가능
- 만들고자 하는 AppBar
AppBar _buildRecipeAppBar() => AppBar();
함수 수정
actions : [] 태그 안에 원하는 아이콘 추가
- 아이콘 위젯은 2개가 존재
- Material → 안드로이드
- Cupurtino → 애플
둘 중 더 이쁜걸 사용
Icon(CupertinoIcons.원하는 아이콘)
을 이용하여 원하는 아이콘 삽입
- 색상 , 사이즈 등 여러가지 인자를 추가적으로 받을 수 있다.
AppBar 메소드 총 완성 코드
AppBar _buildRecipeAppBar() => AppBar(
backgroundColor: Colors.white, // AppBar 색상
elevation: 1.0, // AppBar 하단 그림자 조절
actions: [
Icon(
CupertinoIcons.search, // 아이콘
color: Colors.black, // 색상
),
SizedBox(width: 15), //아이콘끼리 간격
Icon(
CupertinoIcons.heart,
color: Colors.redAccent,
),
],
);
5장 Font 설정 및 Text 위젯 디자인(Title , Main, Page 파일 수정)
폰트 또는 이미지가 적용되려면 에뮬레이터를 한 번 정지했다가 켜야 한다.
- lib/components/recipe_title.dart 파일 수정
Title에 쓰이는 컴포넌트이므로 Text를 반환한다.
style: TextStyle(fontSize: 30)
를 이용하여 원하는 텍스트 스타일의 크기로 수정해준다.padding: const EdgeInsets.only(top: 8.0)
을 이용하여 AppBar와의 간격 유지
수정 코드(recipe_title.dart)
// 커스텀 위젯 만들기 //
import 'package:flutter/material.dart';
// android 앱을 만들기 위해서 material import
class RecipeTitle extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
"Recipes",
style: TextStyle(fontSize: 30),
),
);
}
}
- lib/pages/recipe_page.dart 파일 수정
- 최초 타이틀 Text가 정중앙에 위치 → 제일 왼쪽으로 옮기기 위하여 Colum 태그에 다음 코드 삽입
crossAxisAlignment: CrossAxisAlignment.start,
- 양 끝 텍스트간의 약간의 여백을 위하여 패딩 설정
padding: const EdgeInsets.symmetric(horizontal: 20),
수정 코드(recipe_page.dart)
// 커스텀 위젯 만들기 //
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_recipe/components/recipe_menu.dart';
import 'package:flutter_recipe/components/recipe_title.dart';
import 'package:flutter_recipe/components/recipte_list_item.dart';
// android 앱을 만들기 위해서 material import
class RecipePage extends StatelessWidget {
const RecipePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: _buildRecipeAppBar(),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RecipeTitle(),
RecipeMenu(),
RecipelistItem("coffee", "Made Coffee"),
RecipelistItem("burger", "Made Burger"),
RecipelistItem("pizza", "Made Pizza")
],
),
));
}
AppBar _buildRecipeAppBar() => AppBar(
backgroundColor: Colors.white, // AppBar 색상
elevation: 1.0, // AppBar 하단 그림자 조절
actions: [
Icon(
CupertinoIcons.search, // 아이콘
color: Colors.black, // 색상
),
SizedBox(width: 15), //아이콘끼리 간격
Icon(
CupertinoIcons.heart,
color: Colors.redAccent,
),
],
);
}
- lib/main.dart 파일 수정
- 폰트 추가를 위하여 폰트 테마 설정
theme: ThemeData(fontFamily: "PatuaOne")
이 때 자신이 지정해 놓은 폰트 이름과 같아야 함
수정 코드(main.dart)
import 'package:flutter/material.dart';
import 'package:flutter_recipe/pages/recipe_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(fontFamily: "PatuaOne"),
home: RecipePage(),
);
}
}
6장 Container 디자인(Menu 파일 수정)
메뉴 생성
컨테이너 하나를 만들어서 재사용
- 컨테이너 특징
- 자식이 없는 컨테이너는 가능한 한 박스를 크게 만들려고 한다.
- 자식이 있는 컨테이너는 자식의 크기에 맞게 조정이 된다.
lib/components/recipe_menu.dart 파일 수정
### 1. Container를 만들어야 하므로 Container()태그에 코드 추가
### 2. 컨테이너 꾸미기
decoration: BoxDecoration()
를 이용하여 컨테이너 모양을 수정 할 수 있다.- 컨테이너 사각형의 색상 변경
border: Border.all(원하는 색상)
- 컨테이너 사각형을 라운드 지게 만들기
borderRadius: BorderRadius.circular(원하는 반지름)
decoration: BoxDecoration( border: Border.all( color: Colors.black12, ), borderRadius: BorderRadius.circular(30)),
### 3. 컨테이너는 자식을 하나밖에 가질 수 없다 위 아래로 아이콘과 텍스트를 넣어야 하므로 컬럼 자식 생성
child: Column()
- 컨테이너 안에 있는 아이콘과 텍스트의 위치 설정
mainAxisAlignment: MainAxisAlignment.center
- 아이콘과 텍스트가 들어가야하므로 childen[] 생성 하여 아이콘과 텍스트 설정
- 아이콘 설정
Icon(Icons.food_bank, color: Colors.*redAccent*, size: 30,)
- 텍스트 설정
Text(("text"))
- 아이콘과 텍스트 사이에 SizeBox를 이용하여 공백 생성
child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.food_bank, color: Colors.redAccent, size: 30, ), SizedBox( height: 5, ), Text(("text")) ], )
### 4. (3)에서 만든 컨테이너 메소드 화
Container 클릭 후 메소드 만들기 클릭
- Icon 인자와 Text 인자를 받도록 설정
Container _buildMenuItem(IconData mIcon, String text)
- 4개의 메뉴아이템 메소드에 인자를 넣어서 완성
_buildMenuItem(Icons.food_bank, "ALL"), SizedBox(width: 25), _buildMenuItem(Icons.emoji_food_beverage, "Coffee"), SizedBox(width: 25), _buildMenuItem(Icons.fastfood, "Burger"), SizedBox(width: 25), _buildMenuItem(Icons.local_pizza, "Pizza"),
### 5. Title과의 간격을 위하여 패딩 설정
padding: const EdgeInsets.only(top: 20)
### 완성 코드(Menu.dart)
// 커스텀 위젯 만들기 // import 'package:flutter/material.dart'; // android 앱을 만들기 위해서 material import class RecipeMenu extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(top: 20), child: Row( children: [ _buildMenuItem(Icons.food_bank, "ALL"), SizedBox(width: 25), _buildMenuItem(Icons.emoji_food_beverage, "Coffee"), SizedBox(width: 25), _buildMenuItem(Icons.fastfood, "Burger"), SizedBox(width: 25), _buildMenuItem(Icons.local_pizza, "Pizza"), ], ), ); } Container _buildMenuItem(IconData mIcon, String text) { return Container( height: 80, width: 60, decoration: BoxDecoration( border: Border.all( color: Colors.black12, ), borderRadius: BorderRadius.circular(30)), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( mIcon, color: Colors.redAccent, size: 30, ), SizedBox( height: 5, ), Text((text)) ], ), ); } }
7장 SingleChildScrowView(Menu 파일 수정)
핸드폰 화면에 따라 컨테이너를 추가했을 때 Overflow가 나는 경우가 있다.
이 때 스크롤을 사용하여 이를 방지할 수 있다.
- 컨테이너를 1개 더 추가한 모습 Overflow가 난다
SingleChildScrollView()
를 이용- Row태그를 감싼 후 → 위젯 설정 → SingleChildScrollView()클릭
→ scrollDirection: Axis.horizontal
를 입력
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
이후 화면을 확인해보면 스크롤로 Overlflow난 컨테이너를 확인할 수 있다.
완성 코드(Menu)
// 커스텀 위젯 만들기 //
import 'package:flutter/material.dart';
// android 앱을 만들기 위해서 material import
class RecipeMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 20),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildMenuItem(Icons.food_bank, "ALL"),
SizedBox(width: 25),
_buildMenuItem(Icons.emoji_food_beverage, "Coffee"),
SizedBox(width: 25),
_buildMenuItem(Icons.fastfood, "Burger"),
SizedBox(width: 25),
_buildMenuItem(Icons.local_pizza, "Pizza"),
SizedBox(width: 25),
_buildMenuItem(Icons.local_bar_rounded, "Cocktail"),
],
),
),
);
}
Container _buildMenuItem(IconData mIcon, String text) {
return Container(
height: 80,
width: 60,
decoration: BoxDecoration(
border: Border.all(
color: Colors.black12,
),
borderRadius: BorderRadius.circular(30)),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
mIcon,
color: Colors.redAccent,
size: 30,
),
SizedBox(
height: 5,
),
Text((text))
],
),
);
}
}
8장 ListView와 재사용 가능한 위젯 클래스 생성(list_item, Page 파일 수정)
이미지
텍스트(제목)
텍스트(내용)
위 와 같은 구조이다.
- lib/components/recipe_list_item.dart 파일 수정
1. 컬럼 구조이므로 컬럼 태그안에서 코드 진행
Column()
2. 컬럼 태그 안에서 childen[] 리스트 안에 3개의 구조 삽입
- 이미지
Image.asset(
"assets/images/$imagename.jpeg",
)
들어온 인자에 맞는 경로에 이미지 삽입
- 텍스트(제목)
Text(
title,
style: TextStyle(fontSize: 20),
)
들어온 인자에 맞게 타이틀 제목 삽입
- 텍스트(내용)
Text("원하는 문구",
style: TextStyle(
color: Colors.*grey*,
fontSize: 12)
원하는 문구 삽입
3. 이미지 사이 간격을 두기 위해서 패딩 위젯으로 감싸기
padding: const EdgeInsets.symmetric(vertical: 20)
- SizeBox를 이용하여 이미지와 텍스트간 간격 만들기
SizedBox()
4. 이 때 사진이 화면을 넘어가서 또 Overflow가 발생한다.
- lib/pages/recipe_page.dart 파일 수정
컬럼 구조로 되어있던 구조로 ListView로 변경해주면 해결 가능
Column()
→ ListView()
로 변경
ListView는 crossAxisAlignment: CrossAxisAlignment.start
가 없으므로 삭제해준다.
Overflow가 없어진 모습
완성 코드(page.dart)
// 커스텀 위젯 만들기 //
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_recipe/components/recipe_menu.dart';
import 'package:flutter_recipe/components/recipe_title.dart';
import 'package:flutter_recipe/components/recipe_list_item.dart';
// android 앱을 만들기 위해서 material import
class RecipePage extends StatelessWidget {
const RecipePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: _buildRecipeAppBar(),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: ListView(
children: [
RecipeTitle(),
RecipeMenu(),
RecipelistItem("coffee", "Made Coffee"),
RecipelistItem("burger", "Made Burger"),
RecipelistItem("pizza", "Made Pizza")
],
),
));
}
AppBar _buildRecipeAppBar() => AppBar(
backgroundColor: Colors.white, // AppBar 색상
elevation: 1.0, // AppBar 하단 그림자 조절
actions: [
Icon(
CupertinoIcons.search, // 아이콘
color: Colors.black, // 색상
),
SizedBox(width: 15), //아이콘끼리 간격
Icon(
CupertinoIcons.heart,
color: Colors.redAccent,
),
],
);
}
완성 코드(list_item.dart)
// 커스텀 위젯 만들기 //
import 'package:flutter/material.dart';
// android 앱을 만들기 위해서 material import
class RecipelistItem extends StatelessWidget {
// final은 값을 초기화 시켜줘야 한다
// 이 때 생성자를 이용하여 초기화 가능
// option + enter -> Create constructor for final fields 클릭
final String imagename;
final String title;
const RecipelistItem(this.imagename, this.title);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Column(
children: [
Image.asset(
"assets/images/$imagename.jpeg",
),
SizedBox(
height: 10,
),
Text(
title,
style: TextStyle(fontSize: 20),
),
Text(
"Have you ever made your own $title? Once you 've tried a homemade $title, you'll never go back.",
style: TextStyle(
color: Colors.grey,
fontSize: 12,
),
)
],
),
);
}
}
9장 AspectRatio, ClipRRect 위젯 (list_item 파일 수정)
이미지를 좀 더 이쁘게 꾸미기 위해서 2가지 위젯 사용
- lib/components/recipe_list_item.dart 파일 수정
1. 이미지 태그를 위젯으로 감싼 후 AspectRatio()
를 이용
- 인자로
aspectRatio: 2 / 1,
를 줘야한다 이 때 double자료형을 사용 - 이 때 이미지는 자신의 속성을 유지하려고 한다 이 때 지난강의에서 배운 cover를 이용
- 이미지 태그 안에
fit: BoxFit.cover
를 추가
- 이미지 태그 안에
2. 이미지 사각형의 라운드를 주기 위해 ClipRRect()
를 이용
borderRadius: BorderRadius.circular(20)
를 이용하여 원하는 라운드 값 삽입
완성 코드(list_item.dart)
// 커스텀 위젯 만들기 //
import 'package:flutter/material.dart';
// android 앱을 만들기 위해서 material import
class RecipelistItem extends StatelessWidget {
// final은 값을 초기화 시켜줘야 한다
// 이 때 생성자를 이용하여 초기화 가능
// option + enter -> Create constructor for final fields 클릭
final String imagename;
final String title;
const RecipelistItem(this.imagename, this.title);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Column(
children: [
AspectRatio(
aspectRatio: 2 / 1,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Image.asset(
"assets/images/$imagename.jpeg",
fit: BoxFit.cover,
),
),
),
SizedBox(
height: 10,
),
Text(
title,
style: TextStyle(fontSize: 20),
),
Text(
"Have you ever made your own $title? Once you 've tried a homemade $title, you'll never go back.",
style: TextStyle(
color: Colors.grey,
fontSize: 12,
),
)
],
),
);
}
}
6장 프로필 앱 만들기
0. 이미지 1장 다운로드
1장 프로필 앱 구조 보기
- flutter_profile이름의 새로운 프로젝트 생성
3장 기본 코드 작성
1. lib/components/파일 수정
-
lib/components/profile_header.dart 파일 수정
import 'package:flutter/material.dart'; class ProfileHeader extends StatelessWidget { @override Widget build(BuildContext context) { return Row( children: [_buildHeaderAvatar(), _buildHeaderProfile()], ); } Widget _buildHeaderProfile() { return SizedBox(); } Widget _buildHeaderAvatar() { return SizedBox(); } }
-
lib/components/profile_drawer.dart 파일 수정
import 'package:flutter/material.dart'; class PrifileDrawer extends StatelessWidget { @override Widget build(BuildContext context) { return Container( width: 200, height: double.infinity, //최대 크기만큼 확장 color: Colors.blue, ); } }
-
lib/components/profile_buttons.dart 파일 수정
import 'package:flutter/material.dart'; class ProfileButtons extends StatelessWidget { @override Widget build(BuildContext context) { return Row( children: [ _buildFollowButton(), _buildMessageButton(), ], ); } Widget _buildFollowButton() { return SizedBox(); } Widget _buildMessageButton() { return SizedBox(); } }
-
lib/components/profile_count_inf.dart 파일 수정
import 'package:flutter/material.dart'; class ProfileCountInfo extends StatelessWidget { @override Widget build(BuildContext context) { return Row( children: [ _buildInfo("50", "Posts"), _buildLine(), _buildInfo("30", "Likes"), _buildLine(), _buildInfo("10", "Share"), ], ); } Widget _buildInfo(String count, String title) { return SizedBox(); } Widget _buildLine() { return SizedBox(); } }
-
lib/components/profile_tab.dart 파일 수정
import 'package:flutter/material.dart'; //StateFull : 상태가 있는 위젯 -> 상태에 따라 위젯이 변경된다. class ProfileTab extends StatefulWidget { @override State<ProfileTab> createState() => _ProfileTabState(); } class _ProfileTabState extends State<ProfileTab> { @override Widget build(BuildContext context) { return Column( children: [ _buildTabBar(), _buildTabBarView(), ], ); } Widget _buildTabBar() { return SizedBox(); } Widget _buildTabBarView() { return SizedBox(); } }
2. lib/pages/파일 수정
-
lib/pages/profile_page.dart 파일 수정
import 'package:flutter/material.dart'; import 'package:flutter_profile/components/profile_buttons.dart'; import 'package:flutter_profile/components/profile_count_info.dart'; import 'package:flutter_profile/components/profile_header.dart'; import 'package:flutter_profile/components/profile_tab.dart'; class ProfilePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ ProfileHeader(), ProfileCountInfo(), ProfileButtons(), ProfileTab(), ], ), ); } }
3. lib/파일 수정
-
lib/theme.dart 파일 수정
import 'package:flutter/material.dart'; // primaryColor: 앱의 브랜드 색상 // secondary color(accent color) : 앱의 버튼이나, 상호작용 하는 이벤트 색상 ThemeData theme() { return ThemeData( fontFamily: "PatuaOne", primaryColor: Colors.blue, appBarTheme: AppBarTheme( backgroundColor: Colors.white, iconTheme: IconThemeData( color: Colors.blue, )), ); }
-
lib/main.dart 파일 수정
import 'package:flutter/material.dart'; import 'package:flutter_profile/components/profile_header.dart'; import 'package:flutter_profile/pages/ProfilePage.dart'; import 'package:flutter_profile/theme.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: theme(), home: ProfilePage(), ); } }
4장 AppBar (Page 수정)
- AppBar의 기본 구조
import 'package:flutter/material.dart';
import 'package:flutter_profile/components/profile_buttons.dart';
import 'package:flutter_profile/components/profile_count_info.dart';
import 'package:flutter_profile/components/profile_drawer.dart';
import 'package:flutter_profile/components/profile_header.dart';
import 'package:flutter_profile/components/profile_tab.dart';
import 'package:flutter_profile/theme.dart';
class ProfilePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
endDrawer: ProfileDrawer(),
appBar: _buildAppBar(),
body: Column(
children: [
ProfileHeader(),
ProfileCountInfo(),
ProfileButtons(),
ProfileTab(),
],
),
);
}
AppBar _buildAppBar() {
theme();
return AppBar(
leading: Icon(Icons.arrow_back_ios_new),
title: Text(
"Profile",
style: TextStyle(color: Colors.blue),
),
centerTitle: true,
);
}
}