Flutter 튜토리얼 4편: Widget 개념과 기본 위젯
요약
핵심 요지
문서가 설명하는 범위
- Widget이 무엇이고 왜 모든 것이 Widget인지
- StatelessWidget과 StatefulWidget의 차이와 사용 기준
- 기본 위젯들의 역할과 사용법
읽는 시간: 15분 | 난이도: 초급
참고 자료
- Widgets fundamentals - Widget 핵심 개념
- Widget catalog - 위젯 카탈로그
- Basic widgets - 기본 위젯 목록
문제 상황
전통적인 UI 프레임워크에서는 버튼, 텍스트, 레이아웃, 애니메이션 등을 각각 다른 방식으로 다룹니다.
HTML/CSS에서는 요소와 스타일이 분리되고, 네이티브 앱에서는 뷰와 레이아웃 매니저가 별도로 존재합니다.
이게 왜 문제일까요?
기존 방식의 한계
웹: HTML(구조) + CSS(스타일) + JavaScript(동작)Android: View + LayoutManager + AdapteriOS: UIView + AutoLayout + Delegate문제는 다음과 같습니다.
- UI 요소마다 다른 API와 패턴을 익혀야 한다.
- 구조, 스타일, 동작이 분리되어 코드가 흩어진다.
- 재사용 가능한 컴포넌트를 만들기 어렵다.
해결 방법
Flutter는 “모든 것이 Widget”이라는 단순한 원칙으로 UI를 구성합니다.
텍스트, 버튼, 패딩, 정렬, 애니메이션 모두 Widget입니다.
하나의 개념으로 모든 UI를 표현하는 거죠.
챕터 1: Widget이란 무엇인가
Why
NOTEUI 요소마다 다른 클래스와 패턴을 사용하면 어떻게 될까요?
배워야 할 것이 너무 많아집니다.
그래서 Flutter는 “모든 것을 Widget 하나로 표현”합니다.// 전통적 방식: 요소마다 다른 클래스TextView textView = new TextView();LinearLayout layout = new LinearLayout();Animation animation = new Animation();
What
NOTEWidget은 Flutter에서 UI를 표현하는 기본 단위입니다.
화면에 보이는 모든 것이 Widget입니다.
심지어 보이지 않는 레이아웃과 패딩도 Widget이에요.
How
TIPWidget은 UI의 설계도라고 생각하면 됩니다.
한번 만든 Widget은 변경할 수 없습니다.
상태가 바뀌면 새로운 Widget을 만들어야 합니다.// 모든 것이 WidgetText('Hello') // 텍스트도 WidgetIcon(Icons.star) // 아이콘도 WidgetPadding(...) // 패딩도 WidgetCenter(...) // 정렬도 WidgetElevatedButton(...) // 버튼도 WidgetWidget은 계층 구조를 이룹니다.
graph TD A[MaterialApp] --> B[Scaffold] B --> C[AppBar] B --> D[body: Center] C --> E[Text: 제목] D --> F[Column] F --> G[Text: 내용] F --> H[ElevatedButton]
Watch out
WARNINGWidget은 한번 만들면 변경할 수 없습니다.
그래서 UI를 바꾸고 싶으면 새로운 Widget을 만들어야 합니다.
이게 Flutter의 핵심 개념입니다.// 잘못된 방식: Widget 속성 직접 변경 시도// text.value = '새 텍스트'; // 불가능// 올바른 방식: 새 Widget 생성Text('새 텍스트')
결론: Flutter에서 모든 UI 요소는 Widget이며, 불변 객체로 UI를 선언합니다.
챕터 2: StatelessWidget 이해하기
Why
NOTE대부분의 UI는 한 번 그려지면 변경되지 않습니다.
예를 들어 앱 이름이나 아이콘은 바뀌지 않죠.
이런 경우에는 상태 관리가 필요 없습니다.정적 텍스트, 아이콘, 레이아웃 → 상태 불필요카운터, 입력 필드, 체크박스 → 상태 필요
What
NOTE
StatelessWidget은 상태가 없는 Widget입니다.
생성자로 받은 값만으로 UI가 결정됩니다.
시간이 지나도 스스로 변하지 않아요.
How
TIPStatelessWidget은
build메서드만 구현하면 됩니다.class Greeting extends StatelessWidget {final String name;const Greeting({super.key, required this.name});@overrideWidget build(BuildContext context) {return Text('안녕하세요, $name님!');}}// 사용Greeting(name: 'Flutter')
build메서드가 호출되는 시점은 다음과 같습니다.
시점 설명 최초 렌더링 Widget이 화면에 처음 그려질 때 부모 재빌드 부모 Widget이 재빌드될 때 의존성 변경 InheritedWidget 등 의존성이 바뀔 때
Watch out
WARNING
build메서드는 자주 호출될 수 있으므로 부작용(side effect)이 없어야 합니다.@overrideWidget build(BuildContext context) {// 잘못된 예: build에서 네트워크 호출// fetchData(); // 매번 호출됨!// 올바른 예: 순수하게 Widget만 반환return Text('Hello');}
결론: 변경되지 않는 UI는 StatelessWidget으로 간단하게 구현합니다.
챕터 3: StatefulWidget 이해하기
Why
NOTE카운터, 체크박스, 텍스트 입력 필드를 생각해보세요.
사용자가 뭔가를 하면 UI가 바뀌어야 합니다.
이런 경우에는 상태를 저장하고 관리해야 합니다.// 버튼을 누르면 숫자가 증가해야 함int count = 0; // 이 상태를 어디에 저장?
What
NOTE
StatefulWidget은 상태를 가지는 Widget입니다.
Widget과 State가 따로 분리되어 있습니다.
Widget이 다시 만들어져도 State는 그대로 유지됩니다.
How
TIPStatefulWidget은 두 개의 클래스로 구성됩니다.
// 1. StatefulWidget 클래스 (불변)class Counter extends StatefulWidget {const Counter({super.key});@overrideState<Counter> createState() => _CounterState();}// 2. State 클래스 (상태 보관)class _CounterState extends State<Counter> {int _count = 0; // 상태 변수void _increment() {setState(() {_count++; // 상태 변경});}@overrideWidget build(BuildContext context) {return Column(children: [Text('카운트: $_count'),ElevatedButton(onPressed: _increment,child: Text('증가'),),],);}}
setState()4의 역할은 다음과 같습니다.sequenceDiagram participant User as 사용자 participant Button as 버튼 participant State as State participant Framework as Flutter User->>Button: 클릭 Button->>State: _increment() State->>State: setState(() { _count++ }) State->>Framework: 재빌드 요청 Framework->>State: build() 호출 State->>Framework: 새 Widget 반환
Watch out
WARNING
setState()없이 상태를 변경하면 UI가 업데이트되지 않습니다.void _increment() {// 잘못된 예: setState 없이 변경_count++; // UI 업데이트 안 됨!// 올바른 예: setState로 감싸기setState(() {_count++;});}
결론: 변경되는 상태가 필요하면 StatefulWidget을 사용하고, 반드시 setState()로 변경을 알립니다.
챕터 4: Widget 선택 기준
Why
NOTEStatelessWidget과 StatefulWidget 중 뭘 선택해야 할까요?
명확한 기준이 필요합니다.
잘못 선택하면 코드가 복잡해지거나 버그가 생길 수 있습니다.
What
NOTE선택 기준은 간단합니다. “시간이 지나면서 변하는 데이터가 있는가?”입니다.
How
TIP
상황 선택 예시 고정된 텍스트, 아이콘 StatelessWidget 라벨, 로고, 헤더 사용자 입력 반응 StatefulWidget 버튼 클릭 카운터, 폼 애니메이션 StatefulWidget 로딩 스피너, 전환 효과 외부 데이터 표시만 StatelessWidget API 데이터 표시 (상태는 부모에서) 내부 상태 관리 StatefulWidget 탭 선택, 확장/축소 결정 흐름도
flowchart TD A[Widget 생성] --> B{내부에서 변하는 데이터가 있는가?} B -->|없음| C[StatelessWidget] B -->|있음| D{상태를 이 Widget이 관리해야 하는가?} D -->|예| E[StatefulWidget] D -->|아니오| F[StatelessWidget + 부모에서 상태 전달]
Watch out
WARNING무조건 StatefulWidget을 사용하면 안 됩니다.
성능과 유지보수에 불리합니다.
가능하면 StatelessWidget을 사용하세요.// 권장: 상태를 부모에서 관리class Parent extends StatefulWidget { ... }class Child extends StatelessWidget {final int count; // 부모에서 전달받음const Child({required this.count});...}
결론: 기본적으로 StatelessWidget을 선택하고, 내부 상태가 필요할 때만 StatefulWidget을 사용합니다.
챕터 5: 기본 위젯 살펴보기
Why
NOTEFlutter에는 수백 개의 Widget이 있습니다.
하지만 자주 사용하는 기본 Widget만 알면 됩니다.
이것들을 조합하면 대부분의 UI를 만들 수 있습니다.
What
NOTE기본 Widget은 구조, 레이아웃, 표시, 입력의 네 가지 역할로 분류할 수 있습니다.
How
TIP구조 Widget
Widget 용도 주요 속성 Scaffold5Material Design 기본 구조 appBar, body, drawer AppBar상단 앱 바 title, actions, leading Container6다목적 컨테이너 padding, margin, color, decoration Scaffold(appBar: AppBar(title: Text('제목')),body: Container(padding: EdgeInsets.all(16),color: Colors.white,child: Text('내용'),),)레이아웃 Widget
Widget 용도 주요 속성 Row7가로 배치 mainAxisAlignment, children Column8세로 배치 crossAxisAlignment, children Center중앙 정렬 child Padding여백 추가 padding, child Column(mainAxisAlignment: MainAxisAlignment.center,children: [Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [Icon(Icons.star),Icon(Icons.star),Icon(Icons.star),],),Padding(padding: EdgeInsets.all(8),child: Text('별점'),),],)표시 Widget
Widget 용도 주요 속성 Text텍스트 표시 style, textAlign, maxLines Icon아이콘 표시 size, color Image이미지 표시 fit, width, height Column(children: [Image.network('https://example.com/image.png'),Text('제목',style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),),Icon(Icons.favorite, color: Colors.red, size: 32),],)입력 Widget
Widget 용도 주요 속성 ElevatedButton돌출 버튼 onPressed, child TextButton텍스트 버튼 onPressed, child IconButton아이콘 버튼 onPressed, icon TextField텍스트 입력 controller, decoration Column(children: [ElevatedButton(onPressed: () { print('클릭!'); },child: Text('확인'),),TextField(decoration: InputDecoration(labelText: '이름'),),],)
Watch out
WARNINGWidget 카탈로그에는 수백 개의 Widget이 있습니다.
처음부터 다 외우려고 하지 마세요.
필요할 때 공식 문서를 찾아보면 됩니다.자주 쓰는 Widget → 먼저 익히기특수한 Widget → 필요할 때 찾아보기
결론: Scaffold, Container, Row, Column, Text 등 기본 Widget을 먼저 익히고, 나머지는 필요할 때 학습합니다.
챕터 6: Widget 카탈로그 활용하기
Why
NOTEFlutter는 공식적으로 수백 개의 Widget을 제공합니다.
어떤 Widget이 있는지 알아야 직접 만들 필요가 없습니다.
카탈로그를 잘 활용하면 개발 시간을 크게 줄일 수 있습니다.
What
NOTEWidget 카탈로그는 용도별로 분류된 공식 Widget 목록입니다.
Material Design(Android)과 Cupertino(iOS) 스타일로 나뉘어 있습니다.
각각의 기능별로 세분화되어 있어서 찾기 쉽습니다.
How
TIP디자인 시스템별 분류
시스템 설명 예시 Material Google Material Design 3 AppBar, FloatingActionButton Cupertino Apple iOS 스타일 CupertinoButton, CupertinoNavigationBar 기능별 분류
카테고리 설명 주요 Widget Basics 필수 기본 Widget Container, Row, Column, Text Layout 배치와 정렬 Stack, GridView, ListView Input 사용자 입력 TextField, Checkbox, Slider Scrolling 스크롤 기능 ListView, SingleChildScrollView Animation 애니메이션 AnimatedContainer, Hero Styling 테마와 스타일 Theme, MediaQuery Widget 찾는 방법
- 공식 문서의 Widget catalog 검색
- Widget of the Week 영상 시리즈 시청
- Flutter DevTools의 Widget Inspector 활용
Watch out
WARNING같은 기능을 하는 Widget이 여러 개 있을 수 있습니다.
Android용과 iOS용이 따로 있기 때문입니다.
플랫폼에 맞는 Widget을 선택해야 일관된 UX를 제공할 수 있습니다.// Android 스타일ElevatedButton(onPressed: () {}, child: Text('확인'))// iOS 스타일CupertinoButton(onPressed: () {}, child: Text('확인'))
결론: Widget 카탈로그를 활용해서 필요한 Widget을 찾고, 플랫폼에 맞게 선택합니다.
한계
Widget 개념은 강력하지만 몇 가지 주의점이 있습니다.
처음에는 낯설 수 있지만 익숙해지면 편해집니다.
- 학습 곡선: “모든 것이 Widget”이라는 개념이 처음에는 낯설 수 있습니다.
- 깊은 중첩: Widget을 조합하다 보면 들여쓰기가 깊어질 수 있습니다.
- 상태 관리 복잡성: 앱이 커지면 StatefulWidget만으로는 상태 관리가 어려워집니다.
Footnotes
-
Widget(위젯): Flutter에서 UI를 구성하는 기본 단위다. 모든 것이 Widget이며, 불변 객체로 UI를 선언한다. ↩
-
StatelessWidget(스테이트리스 위젯): 상태를 가지지 않는 Widget이다. 입력만으로 UI가 결정된다. ↩
-
StatefulWidget(스테이트풀 위젯): 변경 가능한 상태를 가지는 Widget이다. Widget과 State 클래스로 구성된다. ↩
-
setState(): StatefulWidget에서 상태 변경을 Flutter에 알리는 메서드다. 호출하면 build()가 다시 실행된다. ↩
-
Scaffold(스캐폴드): Material Design의 기본 레이아웃 구조를 제공하는 Widget이다. AppBar, body, drawer 등을 포함한다. ↩
-
Container(컨테이너): 패딩, 마진, 배경색, 테두리 등 다양한 스타일을 적용할 수 있는 다목적 Widget이다. ↩
-
Row(로우): 자식 Widget들을 가로 방향으로 배치하는 레이아웃 Widget이다. ↩
-
Column(컬럼): 자식 Widget들을 세로 방향으로 배치하는 레이아웃 Widget이다. ↩
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!