Flutter 튜토리얼 7편: 제약 조건(Constraints) 이해하기
요약
핵심 요지
문서가 설명하는 범위
- Constraints 시스템의 작동 원리
- Tight와 Loose Constraints의 차이
- Widget, Element, RenderObject의 관계
- 흔한 레이아웃 문제 해결법
읽는 시간: 16분 | 난이도: 중급
참고 자료
- Understanding constraints - Constraints 이해하기
- Flutter architectural overview - Flutter 아키텍처 개요
문제 상황
Flutter에서 위젯에 width: 100을 설정했는데 100픽셀로 렌더링되지 않는 경우가 있습니다.
이런 “예상과 다른 레이아웃” 문제는 Constraints 시스템을 이해하지 못해서 발생합니다.
자주 겪는 문제들
// 문제 1: Container 크기가 무시됨Container( width: 100, height: 100, color: Colors.red,) // 화면 전체를 차지함!
// 문제 2: Column 안의 위젯이 넓어지지 않음Column( children: [ Container(color: Colors.blue), // 높이가 0! ],)
// 문제 3: Unbounded height 오류ListView( children: [ Column(children: [...]), // 오류 발생! ],)문제는 다음과 같습니다.
- 위젯이 설정한 크기를 항상 가질 수 없다.
- 부모가 전달하는 제약 조건이 자식의 크기를 결정한다.
- Constraints 규칙을 모르면 디버깅이 어렵다.
해결 방법
Flutter의 레이아웃 시스템을 이해하면 이런 문제를 예측하고 해결할 수 있습니다.
핵심은 세 가지 규칙입니다.
챕터 1: 레이아웃의 세 가지 규칙
Why
NOTEFlutter 레이아웃이 다른 프레임워크와 다르게 작동하는 이유는 성능 최적화 때문입니다.
제약 기반 레이아웃은 O(n) 시간 복잡도로 매우 효율적입니다.
What
NOTEFlutter 레이아웃의 핵심 규칙은 다음과 같습니다.
- Constraints go down (제약은 아래로)
- Sizes go up (크기는 위로)
- Parent sets position (부모가 위치 결정)
How
TIP레이아웃 과정을 그림으로 보기
flowchart TB subgraph "1단계: 제약 전달 ⬇️" A[부모] -->|"최대 300×100"| B[위젯] B -->|"최대 290×90"| C[자식] end subgraph "2단계: 크기 보고 ⬆️" D[자식] -->|"100×50 원함"| E[위젯] E -->|"110×60 결정"| F[부모] end subgraph "3단계: 위치 결정" G[부모가 위젯을<br/>x:10, y:20에 배치] end3단계 정리
단계 방향 내용 1. Constraints 부모 → 자식 ”너는 최대 이 크기까지만 가능해” 2. Size 자식 → 부모 ”저는 이 크기로 할게요” 3. Position 부모 결정 ”넌 여기에 배치할게” 중요한 제한사항
규칙 설명 크기 제한 위젯은 제약 범위 내에서만 크기 결정 가능 위치 무지 위젯은 자신의 화면 위치를 알 수 없음 전체 의존 정확한 크기/위치는 전체 트리에 의존
Watch out
WARNING위젯에
width: 100을 설정해도 부모의 제약이 우선합니다.
부모가 “최소 200픽셀”이라고 하면 100픽셀은 무시됩니다.// 부모가 tight constraints를 전달하면// Container의 크기 설정이 무시됨SizedBox.expand(child: Container(width: 100, // 무시됨height: 100, // 무시됨color: Colors.red,),)
결론: 위젯 크기는 자신의 설정과 부모의 제약 중 더 엄격한 것에 따릅니다.
챕터 2: Tight와 Loose Constraints
Why
NOTE같은 위젯이 어떤 부모 아래에서는 원하는 크기로 렌더링되고, 다른 부모 아래에서는 그렇지 않습니다.
이 차이는 부모가 전달하는 제약의 종류 때문입니다.
What
NOTETight Constraints는 정확한 크기를 강제하고, Loose Constraints는 최대 크기만 제한합니다.
How
TIPTight Constraints (긴밀한 제약)
최소값과 최대값이 같은 제약입니다.
자식은 정확히 그 크기여야 합니다.// Tight Constraints 예시BoxConstraints.tight(Size(200, 100))// minWidth: 200, maxWidth: 200// minHeight: 100, maxHeight: 100Loose Constraints (느슨한 제약)
최소값은 0이고 최대값만 있는 제약입니다.
자식은 0부터 최대값 사이에서 원하는 크기를 선택합니다.// Loose Constraints 예시BoxConstraints.loose(Size(200, 100))// minWidth: 0, maxWidth: 200// minHeight: 0, maxHeight: 100차이점 비교
구분 Tight Loose 최소/최대 같음 다름 (최소=0) 자식 자유도 없음 있음 사용 위젯 Scaffold body, SizedBox.expand Center, Align 실제 예제
// 예제 1: Container가 화면 전체를 차지// Scaffold의 body는 tight constraints 전달Scaffold(body: Container(width: 100, // 무시됨height: 100, // 무시됨color: Colors.red,),)// 예제 2: Container가 원하는 크기로 렌더링// Center는 loose constraints 전달Scaffold(body: Center(child: Container(width: 100, // 적용됨!height: 100, // 적용됨!color: Colors.red,),),)왜 Center가 해결책인가?
graph TD A[Scaffold body] -->|Tight: 화면 전체| B[Container] B --> C[화면 전체 크기] D[Scaffold body] -->|Tight: 화면 전체| E[Center] E -->|Loose: 0~화면 전체| F[Container] F --> G[100x100 크기]
Watch out
WARNINGCenter, Align 같은 위젯은 자식에게 loose constraints를 전달합니다.
하지만 자식이 크기를 지정하지 않으면 0 크기가 될 수 있습니다.Center(child: Container(// width, height 없음color: Colors.red,),)// Container 크기가 0x0!
결론: 위젯이 원하는 크기로 렌더링되지 않으면 Center나 Align으로 감싸서 loose constraints를 전달합니다.
챕터 3: ConstrainedBox와 UnconstrainedBox
Why
NOTE때로는 부모의 제약을 수정하거나 무시해야 합니다.
ConstrainedBox는 추가 제약을 적용하고, UnconstrainedBox는 제약을 제거합니다.
What
NOTE
ConstrainedBox4는 자식에게 추가 제약을 적용합니다.
UnconstrainedBox5는 부모의 제약을 무시하고 자식이 원하는 크기를 허용합니다.
How
TIPConstrainedBox: 추가 제약 적용
Center(child: ConstrainedBox(constraints: BoxConstraints(minWidth: 70,minHeight: 70,maxWidth: 150,maxHeight: 150,),child: Container(width: 10, // 최소값 70이 적용됨height: 10, // 최소값 70이 적용됨color: Colors.red,),),)// 결과: 70x70 빨간 ContainerConstrainedBox의 규칙
ConstrainedBox는 부모 제약과 자신의 제약 중 더 엄격한 것을 적용합니다.
// 부모 제약: maxWidth 100// ConstrainedBox: maxWidth 150// 결과: maxWidth 100 (더 엄격한 것)// 부모 제약: minWidth 0// ConstrainedBox: minWidth 70// 결과: minWidth 70 (더 엄격한 것)UnconstrainedBox: 제약 무시
ConstrainedBox(constraints: BoxConstraints(maxWidth: 200,maxHeight: 200,),child: UnconstrainedBox(child: Container(width: 300, // 200 제약 무시!height: 50,color: Colors.red,),),)// 결과: 300x50 Container (overflow 경고 발생)OverflowBox: 오버플로우 허용
SizedBox(width: 100,height: 100,child: OverflowBox(maxWidth: 300,child: Container(width: 300,height: 50,color: Colors.red,),),)// 결과: 300x50 Container가 부모 밖으로 넘침
Watch out
WARNINGUnconstrainedBox로 제약을 무시하면 overflow 경고가 발생할 수 있습니다.
화면 밖으로 위젯이 넘치면 노란색/검은색 줄무늬로 표시됩니다.// overflow 경고 예시Row(children: [UnconstrainedBox(child: Container(width: 1000), // 화면보다 큼!),],)
결론: ConstrainedBox로 추가 제약을, UnconstrainedBox로 제약 해제를 조절합니다.
챕터 4: Widget, Element, RenderObject
Why
NOTEFlutter의 효율적인 렌더링을 이해하려면 세 가지 트리 구조를 알아야 합니다.
Widget은 설계도, Element는 인스턴스, RenderObject는 실제 렌더링을 담당합니다.
What
NOTEFlutter는
Widget Tree6,Element Tree7,RenderObject Tree8 세 가지 트리를 유지합니다.
How
TIP세 가지 트리의 역할
트리 역할 특징 Widget UI 설계도 불변, 매 프레임 재생성 가능 Element Widget 인스턴스 프레임 간 유지, 캐시 역할 RenderObject 실제 렌더링 레이아웃과 페인팅 수행 관계도
graph TD subgraph Widget Tree W1[Container] --> W2[Row] W2 --> W3[Text] W2 --> W4[Image] end subgraph Element Tree E1[ContainerElement] --> E2[RowElement] E2 --> E3[TextElement] E2 --> E4[ImageElement] end subgraph RenderObject Tree R1[RenderDecoratedBox] --> R2[RenderFlex] R2 --> R3[RenderParagraph] R2 --> R4[RenderImage] end W1 -.-> E1 E1 -.-> R1성능 최적화: Element 캐싱
Widget이 변경되어도 Element는 재사용됩니다.
// 이전 프레임Text('Hello')// 현재 프레임Text('World')// 동작:// 1. 새 Text Widget 생성// 2. 기존 TextElement 재사용// 3. RenderParagraph만 업데이트RenderBox와 제약
RenderObject 중 대부분은
RenderBox입니다. RenderBox는 BoxConstraints를 받아 레이아웃을 계산합니다.// RenderBox가 받는 제약BoxConstraints(minWidth: 0,maxWidth: 300,minHeight: 0,maxHeight: 200,)// RenderBox가 결정하는 크기Size(200, 150) // 제약 범위 내
Watch out
WARNINGWidget은 불변이므로 속성을 변경하면 새 Widget이 생성됩니다.
하지만 Element와 RenderObject는 가능하면 재사용되어 성능을 유지합니다.// 비효율적: 매번 새 Widget 타입condition ? Text('A') : Container(child: Text('B'))// 효율적: 같은 Widget 타입, 속성만 변경Text(condition ? 'A' : 'B')
결론: Flutter는 Widget-Element-RenderObject 구조로 효율적인 렌더링을 달성합니다.
챕터 5: 흔한 레이아웃 문제 해결
Why
NOTEConstraints를 이해해도 실제 개발에서는 다양한 문제를 만납니다.
흔한 문제들의 원인과 해결책을 알아둡니다.
What
NOTEUnbounded constraints, overflow, 크기 무시 등의 문제는 대부분 제약 조건 관련입니다.
How
TIP문제 1: Container 크기가 무시됨
// 문제Scaffold(body: Container(width: 100,height: 100,color: Colors.red,),)// Container가 화면 전체 차지// 해결: Center로 감싸기Scaffold(body: Center(child: Container(width: 100,height: 100,color: Colors.red,),),)문제 2: Column 안의 Container가 안 보임
// 문제Column(children: [Container(color: Colors.blue), // 높이 0!],)// 해결 1: 높이 지정Column(children: [Container(height: 100,color: Colors.blue,),],)// 해결 2: Expanded 사용Column(children: [Expanded(child: Container(color: Colors.blue),),],)문제 3: Unbounded height 오류
// 문제: Column 안에 ListViewColumn(children: [ListView(...), // 오류!],)// 해결 1: Expanded로 감싸기Column(children: [Expanded(child: ListView(...),),],)// 해결 2: SizedBox로 높이 제한Column(children: [SizedBox(height: 200,child: ListView(...),),],)문제 4: Row 안의 Text가 넘침
// 문제Row(children: [Text('아주 긴 텍스트가 화면을 넘어갑니다...'), // overflow!],)// 해결: Expanded나 Flexible 사용Row(children: [Expanded(child: Text('아주 긴 텍스트가 화면을 넘어갑니다...'),),],)문제 해결 체크리스트
증상 원인 해결책 위젯이 화면 전체 Tight constraints Center/Align 사용 위젯이 안 보임 크기 0 높이/너비 지정 또는 Expanded Unbounded 오류 무한 제약 Expanded 또는 SizedBox Overflow 경고 부모보다 큼 Expanded, Flexible, 스크롤
Watch out
WARNING
flutter analyze나 DevTools의 Widget Inspector를 사용하면 제약 조건을 확인할 수 있습니다.
문제가 발생하면 해당 위젯의 constraints를 직접 확인하세요.LayoutBuilder(builder: (context, constraints) {print('Constraints: $constraints');return Container(...);},)
결론: 레이아웃 문제는 대부분 제약 조건 관련이며, Center, Expanded, SizedBox로 해결합니다.
한계
Constraints 시스템은 강력하지만 복잡합니다.
- 직관적이지 않음: CSS나 다른 프레임워크와 다르게 작동합니다.
- 전체 트리 의존: 단일 위젯만 보고는 최종 크기/위치를 알 수 없습니다.
- 디버깅 어려움: 어떤 부모가 제약을 전달했는지 추적이 필요합니다.
Footnotes
-
Constraints go down, Sizes go up, Parent sets position: Flutter 레이아웃의 핵심 규칙이다. 제약은 부모에서 자식으로, 크기는 자식에서 부모로 전달되며, 위치는 부모가 결정한다. ↩
-
Tight Constraints(긴밀한 제약): 최소값과 최대값이 같은 제약이다. 자식은 정확히 그 크기여야 한다. ↩
-
Loose Constraints(느슨한 제약): 최소값은 0이고 최대값만 있는 제약이다. 자식이 원하는 크기를 선택할 수 있다. ↩
-
ConstrainedBox: 자식에게 추가 제약 조건을 적용하는 위젯이다. ↩
-
UnconstrainedBox: 부모의 제약을 무시하고 자식이 원하는 크기를 허용하는 위젯이다. ↩
-
Widget Tree(위젯 트리): UI를 선언하는 불변 객체들의 트리이다. ↩
-
Element Tree(엘리먼트 트리): Widget의 인스턴스를 나타내며 프레임 간 유지되는 트리이다. ↩
-
RenderObject Tree(렌더 오브젝트 트리): 실제 레이아웃과 페인팅을 수행하는 트리이다. ↩
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!