https://www.youtube.com/watch?v=NpbdmmZSmFk
description: |
1. 이건 꼭 알아야 한다[^1]
[? 질문] Flutter 중급 과정(Part.3-1)에서 “화면 단위 UI 구성”을 어떤 방식으로 시작하는가[^1]
[= 답] 초급에서 배운 개별 위젯을 “묶어서” 한 화면을 구성하는 흐름(앱 뼈대 → Scaffold 표준 레이아웃 → Column/Row로 배치 → Padding/Space/SizedBox로 간격 → Image/Expanded로 영역 처리 → SafeArea로 상단 침범 방지)으로 진행한다.[^1]
[? 질문] 실무 Flutter 개발에서 기본 위젯만으로는 부족한데, 중급 과정에서 무엇을 추가로 다루려는가[^2]
[= 답] 실무에서는 서드파티 라이브러리를 많이 쓰며, 그중 대표로 상태 관리(State Management) 같은 필수 요소(또는 number format 등)와 **Firebase 알림(notification)**처럼 Flutter 기본 제공이 아닌 기능을 프로젝트에 “섞어 쓰는 방법”을 중급에서 다룬다고 방향을 제시한다.[^2]
[? 질문] 복잡한 UI를 Flutter 레이아웃으로 분석/구현하는 핵심 사고법은 무엇인가[^3]
[= 답] 화면을 먼저 세로로 썰고(Column 관점), 다음 가로로 썰고(Row 관점), 필요하면 그 안에서 다시 세로/가로로 반복해서 “컴포넌트” 단위로 쪼개면 대부분(약 90%)의 레이아웃을 만들 수 있으며, 나머지 10%는 Stack(절대좌표/레이어 겹침) 같은 방식으로 처리한다.[^3]
2. 큰 그림[^4]
이 콘텐츠는 Flutter 중급 과정의 첫 시간으로, 초급에서 다룬 개별 위젯들을 조합해 간단한 쇼핑몰 형태의 한 화면 레이아웃을 구성하는 실습을 진행한다.[^4] 또한 실무에서 필요한 **외부 라이브러리 사용(상태관리, Firebase 알림 등)**을 앞으로 다루기 위한 전제를 깔고, 이번 시간은 “작동”이 아니라 “배치” 중심으로 레이아웃 기본기를 다진다.[^4]
- 화면 뼈대 구성 흐름:
main()→runApp()→StatelessWidget(MyApp)→ MaterialApp →home:→ Scaffold(appBar/body)로 앱의 표준 골격을 세운다.[^5] - 레이아웃 배치 핵심 도구: 세로는 Column, 가로는 Row로 쪼개서 배치하고, 간격은 SizedBox / Space / Padding으로 해결한다.[^6]
- 리소스(assets)와 이미지 표시: 내장 이미지를 프로젝트에 포함시키려면
assets/폴더 구성 후pubspec.yaml에 등록하고Image.asset()으로 불러온다(들여쓰기/flutter pub get포함).[^7]
3. 하나씩 살펴보기[^8]
3.1 중급 과정에서 무엇을 할 것인가: “화면 단위” + “서드파티/상태관리” 예고[^9]
강사는 이번 파트가 “중급자 과정”이며 초급에서 했던 방식(문법, 위젯을 “낱개로” 살펴봄)과 달리, 중급에서는 위젯들을 묶어서 하나의 화면을 구성하는 연습을 한다고 선언한다.[^9] 다만 거대한 프로젝트를 만드는 것이 아니라, 화면 단위/단위 기능 단위로 배워나갈 것이라고 범위를 제한한다.[^9]
또한 실무에서는 Flutter 기본 위젯만 쓰기보다, 기능을 보완하는 외부(서드파티) 라이브러리를 많이 사용한다고 설명한다.[^10] 그중에서 “꼭 필요한 것”으로 **상태 관리(State Management)**를 언급하며, number format 같은 유틸 성격 라이브러리도 많다고 덧붙인다.[^10]
초급 말미에 다뤘던 Firebase도 예로 들며, 특히 Notification(알림) 용도로 많이 쓰는데 이것도 Flutter 기본 제공이 아니라 외부 라이브러리 지원으로 프로젝트에 붙여서 사용한다고 말한다.[^11] 중급 과정에서는 이런 외부 라이브러리들을 실제 업무에서 어떻게 쓰고 프로젝트와 믹스(mix)해 나가는지를 다룰 것이라고 예고한다.[^12]
3.2 오늘 만들 화면의 목표: “작동 X, 레이아웃만” 쇼핑몰 형태[^13]
오늘(첫 시간)은 여러 위젯을 사용해 “하나의 화면”을 구성해보는데, 사용할 위젯 목록으로 Material, Scaffold, Column, Row, Text, SafeArea, Image, Space, Expanded, Padding, SizedBox를 나열한다.[^13]
강사는 그림으로 목표 UI를 설명한다.[^14]
- 상단에 쇼핑몰 “메뉴”가 가로로 놓이고[^14]
- 그 아래에 상품 사진(예시로 2장)을 배치한다.[^14]
- 메뉴 예시: “여성 의류/남성 의류/생활 잡화 …” 같은 카테고리 형태[^15]
- 쿠팡 같은 앱/웹에서 상단 메뉴 + 하단 상품 리스트로 이어지는 “아주 기본적인” 구성이라고 비유한다.[^16]
여기서 강사는 중요한 주장을 한다:
[h 기본 레이아웃만 정확히 알면 어떤 레이아웃도 잡아낼 수 있다] 라고 하며, 오늘은 그 레이아웃을 직접 구성해본다고 한다.[^17]
3.3 Flutter 프로젝트 생성과 “빈 화면에서 시작” 준비[^18]
강사는 IDE에서 새 Flutter 프로젝트를 만드는 흐름을 짚는다.[^18]
File→New→New Flutter Project[^18]- SDK 경로 지정[^18]
- 프로젝트명 입력(중급 파트는 “03-01, 03-02…”처럼 03 시리즈로 진행하겠다고 언급)[^19]
이후 기존 코드를 정리(“싹 지워 보고”)하고, 빈 상태에서 앱이 어떻게 시작되는지부터 다시 구성한다.[^20]
3.4 Dart/Flutter 진입점: void main()과 runApp()[^21]
강사는 앱 실행을 위해 가장 먼저 필요한 것은 **main() 함수(진입점)**라고 설명한다.[^21]
void main()을 만들고, 그 안에서 print()를 실행해도 화면에 UI는 없고 콘솔 출력만 생긴다고 말하며, “여기서부터 실행이 된다”는 점을 확인시킨다.[^22]
이어서 실제 “모바일 화면에 그리는 작업”을 하려면 runApp()이 필요하다고 말한다.[^23]
runApp(MyApp())형태로, 특정 클래스/인스턴스(위젯)를 앱으로 실행한다는 설명이다.[^23]- 이때
MyApp이 정의되어 있지 않으면 오류가 나므로, 다음 단계에서MyApp을 만든다고 연결한다.[^23]
3.5 Flutter 앱의 계층 구조 그림 설명: StatelessWidget → MaterialApp/CupertinoApp → 위젯들 → Scaffold[^24]
강사는 그림으로 “앱이 몇 겹으로 쌓이는지”를 설명한다.[^24]
- 화면(모바일) 안에 가장 먼저 StatelessWidget(하나의 앱 역할)이 들어가고[^24]
- 그 안에 MaterialApp 또는 CupertinoApp이 들어간다고 말한다.[^25]
Material과 Cupertino는 각각:
- MaterialApp: 안드로이드 스타일[^25]
- CupertinoApp: iOS(애플) 스타일[^25]
둘의 기본 디자인 차이를 예로 든다.[^26]
- 스위치/체크박스 UI 차이[^26]
- 리스트 선택 UI가 iOS는 “휠(회전 휠)”처럼 보이는 형태가 있다고 언급[^26]
즉, 앱의 최상단에서 스타일/테마 같은 것을 물고 들어오며, 이번에는 먼저 Material을 사용하겠다고 한다.[^27]
그리고 일반 앱에서 흔히 보이는 표준 영역(관례)을 설명한다.[^28]
- 상단 제목(타이틀)[^28]
- 왼쪽 햄버거 메뉴[^28]
- 오른쪽 검색/설정 등 아이콘들[^28]
- 본문 데이터 영역[^28]
- 하단 내비게이션 버튼이나 플로팅 버튼(예: 당근마켓 새 상품 등록 등)[^29]
이런 “표준화된 페이지 스타일”을 매번 직접 만들기 귀찮으니 Flutter가 제공하는 것이 Scaffold이며, Scaffold 안에 UI 컴포넌트(위젯)를 넣어 화면을 만든다고 설명한다.[^30]
3.6 import와 StatelessWidget(MyApp) 생성, MaterialApp으로 감싸기[^31]
강사는 MyApp을 만들기 위해 IDE 템플릿(예: stl)을 사용해 StatelessWidget 클래스를 생성한다.[^31]
이어서 runApp 등 Flutter 요소를 쓰려면 라이브러리를 가져와야 하므로 다음을 import한다고 한다.[^32]
import 'package:flutter/material.dart';[^32]
그리고 “앱 실행 시 어떤 테마로 감싸주느냐”로 MaterialApp을 소개한다.[^33]
MaterialApp(home: MyHomePage())처럼home에 시작 페이지 위젯(클래스 인스턴스)을 지정한다.[^33]MyHomePage도 별도의 StatelessWidget로 생성해서 실제 페이지 구성을 하게 만든다.[^34]
강사는 여기까지의 계층을 다시 말로 정리한다.[^35]
main()에서 시작[^35]runApp()이MyApp실행[^35]MyApp안에MaterialApp[^35]home에MyHomePage[^35]MyHomePage에서 “제대로 된 페이지”를 구성[^35]
3.7 Scaffold의 appBar/body, 타입 확인(CTRL로 정의 이동) 학습법[^36]
MyHomePage에서 표준 페이지 폼을 잡아주는 것이 Scaffold라고 말한다.[^36]
Scaffold가 있으면 배경이 하얗게 보이고, 없으면(또는 다르게 구성하면) 검정 배경처럼 보일 수 있다고 언급한다.[^37]
Scaffold는 크게:
appBarbody
두 영역으로 나뉜다고 설명한다.[^38]
그리고 초보자가 흔히 헷갈리는 “여기에 뭐가 들어가야 하는지(타입)”를 확인하는 방법을 알려준다.[^39]
Ctrl누르고 해당 속성/클래스로 들어가면(Scaffold정의 파일 이동) 생성자 파라미터 타입을 볼 수 있다.[^39]- 예:
appBar도 위젯,body도 위젯이 들어온다는 것을 정의에서 확인할 수 있다.[^39]
앱바에는 AppBar 위젯을 넣고, AppBar의 title도 위젯이므로 Text() 같은 컴포넌트를 넣을 수 있다고 설명한다.[^40]
실제로:
- appBar title에
Text('제목') - body에
Text('바디')
를 넣어 화면에 표시되는 것을 확인한다.[^41]
또한 코드 정렬(재정렬)로 Ctrl + Shift + F를 언급한다.[^42]
3.8 “복잡한 UI도 결국 Column/Row로 썬다”: 90% 커버 원리 + Stack 10%[^43]
강사는 이제 “여러 위젯을 배열하는 방법”으로 넘어가며, 대부분의 UI는 큰 틀에서 두 가지(세로/가로)로 나뉜다고 말한다.[^43]
예시로 지마켓 같은 복잡한 화면을 Flutter로 만든다고 가정하고, 분석법을 그림으로 보여준다.[^44]
핵심은 “썰기”다.[^45]
- 먼저 화면을 수직(세로)으로 여러 덩어리로 자른다.[^45]
- 그 다음 각 덩어리를 수평(가로)으로 다시 자른다.[^46]
- 필요하면 그 안에서 다시 세로/가로로 반복해서 자르고, 각 조각을 컴포넌트(component) 단위로 만든다.[^47]
예를 들어:
- 메뉴 영역은 아이콘/이미지/텍스트 묶음을 하나의 컴포넌트로 만든다[^47]
- 회전 배너처럼 바뀌는 영역은 타이머/애니메이션으로 바꿔치기하면 된다고 언급한다.[^48]
그리고 이 방식으로 “못 만들게 없다”고 하며, 이렇게 세로/가로로만 썰어서 해결되는 영역이 약 **90%**라고 말한다.[^49]
나머지 10%는 예외가 있는데, 그 대표가 Stack이라고 설명한다.[^50]
- Stack은 절대좌표처럼 “밑에 이미지를 깔고 위에 오른쪽 상단/하단 등에 요소를 붙이는” 방식[^50]
- 포토샵의 레이어처럼 층을 쌓아 겹치기를 구현한다고 비유한다.[^51]
3.9 쇼핑몰 상단 메뉴: Column(세로) + Row(가로) + Text 배치[^52]
강사는 원래 목표(쇼핑몰 레이아웃)로 돌아와, 먼저 쇼핑몰 제목을 앱바에 적을 수 있음을 보여주며(예: “단화와 쇼핑몰”), 본격적으로는 body 안의 배치를 생각하자고 한다.[^52]
body 내부 구조는:
- 위: 메뉴 줄
- 아래: 사진 2개
라고 다시 확인한다.[^53]
여러 위젯을 수직으로 배열할 때는 Column을 사용한다.[^54]
Column(children: [ ... ])형태로 여러 위젯을 넣는다.[^54]- 예시로 Text들을 여러 개 넣으면 세로로 쌓이는 것을 확인한다.[^55]
메뉴를 가로로 나열하려면 Row를 사용한다.[^56]
IDE 팁: “감싸기(wrap with)” 단축키(Alt+Enter)
강사는 텍스트 위젯에 커서를 두고 Alt + Enter를 누르면 “현재 위젯을 Row로 감싸기” 같은 자동 래핑 메뉴가 뜨고, 선택하면 Row(children) 구조로 감싸진다고 시연한다.[^57]
다시 같은 방식으로 제거도 가능하다고 말한다.[^57]
Row 안에 텍스트를 4개 배치하고, 이를 메뉴(여성의류/남성의류/… 등)로 바꿔 넣는다.[^58]
3.10 메뉴 간격 주기 1: SizedBox(width)로 “고정 간격” 만들기[^59]
Row의 메뉴가 “따닥따닥” 붙어 있으니 간격을 띄우는 방법을 소개한다.[^59]
첫 번째 방법은 SizedBox다.[^60]
SizedBox(width: 10)을 텍스트 사이에 끼워 넣으면 그 폭만큼 간격이 생긴다.[^60]- 같은 방식으로 사이사이에 복사해 넣는다.[^60]
다만 강사는 이 방식이 “애매”할 수 있다고 말한다.[^61]
- 왜냐하면 화면 폭/텍스트 길이에 따라 간격이 과하거나 균일해 보이지 않을 수 있기 때문이다.[^61]
3.11 메뉴 간격 주기 2: Spacer로 “가변 균등 분배(스프링)”[^62]
두 번째 방법으로 Spacer를 소개한다.[^62]
- Spacer는 스프링처럼 동작해서, Row 안의 남는 공간을 동일 비율로 나눠 간격을 만든다고 설명한다.[^62]
- 텍스트들 사이에 Spacer를 넣으면 간격이 자동으로 균등해진다.[^63]
또한 앞/뒤에도 Spacer를 넣어 좌우 여백을 균등하게 줄 수 있지만, “사실 이렇게는 잘 안 한다”고 말한다.[^64]
Spacer 사용 시 주의점도 언급한다.[^65]
- 글자가 길어져서 더 이상 가변 영역이 없으면, 전체가 줄어들다가 결국 오버플로(넘침)가 발생할 수 있다.[^65]
- 오버플로는 “영역을 넘어갔다, 대책을 세워라”는 의미로, 나중에 스크롤 등으로 처리할 수 있다고 말한다.[^66]
3.12 바깥 여백(좌우 여백)은 Padding으로 처리[^67]
Row 앞뒤에 Spacer로 여백을 만들기보다, 보통은 바깥쪽에 Padding을 준다고 설명한다.[^67]
적용 방식:
- Column(또는 전체)을
Padding으로 감싼다(Alt+Enter로 wrap).[^68] - 그러면 기본적으로 상하좌우 여백이 들어가고, 더 여유 있게 하려면 값(예: 15, 20)을 키운다.[^69]
강사는 “패딩”을 일상 용어(충전재)로 비유하며, 바깥에 뭔가를 채워 여유를 준다고 설명한다.[^70]
3.13 상품 이미지 배치: 내장 이미지 vs 네트워크 이미지[^71]
이제 상품 영역을 위해 “이미지”를 넣는 방법을 설명한다.[^71]
강사는 이미지를 크게 두 종류로 나눈다.[^72]
- 내장 이미지(Asset 이미지): 앱 내부에 이미지를 포함해서 배포[^72]
- 네트워크 이미지: 서버에 있는 이미지를 URL로 로딩[^72]
쇼핑몰 상품 이미지는 원래 계속 바뀌니 서버에서 가져오는 게 일반적이라고 말하지만, 지금은 서버가 없으니 학습을 위해 내장 이미지로 테스트하자고 한다.[^73]
내장 이미지의 장단점도 언급한다.[^74]
- 앱 용량은 커질 수 있음[^74]
- 대신 네트워크 통신을 안 하니 통신료 부담이 줄고, 로딩이 빠르다[^74]
3.14 assets(리소스) 폴더 구성: images, fonts, svg 등 관례[^75]
내장 이미지를 배포하려면 리소스(assets)를 프로젝트에 넣어야 하며, 이를 리소스 자원이라고 부른다.[^75]
강사는 프로젝트 디렉토리들을 훑으며 각각의 의미를 간단히 짚는다.[^76]
android/: 안드로이드 배포 관련[^76]ios/: iOS 배포 관련[^76]lib/: 실제 Dart 코드 위치[^76]- 그 외 linux/mac/web/windows 등 멀티플랫폼 대상 디렉토리 언급[^76]
리소스 폴더는 보통 프로젝트 루트에 assets(강의에서는 “ass”로 발음/표기되는 부분이 있으나 맥락상 assets) 폴더를 만들고 그 안에 목적별 폴더를 둔다고 한다.[^77]
assets/images/: 이미지[^77]assets/fonts/: 폰트[^78]- 필요하면
assets/videos/,assets/sounds/등도 가능[^79] - 디렉토리 이름은 임의로 만들어도 되며 “큰 의미는 없고 관상(관리 목적)”이라고 말한다.[^80]
SVG에 대해서도 실무 팁을 준다.[^81]
- SVG는 Flutter가 기본적으로 표시 못한다[^81]
- 그래서 SVG 렌더링을 위한 서드파티 라이브러리가 필요하며, 보통
assets/svg/로 별도 관리한다고 한다.[^81]
또한 폴더명 변경은 우클릭 → rename으로 가능하다고 안내한다.[^82]
3.15 샘플 이미지 추가 후, pubspec.yaml에 assets 등록(들여쓰기 주의) + pub get[^83]
강사는 샘플 이미지 파일 여러 개(S1, S2, w01, w02 등)를 assets/images/에 넣는다.[^83]
하지만 단순히 폴더에 넣는 것만으로는 앱에서 자동 인식되지 않으며, 컴파일 이전에 리소스 정보를 알려줘야 한다고 강조한다.[^84]
그 설정 위치가 pubspec.yaml이며, 다음 절차를 설명한다.[^85]
flutter:섹션 아래 주석 처리된assets:항목을 찾아 주석을 해제한다.[^85]- **들여쓰기(2칸 단위)**를 반드시 지켜야 하며, 틀리면 에러가 난다고 경고한다.[^86]
- 과거에는 파일을 하나씩 나열했지만, 이미지가 수십~수백 개가 되면 관리가 어려우므로 이제는 디렉토리를 통째로 등록하는 방식을 쓴다고 한다.[^87]
- 예:
- assets/images/처럼 폴더 경로만 등록[^87]
- 예:
- 설정 후 반드시 pub get(의존성/리소스 갱신)을 실행한다.[^88]
- 실행 후 “exit code 0” 같은 정상 종료를 확인하라고 한다.[^88]
여기서 강사는 pub get이 단지 에셋뿐 아니라, 이후 외부 라이브러리들을 붙일 때도 매우 중요하다고 연결한다.[^89]
- 라이브러리 버전 호환성 충돌 사례를 설명한다.[^90]
- A가 B를 참조하고, C도 B를 참조하는데 A가 원하는 B 버전과 C가 원하는 B 버전이 다르면 에러가 난다[^90]
- 그러면 에러 메시지에 “어떤 버전을 쓰라”가 나오고, 충돌이 없어질 때까지 버전을 조정(다운/업)해야 한다[^91]
즉 pub get은 “라이브러리를 재구축”하는 과정이라고 설명한다.[^92]
3.16 Image.asset()로 이미지 표시, 그리고 오버플로 문제 확인[^93]
이제 코드로 돌아와 Column 아래에 이미지를 넣는다.[^93]
Image.asset('assets/images/w01.jpg')형태로 경로를 지정한다.[^94]- 이어서 w02도 추가한다.[^94]
이미지 2개가 표시되지만, 화면을 넘어가면서 **오버플로(overflow)**가 발생한다는 것을 보여준다.[^95]
3.17 Image의 fit: cover / contain / fill 개념을 그림으로 설명[^96]
강사는 이미지가 영역에 어떻게 맞춰질지 결정하는 속성으로 fit을 소개한다.[^96]
BoxFit.fillBoxFit.containBoxFit.cover
세 가지를 주로 본다고 말한다.[^97]
코드 적용만으로 차이가 즉시 잘 안 보이자, 그림으로 의미를 설명한다.[^98]
- cover: 핸드폰(영역)을 “꽉 채우도록” 이미지를 덮는다. 이미지 비율 때문에 좌우/상하가 잘릴 수 있다.[^99]
- contain: 이미지 전체가 영역 안에 “포함”되도록 축소/조정한다. 대신 남는 공간(레터박스)이 생길 수 있다.[^100]
- fill: 비율이 찌그러져도 영역을 채우도록 늘린다. 그래서 잘 안 쓴다고 말한다.[^101]
실무적으로는:
- contain은 남는 공간이 보기 싫을 수 있어 잘 안 쓰고[^102]
- cover를 많이 쓴다고 결론낸다.[^102]
3.18 오버플로 해결: Expanded로 남은 공간을 비율로 나눠 쓰기 + flex 비율 조정[^103]
오버플로를 “스크롤로 해결할 수도 있지만”, 여기서는 화면 안에서 이미지가 차지할 공간을 나누는 방식으로 접근한다.[^103]
강사는 이미지를 Expanded로 감싸면 “안에서 최대한만 쓰라(주어진 공간을 채우라)”는 의미가 되어 오버플로를 피할 수 있다고 말한다.[^104]
- Expanded를 하나만 쓰면 그 위젯이 가능한 영역을 차지하고, 다른 요소는 남은 공간을 쓰는 식으로 보일 수 있다고 설명한다.[^105]
- 이미지 두 개 모두를 Expanded로 감싸면 1:1로 공간을 나눠 쓴다.[^106]
또한 Expanded의 flex로 비율을 조정할 수 있다고 소개한다.[^107]
- 예: 전체를 3으로 보고, 하나는 1, 다른 하나는 2처럼 나눌 수 있는 개념을 설명한다.[^107]
3.19 요소 사이 세로 간격: SizedBox(height)로 간단히 처리[^108]
이미지와 메뉴 사이, 이미지들 사이가 너무 붙어 보일 때는 **SizedBox(height: 10)**을 넣어 간격을 준다고 시연한다.[^108]
height를 주면 세로 간격이 생기고, UI가 더 보기 좋아진다고 한다.[^109]
3.20 SafeArea: iOS 상단 상태바(시계/배터리) 영역 침범 문제 방지[^110]
강사는 타이틀바(appBar)를 없애거나 구성에 따라, 특히 iPhone에서 화면이 상태바 영역(시계까지)으로 먹어 들어가 글자가 겹치는 문제가 생길 수 있다고 말한다.[^110]
이를 자동으로 감지해서 안전 영역을 확보해 주는 것이 SafeArea이며, 보통 body 쪽에서 감싸거나(혹은 Scaffold 바깥에서) 사용한다고 설명한다.[^111]
- SafeArea를 적용하면 상태바 영역을 피해 배치되도록 보호해 준다고 정리한다.[^112]
3.21 이번 시간에서 다룬 위젯/개념 총정리(강사의 정리 파트 그대로 재구성)[^113]
강사는 여기까지 배운 내용을 한 번에 다시 나열하며 의미를 붙여 정리한다.[^113]
- MaterialApp: 앱 테마/스타일의 큰 틀(이번엔 Material) 구성[^113]
- Scaffold: 표준 페이지 구조(배경, appBar/body 등) 제공[^113]
- Column: 세로 배치[^113]
- Row: 가로 배치[^113]
- Text: 글자 표시[^113]
- SafeArea: 상단 상태표시줄(시계/배터리 등) 영역 침범 방지, 안전 영역 확보[^114]
- Image(내장 이미지): assets 폴더 + pubspec.yaml 등록 후 표시[^115]
- Spacer: 남는 공간을 균등 분배(스프링처럼), 가변 간격[^116]
- Expanded: 남는 공간을 확장하여 채우고, 여러 Expanded가 있으면 비율 분배(flex) 가능[^117]
- Padding: 안쪽 여백(테두리와 내용 사이 간격) 부여[^118]
- SizedBox: 고정 크기 공간(폭/높이)으로 간격 만들기[^119]
그리고 “작동은 안 하고 화면 배치만 해본 것”이며, Column/Row만으로도 대부분 UI가 가능(90%)하다는 주장을 다시 확인한다.[^120]
3.22 Row/Column 정렬 옵션: MainAxisAlignment, CrossAxisAlignment 실전 상황 예시[^121]
강사는 “옵션이 더 있다”며 Row 정렬을 추가로 시연한다.[^121]
3.22.1 Row의 메인 축 정렬(MainAxisAlignment): start/center/end[^122]
Row에서 좌우 방향이 메인 축이며, 메인 축에서 시작 위치를 정할 수 있다고 한다.[^122]
MainAxisAlignment.center로 가운데 정렬[^123]- 기본은 start(왼쪽부터)[^123]
- 오른쪽 정렬은 end로 설정[^124]
강사는 또한 “사이즈박스로 간격을 억지로 띄우는” 방식도 함께 보여주며, 간격을 10, 20으로 늘리면 살짝씩 띄워진다고 설명한다.[^125]
3.22.2 글자 크기가 달라질 때의 수직 정렬 문제 → CrossAxisAlignment로 해결[^126]
메뉴에서 “선택된 항목”을 강조하려고 특정 글자의 폰트만 크게 키운 상황을 예로 든다.[^126]
TextStyle(fontSize: 20)처럼 폰트 크기를 키운다.[^126]
그런데 글자 크기가 달라지면 Row 안에서 가운데 정렬처럼 보여 “보기 안 좋게” 될 수 있다고 한다.[^127] 이때 Row는 위아래 정렬(교차 축 정렬)도 할 수 있는데, 그것이 CrossAxisAlignment라고 설명한다.[^128]
- start(위쪽), center(가운데), end(아래쪽) 정렬 개념을 말하며[^128]
- 예시로 end를 주면 큰 글자/작은 글자가 아래쪽 기준선으로 정렬되는 효과를 보여준다.[^129]
또한 “너무 아래로 내려온 것 같으면 약간씩 조정이 필요”하다고 언급하며, 실제 UI에서는 선택 상태에 따라 스타일(크기/색상)을 바꿔 사용자에게 선택됨을 알려줄 수 있다고 말한다.[^130]
이 정렬 개념은 Column에도 동일하게 존재하되, Column은 메인 축이 위아래이고 크로스 축이 좌우라는 점만 다르다고 덧붙인다.[^131]
3.23 const의 의미와 성능/가변값 제약: 상수 위젯 최적화 vs 변수 사용[^132]
강사는 마지막으로 const를 “참고적으로” 설명한다.[^132]
const는 상수, 즉 값이 고정되어 안 바뀌는 수/객체라는 뜻이라고 정의한다.[^132]
Flutter 위젯 트리에서 숫자 리터럴로 고정된 값들(예: SizedBox(width: 20))은 const로 만들 수 있으며, 이렇게 하면 실행 시점에 매번 객체를 만드는 것이 아니라 미리 고정된 것으로 취급되어 “조금이라도 속도가 빨라진다”고 설명한다.[^133]
const를 넣든 안 넣든 동작은 같지만,const가 권장 사항이라고 말한다.[^134]
반대로, 패딩 값 같은 것을 변수로 두고(예: double pad = 10;) 나중에 바꾸고 싶다면 const를 쓰면 안 된다고 설명한다.[^135]
const는 가변 값을 받을 수 없어서 에러가 난다[^135]- 또한 타입이 안 맞아 에러가 났던 상황을 언급하며(예: int vs double), 결국
double로 맞춰야 한다고 정리한다.[^136]
마지막으로, 홈 페이지가 const로 만들어진 것은 “한 번 만들어지면 안 바뀌는” 의미로 이해할 수 있고, 값이 바뀔 여지가 있으면 const를 제거해야 한다고 결론낸다.[^137]
3.24 다음 시간 예고: 폰트 내장 + 외부 폰트 가져와 적용 + 화면 구성 계속[^138]
강사는 다음 시간에는:
- 폰트 내장하는 방법
- 외부에서 폰트 가져와서 불러 쓰는 방법
- 추가적인 화면 배치 방법
을 다루겠다고 예고하며 첫 시간을 마무리한다.[^138]
4. 핵심 통찰[^139]
- [h Flutter 화면 구성은 “위젯 단위 암기”보다 “위젯 조합 순서(뼈대→표준폼→배치→간격→리소스)”를 몸으로 익히는 것이 핵심이다.] 강의는 main/runApp에서 시작해 MaterialApp/Scaffold로 골격을 세우고, Column/Row로 썰어 넣는 방식으로 반복 훈련을 시킨다.[^139]
- [h 복잡한 UI는 구현 전에 ‘세로로 자르고 → 가로로 자르고 → 반복’하는 분석이 먼저다.] 이 사고법이 되면 “못 만들게 없다(90%)”는 주장을, 지마켓 같은 화면을 썰어 컴포넌트화하는 예로 설득한다.[^49]
- [h 간격은 세 종류가 성격이 다르다: SizedBox(고정), Spacer(가변 균등), Padding(외곽 여백).] 메뉴 배치에서 고정 간격의 애매함 → Spacer 균등 분배 → 바깥 여백은 Padding으로 처리라는 흐름으로 “언제 무엇을 쓰는지”를 구분한다.[^60]
- [h 이미지 리소스는 ‘폴더에 넣기’로 끝이 아니라 pubspec.yaml 등록과 pub get까지가 한 세트다.] 들여쓰기 오류와 exit code 확인을 강조하면서, 이후 라이브러리 의존성 충돌까지 연결해 “설정 파일 관리의 중요성”을 미리 심어준다.[^86]
- [m iOS 상태바 침범은 SafeArea로 선제 대응해야 한다.] Android에서는 티가 덜 나도 iPhone에서 문제가 터질 수 있다는 실무형 포인트를 짚는다.[^110]
- [m const는 ‘안 바뀌는 위젯’에 대한 선언이며, 성능에 작은 이점을 주지만 가변값(변수)이 들어오면 바로 포기해야 한다.] 마지막에 const/변수/타입(double)까지 엮어 “왜 에러가 나는지”를 실제 상황처럼 설명한다.[^135]
실행 시사점(강의 흐름 기반)
- 앱 화면을 만들 때마다:
main → runApp → MaterialApp → Scaffold(appBar/body)골격을 먼저 고정한 뒤, body를 Column/Row로 쪼개는 습관을 들인다.[^35] - 레이아웃이 막히면: “먼저 세로로 썰고, 다음 가로로 썰기”를 종이에 그려 컴포넌트 단위로 분해한다.[^45]
- 에셋/라이브러리 추가 시: pubspec.yaml 수정 후
pub get과 콘솔 정상 종료를 반드시 확인한다.[^88]
5. 헷갈리는 용어 정리[^140]
MaterialApp: 안드로이드(Material) 스타일 테마를 적용하는 앱 루트 위젯(대안으로 iOS 스타일의 CupertinoApp이 있음).[^25]
CupertinoApp: iOS(Cupertino) 스타일 위젯/테마를 적용하는 앱 루트 위젯.[^25]
Scaffold: 앱 화면의 표준 구조(appBar/body/하단 영역 등)를 제공하는 레이아웃 뼈대 위젯.[^30]
Column: children 위젯들을 “세로(수직)”로 배치하는 레이아웃 위젯.[^54]
Row: children 위젯들을 “가로(수평)”로 배치하는 레이아웃 위젯.[^56]
Spacer: Row/Column의 남는 공간을 유연하게 차지해 간격을 균등 분배하는 위젯(스프링 비유).[^62]
SizedBox: 고정 폭/높이를 갖는 박스. 간격(여백) 만들 때 자주 사용.[^60]
Padding: 자식 위젯 바깥에 여백을 주는 위젯.[^67]
Expanded: Row/Column에서 남는 공간을 확장해 차지하는 위젯. 여러 개면 flex 비율로 분배.[^106]
SafeArea: 상태바/노치 등 시스템 UI 영역을 피해 안전한 영역에 배치되게 해주는 위젯.[^111]
assets(리소스): 앱에 포함해 배포할 이미지/폰트/사운드 등 파일 자원. pubspec.yaml에 등록 필요.[^84]
pubspec.yaml: Flutter 프로젝트 설정/의존성/에셋 등록 파일. 들여쓰기 등 문법이 중요.[^86]
pub get: 의존성과 에셋 설정을 반영해 프로젝트를 재구축/동기화하는 작업.[^92]
BoxFit.cover/contain/fill: 이미지가 주어진 영역에 맞춰지는 방식(덮기/포함/늘리기).[^99]
MainAxisAlignment: Row/Column의 “메인 축” 방향 정렬 옵션(가로/세로 방향의 정렬).[^122]
CrossAxisAlignment: Row/Column의 “교차 축” 방향 정렬 옵션(메인 축에 직교하는 방향 정렬).[^128]
const: 컴파일 타임 상수. 값이 변하지 않는 위젯/객체를 상수로 만들어 재사용/최적화에 도움.[^133]
참고(콘텐츠 정보)[^141]
- 제목: [Flutter] 모바일 앱 개발자를 위한 Flutter(플러터) 제대로 배우기 Part.3 중급 1[^141]
- 채널: 아이티동스쿨[^141]
- 길이: 60분 58초[^141]
- 링크: https://www.youtube.com/watch?v=NpbdmmZSmFk[^141]
각주[^1]
[^1]: @[00:04] “플러터 이제 중급자 과정을…” [^2]: @[00:47] “서드 파트 라이브러리를 많이… 상태 관리… 넘버 포맷…” [^3]: @[21:43] “수직으로 먼저 다 썰어…” 및 @[24:57] “한 90%… 나머지 10%…” [^4]: @[00:33] “중급 과정에서는 위젯들을 묶어서… 화면 구성…” 및 @[02:37] “오늘은… 화면 구성… 쇼핑몰…” [^5]: @[05:38]~@[16:10] main/runApp/MyApp/MaterialApp/home/MyHomePage 흐름 설명 [^6]: @[26:04] Column, @[27:10] Row, @[29:01] SizedBox, @[29:38] Spacer, @[31:14] Padding [^7]: @[34:08]~@[42:12] assets 폴더/ pubspec.yaml 등록/ Image.asset 경로 [^8]: @[02:23] “화면 단위로 단위 기능들로 배워…” [^9]: @[00:18]~@[00:33] 초급(위젯 낱개) vs 중급(화면 구성) [^10]: @[00:47]~@[01:20] 서드파티/상태관리/넘버포맷 언급 [^11]: @[01:29]~@[02:00] Firebase notification, 외부 라이브러리 지원 [^12]: @[02:00] “업무에서 어떻게 사용… 프로젝트와 믹스…” [^13]: @[02:37] 오늘 다룰 위젯 나열 [^14]: @[03:13]~@[03:50] 쇼핑몰 화면 스케치(메뉴+사진2장) [^15]: @[03:50]~@[04:04] 메뉴 예시(여성/남성/생활잡화) [^16]: @[04:13]~@[04:20] 쿠팡 비유(상단 메뉴, 하단 상품) [^17]: @[04:26] “기본 레이아웃만… 어떤 레이아웃 다 잡아낼 수…” [^18]: @[04:47] 프로젝트 생성 메뉴 경로 [^19]: @[05:19]~@[05:31] 프로젝트명 03 시리즈 언급 [^20]: @[05:31]~@[05:38] “싹 지워 보고… 빈 화면…” [^21]: @[05:38]~@[06:03] “제일 먼저… main…” [^22]: @[06:11]~@[07:11] print 실행, 진입점 설명 [^23]: @[07:47]~@[08:12] runApp, MyApp 실행 개념 [^24]: @[08:27]~@[09:50] 앱 계층 그림, StatelessWidget → MaterialApp [^25]: @[09:50]~@[10:06] MaterialApp vs CupertinoApp [^26]: @[10:14]~@[10:35] 스위치/체크박스/휠 UI 차이 예시 [^27]: @[10:44]~@[10:55] “테마… 우리는 머터리얼…” [^28]: @[11:15]~@[11:53] 앱 표준 구조(타이틀, 햄버거, 아이콘, 바디, 네비게이션, 플로팅) [^29]: @[11:53]~@[12:09] 당근마켓 예시(등록/검색/나의정보 등) [^30]: @[12:17]~@[12:45] Scaffold로 표준 페이지 스타일 제공 [^31]: @[13:16]~@[13:30] stl로 StatelessWidget 생성 [^32]: @[13:43]~@[14:06] import 'package:flutter/material.dart' [^33]: @[15:00]~@[15:17] MaterialApp으로 감싸고 home 지정 [^34]: @[15:24]~@[15:55] MyHomePage 클래스 생성(StatelessWidget) [^35]: @[16:01]~@[16:19] main → MyApp → MaterialApp → MyHomePage 구조 재확인 [^36]: @[16:19]~@[16:46] Scaffold가 페이지 구성의 표준 폼 [^37]: @[16:28]~@[16:32] Scaffold 없을 때 배경 언급 [^38]: @[16:36]~@[16:46] appBar/body 프로퍼티 [^39]: @[16:51]~@[17:49] Ctrl로 정의 이동, 타입 확인 [^40]: @[18:06]~@[18:38] appBar=AppBar, title은 Widget [^41]: @[18:52]~@[19:18] 제목/바디 Text 표시 확인 [^42]: @[19:24]~@[19:30] 코드 재정렬 단축키 [^43]: @[20:08]~@[20:22] 배열 방법은 크게 두 가지(90% 커버) [^44]: @[21:07]~@[21:33] 지마켓 화면 예시로 분석 시작 [^45]: @[21:43]~@[22:06] “수직으로 먼저 다 썬다” [^46]: @[22:06]~@[22:22] “가로로 다시 썬다” [^47]: @[22:27]~@[23:22] 세로/가로 반복, 컴포넌트화 설명 [^48]: @[23:22]~@[23:28] 타이머/애니메이션으로 배너 변경 언급 [^49]: @[23:52]~@[24:57] “못 만들게 없다… 90%…” [^50]: @[24:02]~@[24:26] Stack(절대좌표, 오른쪽 상단/하단 배치) [^51]: @[24:32]~@[24:40] 포토샵 레이어 비유 [^52]: @[25:22]~@[25:38] 쇼핑몰 제목, body 기준으로 생각 [^53]: @[25:42]~@[25:50] 메뉴 줄 + 사진 두 개 요구 재확인 [^54]: @[25:57]~@[26:15] Column(children) 소개 [^55]: @[26:31]~@[26:34] Text가 수직으로 배치됨 확인 [^56]: @[27:10]~@[27:18] Row 소개 [^57]: @[27:18]~@[27:53] Alt+Enter로 Row로 감싸기/제거 [^58]: @[28:24]~@[28:32] 메뉴 텍스트로 변경(여성의류/남성의류 등) [^59]: @[28:40]~@[29:03] “사이를 띄우는 방법” [^60]: @[29:01]~@[29:23] SizedBox(width)로 간격 [^61]: @[29:23]~@[29:30] SizedBox 방식이 애매할 수 있음 [^62]: @[29:38]~@[29:54] Spacer=스프링 비유 [^63]: @[29:53]~@[30:02] 균등 분배 효과 설명 [^64]: @[30:06]~@[30:24] 앞뒤 Spacer는 보통 잘 안 함 [^65]: @[30:24]~@[30:52] 글자 많아지면 줄어들다 오버플로 [^66]: @[30:52]~@[31:01] 오버플로 의미, 스크롤로 해결 가능 [^67]: @[31:11]~@[31:20] 앞뒤 여백은 Padding으로 [^68]: @[31:32]~@[31:41] Column을 Padding으로 감싸기 [^69]: @[31:45]~@[31:54] Padding 값 조정(15/20) [^70]: @[31:20]~@[31:32] 패딩 비유(채워 넣는 것) [^71]: @[32:13]~@[32:34] 상품 이미지 배치로 전환 [^72]: @[33:03]~@[33:19] 내장 이미지 vs 네트워크 이미지 [^73]: @[33:29]~@[33:45] 서버 없으니 내장 이미지로 테스트 [^74]: @[33:49]~@[33:57] 내장 이미지 장단점(용량 vs 통신/로딩) [^75]: @[34:03]~@[34:13] 리소스(자원) 설명 [^76]: @[34:21]~@[34:49] android/ios/lib/웹/윈도우 등 디렉토리 의미 [^77]: @[35:04]~@[35:34] assets 폴더 및 images 폴더 생성 [^78]: @[35:34]~@[36:13] fonts 폴더 및 폰트 내장 필요성 [^79]: @[36:17]~@[36:26] 영상/사운드 등도 가능 [^80]: @[36:27]~@[36:35] 디렉토리 이름은 임의, 관리 목적 [^81]: @[36:35]~@[37:06] svg는 기본 표시 불가, 외부 라이브러리 필요 [^82]: @[37:11]~@[37:25] rename 방법 [^83]: @[37:28]~@[37:55] 샘플 이미지 파일 추가 [^84]: @[38:08]~@[38:14] 자동 인식 안 됨, 컴파일 전 알려야 함 [^85]: @[38:17]~@[38:46] pubspec.yaml assets 주석 해제 [^86]: @[38:49]~@[39:02] 들여쓰기(2칸) 틀리면 에러 [^87]: @[39:19]~@[39:47] 파일 나열 대신 폴더 등록 방식 [^88]: @[39:50]~@[40:01] pub get, exit code 0 확인 [^89]: @[40:09]~@[40:16] 나중에 외부 라이브러리 계속 추가됨 [^90]: @[40:16]~@[40:30] 라이브러리 버전 충돌 설명(A/B/C) [^91]: @[40:43]~@[40:58] 에러 메시지 버전에 맞춰 조정 [^92]: @[41:06] “라이브러리를 재구축… get” [^93]: @[41:39]~@[41:48] Column 아래 이미지 두 개 넣기 [^94]: @[41:53]~@[42:26] Image.asset 경로(w01/w02) [^95]: @[42:30]~@[42:45] 이미지 오버플로 발생 [^96]: @[43:02]~@[43:19] fit 속성 소개 [^97]: @[43:36]~@[43:49] fill/contain/cover [^98]: @[44:20]~@[44:31] 차이 설명 위해 그림으로 전환 [^99]: @[44:44]~@[45:07] cover=영역 덮기, 일부 잘림 [^100]: @[45:11]~@[45:31] contain=전체 포함, 남는 공간 가능 [^101]: @[45:53]~@[46:13] fill=비율 깨짐, 잘 안 씀 [^102]: @[46:19]~@[46:45] contain 단점, cover를 많이 씀 [^103]: @[47:12]~@[47:22] 오버플로 해결 방향(스크롤도 가능) [^104]: @[47:24]~@[47:40] Expanded로 영역 안에서 최대 사용 [^105]: @[47:42]~@[47:51] Expanded 하나 적용 시 동작 설명 [^106]: @[47:59]~@[48:09] 두 이미지 모두 Expanded로 1:1 분할 [^107]: @[48:55]~@[49:07] flex로 비율 분배(예: 1:2) [^108]: @[49:23]~@[49:44] SizedBox(height)로 간격 [^109]: @[49:50]~@[50:04] 공백이 들어간 화면 설명 [^110]: @[50:13]~@[50:40] iPhone 상태바 침범 문제 [^111]: @[50:46]~@[51:12] SafeArea 소개, 적용 위치 언급 [^112]: @[51:42]~@[51:54] SafeArea가 글자 겹침 보호 [^113]: @[52:05]~@[53:13] 강사가 직접 나열한 위젯/개념 정리 [^114]: @[52:19]~@[52:31] SafeArea 정의 설명 [^115]: @[52:31]~@[52:44] 내장 이미지, 폴더+yaml 등록 [^116]: @[52:44]~@[52:57] Spacer 설명 [^117]: @[52:57]~@[53:04] Expanded 설명 [^118]: @[53:04]~@[53:13] Padding 설명 [^119]: @[53:04]~@[53:13] SizedBox 설명 [^120]: @[53:20]~@[53:41] “작동은 안 하고 배치만… Column/Row로 90%” [^121]: @[53:48]~@[54:14] “옵션 더 있다”, Row 추가 설명 [^122]: @[54:49]~@[55:03] Row 메인 축 설명 [^123]: @[55:03]~@[55:10] center 정렬 시연 [^124]: @[55:10]~@[55:17] end(오른쪽) 정렬 시연 [^125]: @[54:19]~@[54:44] SizedBox로 억지 간격(10,20) [^126]: @[55:32]~@[55:55] 선택 항목 폰트 키우기 예시(TextStyle) [^127]: @[56:03]~@[56:18] 크기 다르면 보기 안 좋음 [^128]: @[56:22]~@[56:41] CrossAxisAlignment 개념 소개 [^129]: @[56:41]~@[56:58] end로 아래쪽 정렬 시연 [^130]: @[57:07]~@[57:29] 선택 상태 표시(크기/색상 등) 언급 [^131]: @[57:35]~@[57:51] Column에도 동일 개념, 축만 반대 [^132]: @[57:59]~@[58:15] const=상수 정의 [^133]: @[58:25]~@[58:48] const의 성능 이점 설명 [^134]: @[58:48]~@[58:57] 동작은 같지만 권장 [^135]: @[59:14]~@[59:42] 변수 쓰려면 const 빼야 함(에러 이유) [^136]: @[59:52]~@[01:00:06] int vs double 타입 문제 언급 [^137]: @[01:00:06]~@[01:00:19] 안 바뀌면 const, 바뀌면 const 금지 [^138]: @[01:00:31]~@[01:00:38] 다음 시간 예고(폰트 내장/외부 폰트/배치) [^139]: @[19:35]~@[19:55] 구조 정리 + @[21:43]~@[24:57] 썰기 원리 전반 [^140]: @[09:50]~@[12:45] 용어들( Material/Cupertino/Scaffold) + @[29:38]~@[53:13] 각 위젯 설명 구간 [^141]: 사용자 제공 메타데이터(제목/채널/길이/링크)