Flutter

1장 플러터란?

플러터란 ?

고성능,고품질의 iOS, Android앱과 web을 단일 코드베이스로 개발할 수 있는 구글의 모바일 UI프레임 워크이며 구글이 제공하는 무료 오픈소스이며 네이티브 수준의 성능을 자랑한다.

#UI : user interface → 쉽게 생각하면 화면(그림)

  • 기본 코드베이스 - dart언어

iOS → swift필요

Android → Java 필요

하지만 플러더를 사용하면 안번에 해결가능

Skia 엔진 → 네이티브 수준의 성능을 낼 수 있는 이유

구글이 인수한 2D 그래픽 라이브러리(엔진)이며 다음과 같은 특징이 있다.

  • 리액트 네이티드(브릿지) 방식과 다르게 Skia엔진에 바로 원하는 그림을 그릴 수 있다.

→ IOS만든 그림을 Android에도 그릴 수있다.

  • AOT(프로덕션) 실제 서비스 환경

Ahead of time을 지원한다. Dart언어로 개발할 때 핸드폰에 빌드하기 전 사전 컴파일하여 코드를 빌드할 수 있다.

스크린샷 2022-08-19 오후 10 45 46

  • JIT(개발모드)

Just in time을 지원한다. Dart코드로 개발하고 →Dart가상머신이 이해하는 중간언어로 번환 후 실행한다.

이후 핸드폰에 환경에 맞게 실행하며 부분 컴파일로 빠른 실행이 가능하다.

스크린샷 2022-08-19 오후 10 48 08

2장 플러터 설치
1.플러터 설치

Mac을 기준으로 설치

플러터 다운로드페이지

위 링크에서 자신의 mac에 맞는 zip 파일 다운로드

  1. 플러터를 설치할 경로에 develope 폴더 생성
  2. 생성한 폴더에 다운받은 플러터를 압축해제
  3. 환경변수 설정
cd 
vi .zshrc

vi 텍스트 편집기가 열리면 아래 코드를 붙혀놓고 저장

export PATH="$HOME/development/flutter/bin:$PATH"
#-- export PATH="$HOME/"자신이 생성한 폴더 경로"/flutter/bin:$PATH"
  1. 설치 확인

다음 명령어로 플러터 설치 확인

flutter doctor 

스크린샷 2022-08-19 오후 11 15 14

위와 같이 나온다면 설치 완료.

2. IOS 개발을 위한 Xcode 설치
  1. app store → Xcode 설치(설치 시간이 조금 걸린다)
  2. Xcode를 한번 실행 한 후 터미널 실행
  3. 다음 코드 입력
sudo gem install cocoapods
pod setup
  1. 설치 확인
flutter doctor

스크린샷 2022-08-19 오후 11 47 08

Xcode가 잘 설치되었으면 성공적으로 설치완료!

3. Android 개발을 위한 Android Studio 설치
  1. Java 설치
  2. 해당 링크에서 안드로이드 스튜디오 설치 안드로이드 스튜디오 설치

스크린샷 2022-08-19 오후 11 49 49

  1. 다운받은 dmg파일 실행 후 안드로이드 스튜디오 실행

스크린샷 2022-08-19 오후 11 52 31 스크린샷 2022-08-19 오후 11 53 23 스크린샷 2022-08-19 오후 11 53 31

이후 계속 Next를 눌러서 설치를 완료한다.

이후 Dart 플러그인 설치

스크린샷 2022-08-19 오후 11 56 03

Flutter 플러그인 설치

스크린샷 2022-08-19 오후 11 56 45

  1. 설치된 안드로이드 스튜디오 확인
flutter doctor

만약 오류가 난다면 다음 과정을 따라한다.

  1. 화면 왼쪽 상단의 Android Studio를 클릭하고 Preferences를 클릭

스크린샷 2022-08-20 오전 12 03 40

  1. Appearance & Behavior > System Settings > Android SDK

스크린샷 2022-08-20 오전 12 04 38

  1. Hide Obsolete Pacakges 체크를 해제한 후

SDK Tools 탭에서 아래 의 세 가지를 찾아 체크된 상태로 만든 후 OK

  • Android SDK Command-line Tools (latest)
  • Android SDK Platform-Tools
  • Android SDK Tools (Obsolete)

없는 체크항목은 넘어가도 된다.

스크린샷 2022-08-20 오전 12 06 08

  1. 이후 터미널 실행 후 다음 명령어 실행
flutter doctor --android-licenses

무언가 묻는 창이 나오면 계속 y를 입력하고 엔터

  1. 설치 확인
flutter doctor

스크린샷 2022-08-20 오전 12 07 34

위 처럼 나온다면 설치 완료!

4. VScode 확장자 설치
  1. 플러터 확장자 설치 스크린샷 2022-08-20 오전 12 09 49

  2. Dart 확장자 설치 스크린샷 2022-08-20 오전 12 10 17

5. 에뮬레이터 설치
  1. 빨간색 원 모양 클릭 스크린샷 2022-08-20 오후 9 31 12

  2. Virtual device 클릭 스크린샷 2022-08-20 오후 9 32 40

  3. Phone → Pixel 3a 클릭 후 Next 스크린샷 2022-08-20 오후 9 33 08

  4. R버전 다운로드 스크린샷 2022-08-20 오후 9 33 19

  5. 에뮬레이터 실행 스크린샷 2022-08-20 오후 9 36 45

최초 실행 시 꽤나 오랜 시간이 걸린다. 스크린샷 2022-08-20 오후 9 42 26

  1. 잘 동작하는지 확인
3장 Dart 문법

Dart Pad에서 실습 진행

실습링크 스크린샷 2022-08-20 오후 9 56 25

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가지 타입이 존재한다.

    1. 기본형 타입
      • 논리형, 문자형, 정수형, 실수형
    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)

    객체 : 자동차

    필드 : 자동차의 구성요소 (속성)

    1. 차 이름
    2. 차량번호

    객체 : 학생

    필드 : 학생의 구성요소(속성)

    1. 이름
    2. 번호
      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. 상속 및 이니셜라이즈 키워드

상속이 되려면 다형성이 성립 되어야 한다.

스크린샷 2022-08-21 오후 4 02 32

BMW ≠ 엔진 따라서 다형성이 성립되지 않는다.

스크린샷 2022-08-21 오후 4 03 42

치즈햄버거 == 햄버거 따라서 다형성이 성립이 된다

이니셜라이즈 키워드는 부모생성자를 실행할 때 인자로 넘겨주는 값을 말한다.

  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. 앱의 기본구조
  1. Material 설정
  • 안드로이드 : Material
  • IOS : Cpertino

스크린샷 2022-08-21 오후 5 43 33

  1. Scaffold 설정

만들어져 있는 Scaffold를 이용

스크린샷 2022-08-21 오후 5 45 10

  1. 위젯을 이용하여 그림 그리기.
2. Column, Row 위젯

스크린샷 2022-08-21 오후 5 50 56 스크린샷 2022-08-21 오후 5 51 14

해당 이미지 2개 다운로드

cloth bag

  1. 새로운 프로젝트 생성 스크린샷 2022-08-21 오후 5 56 15 스크린샷 2022-08-21 오후 5 56 37

이 때 SDK경로는 그대로 두고 프로젝트 이름은 flutter_store로 해준 뒤 workspace를 원하는 곳에 잡아준다.

  1. 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();
  }
}
  1. return Container() 을 안드로이드를 위한 MaterialApp 과 Scaffold로 바꿔준다.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(),
    );
  }
}
  1. 에뮬레이터를 실행 후 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!"),
      ),
    );
  }
}

스크린샷 2022-08-21 오후 6 11 22

위와 같이 잘 나온다면 된다.

  1. assets 폴더 생성

스크린샷 2022-08-21 오후 6 12 14

작업중인 flutter 프로젝트를 클릭해서 새로운 assets 폴더 생성 후 이미지 2장을 넣어준다.

스크린샷 2022-08-21 오후 6 13 07

  1. yaml 파일 수정

들여쓰기에 따라 코드가 수행되지 않을 수 있으므로 잘 맞춰줘야 한다.

스크린샷 2022-08-21 오후 6 15 29 스크린샷 2022-08-21 오후 6 16 42

  1. 상단에 원하는 텍스트 삽입
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 위젯
  1. SafeArea

2 번과정에서 텍스트가 상태창을 침범하였다 이를 방지하기 위하여 SafeArea를 사용할 수 있다.

스크린샷 2022-08-21 오후 6 28 53

option + Enter 키를 입력하여 컬럼을 위젯으로 감싸준 뒤 위젯을 → SafeArea로 바꿔준다.

Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Row(
              children: [
                Text("Women"),
                Text("Kids"),
                Text("Shoes"),
                Text("Bag")
              ],
            )
          ],
        ),
      ),
    );
  }
}
  1. 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),
                )
              ],
            )
  1. 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(),
        );
      }
    }

완성된 모습

스크린샷 2022-08-21 오후 6 55 39

4. mage, Expanded, SizeBox 위젯

아래 모양을 클릭 후 웹에서 현재 app의 범위에 대한 정보를 볼 수 있음 (크롬에서 실행)

스크린샷 2022-08-21 오후 6 40 35

위에서 만든 Text밑에 이미지를 추가해야 하므로 padding 밑에 코딩 진행

  1. Image.asset()을 이용하여 이미지 추가
Image.asset("assets/bag.jpeg"),
// 저장해뒀던 이미지 경로
  1. Expanded를 이용하여 여백 제거

Image를 위젯으로 감싼 뒤 Expanded로 바꿔 준다.

Expanded(
	flex : 1 // 1:1비율로 맞춰준다.
	child: Image.asset("assets/bag.jpeg")),

이 때 이미지의 여백을 완전히 제거하려면

Image.asset(
		"assets/bag.jpeg"
		fit : BoxFit.cover,
),
// 위 코드를 이용하면 비율에 맞게 이미지가 확장되며 넘어간 부분은 잘린다.
  1. 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,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

완성 이미지

스크린샷 2022-08-21 오후 7 31 14

  1. 앱은 Material(안드로이드) Scaffold 로 구조를 생성 이 두가지는 기본이다.
  2. 상태창에 침범을 막기 위하여 SafeArea로 감싸준다.
  3. 전체 레이어는 컬럼이다.
  4. Spacer를 이용하여 각각의 여백을 만들 수 있으며
  5. BoxFit.cover를 이용하여 이미지의 크기를 확장시킬 수 있다.
5장 레시피 앱 만들기
이미지 3장 다운로드

burger coffee pizza

1장 레시피 앱 구조 보기
  • flutter_recipe라는 새로운 프로젝트 생성

스크린샷 2022-08-23 오후 10 45 20

스크린샷 2022-08-23 오후 10 43 25

2장 앱 뼈대 구성

작업순서

  1. flutter_recipe/asserts 폴더 생성
  2. flutter_recipe/asserts/fonts 폴더 생성
  3. flutter_recipe/asserts/images 폴더 생성
  4. flutter_recipe/asserts/fonts 폰트 추가
  5. flutter_recipe/asserts/images 이미지 추가
  6. lib/components 폴더 생성
    • 자주 사용하는 디자인을 컴포넌트로 만들면 재사용하기 용이하다.
  7. lib/components/recipe_title.dart 파일 추가
  8. lib/components/recipe_menu.dart 파일 추가
  9. lib/components/recipe_list_item.dart 파일 추가
  10. lib/pages 폴더 생성
  11. lib/pages/recipe_page.dart 파일 추가

스크린샷 2022-08-23 오후 10 55 00

  1. pubspec.yaml에서 이미지 파일과 폰트를 인식을 위한 자원 폴더 위치 설정

스크린샷 2022-08-23 오후 10 59 55

  1. Pub get을 누르고 애뮬레이터를 키고 컴파일

스크린샷 2022-08-23 오후 11 01 34

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()

  1. Background 컬러 변경

backgroundColor: Colors.*white*,

  1. AppBar 함수 생성

appBar: AppBar(), 명령어를 실행 한 후 AppBar() 커서 위에서 ctrl+ alt + m 으로 메소드 만들기

위와 같이 입력 후 Refactor

스크린샷 2022-08-23 오후 11 28 52

  1. 앱 구조에 맞게 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의 기본 구조

스크린샷 2022-08-23 오후 11 28 52

pub.dev

icon 입력 후 원하는 아이콘 라이브러리 사용가능

  • 만들고자 하는 AppBar

스크린샷 2022-08-24 오후 12 51 48

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를 반환한다.

  1. style: TextStyle(fontSize: 30) 를 이용하여 원하는 텍스트 스타일의 크기로 수정해준다.
  2. 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 파일 수정
    1. 최초 타이틀 Text가 정중앙에 위치 → 제일 왼쪽으로 옮기기 위하여 Colum 태그에 다음 코드 삽입

crossAxisAlignment: CrossAxisAlignment.start,

  1. 양 끝 텍스트간의 약간의 여백을 위하여 패딩 설정

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 파일 수정
    1. 폰트 추가를 위하여 폰트 테마 설정

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(),
    );
  }
}

스크린샷 2022-08-24 오후 12 51 45

6장 Container 디자인(Menu 파일 수정)

메뉴 생성

스크린샷 2022-08-24 오전 12 05 01

컨테이너 하나를 만들어서 재사용

  • 컨테이너 특징
    • 자식이 없는 컨테이너는 가능한 한 박스를 크게 만들려고 한다.

    스크린샷 2022-08-24 오전 12 10 19

    • 자식이 있는 컨테이너는 자식의 크기에 맞게 조정이 된다.

    스크린샷 2022-08-24 오전 12 10 44

    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 클릭 후 메소드 만들기 클릭

      스크린샷 2022-08-24 오전 12 25 47

      • 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가 난다

스크린샷 2022-08-24 오후 12 43 45

  • SingleChildScrollView()를 이용
  • Row태그를 감싼 후 → 위젯 설정 → SingleChildScrollView()클릭

scrollDirection: Axis.horizontal를 입력

child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,

이후 화면을 확인해보면 스크롤로 Overlflow난 컨테이너를 확인할 수 있다.

// 커스텀 위젯 만들기 //
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 파일 수정)

1

이미지

텍스트(제목)

텍스트(내용)

위 와 같은 구조이다.

  • 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가 발생한다.

스크린샷 2022-08-24 오후 12.59.33.png

  • lib/pages/recipe_page.dart 파일 수정

컬럼 구조로 되어있던 구조로 ListView로 변경해주면 해결 가능

Column()ListView() 로 변경

ListView는 crossAxisAlignment: CrossAxisAlignment.start 가 없으므로 삭제해준다.

2

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,
            ),
          )
        ],
      ),
    );
  }
}

3

6장 프로필 앱 만들기
0. 이미지 1장 다운로드

avatar

1장 프로필 앱 구조 보기
  • flutter_profile이름의 새로운 프로젝트 생성

스크린샷 2022-08-24 오후 8 19 18

스크린샷 2022-08-24 오후 8 21 15

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의 기본 구조

스크린샷 2022-08-23 오후 11 36 58

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,
    );
  }
}