Constraints 시스템의 핵심

Flitter의 레이아웃 시스템은 하나의 간단한 규칙을 따릅니다:

“Constraints go down, Sizes go up”

부모가 자식에게 제약(constraints)을 전달하고, 자식은 그 제약 내에서 자신의 크기를 결정하여 부모에게 보고합니다.

Constraints go down, Sizes go up

        Parent Widget

    ┌────────▼────────┐
    │   Constraints   │ (go down)
    │   min: 0x0      │
    │   max: 300x200  │
    └────────┬────────┘

        Child Widget

    ┌────────▼────────┐
    │   Decides Size  │ (goes up)
    │   chosen: 150x80│
    └─────────────────┘

BoxConstraints의 구성 요소

class BoxConstraints {
  minWidth: number;   // 최소 너비
  maxWidth: number;   // 최대 너비  
  minHeight: number;  // 최소 높이
  maxHeight: number;  // 최대 높이
}

자식은 이 제약 내에서 자신의 크기를 선택할 수 있습니다:

  • minWidth ≤ width ≤ maxWidth
  • minHeight ≤ height ≤ maxHeight

주요 Constraints 패턴

1. Tight Constraints

Constraints.tight(new Size(100, 50))
// minWidth = maxWidth = 100
// minHeight = maxHeight = 50
┌─────────────┐
│ 정확히 100x50 │  ← 자식은 이 크기로 강제됨
└─────────────┘

2. Loose Constraints

Constraints.loose(new Size(100, 50))
// minWidth = minHeight = 0
// maxWidth = 100, maxHeight = 50
┌─────────────┐
│ 0x0 ~ 100x50│  ← 자식이 범위 내에서 자유롭게 선택
└─────────────┘

3. Expand Constraints

Constraints.expand()
// minWidth = maxWidth = ∞
// minHeight = maxHeight = ∞
┌─────────────┐
│ 가능한 최대크기│  ← 부모 공간을 모두 차지
└─────────────┘

4. Unconstrained

UnconstrainedBox({
  child: Container({ width: 1000 })  // 부모 제약 무시
})
Parent: max 300px


UnconstrainedBox  ──→  Child: 1000px (오버플로우 가능)

실제 동작 이해하기

Container vs SizedBox

// Container의 width/height는 "희망사항"
Container({
  width: 200,   // ← 부모가 100px만 허용하면 100px가 됨
  height: 100,
  child: Text("Hello")
})

// SizedBox는 자신의 크기를 "강제"
SizedBox({
  width: 200,   // ← 자식에게 정확히 200px 강제
  height: 100,
  child: Text("Hello")
})

Column 내부의 제약

Column (height: ∞)

  ├─ Child 1 (height: tight 자연스러운 크기)
  ├─ Child 2 (height: tight 자연스러운 크기)  
  └─ Child 3 (height: tight 자연스러운 크기)

Column은 자식들에게 높이를 “tight”하게 제한하지 않습니다. 각 자식이 원하는 만큼 높이를 가질 수 있습니다.

흔한 문제와 해결

문제 1: 무한 크기 에러

// ❌ Column 안의 Column은 무한 높이를 받음
Column({
  children: [
    Column({ children: [...] })  // RenderFlex overflowed!
  ]
})

// ✅ Expanded로 크기 제한
Column({
  children: [
    Expanded({
      child: Column({ children: [...] })
    })
  ]
})

문제 2: 위젯이 원하는 크기가 안 나옴

// ❌ 부모가 tight constraints 전달
Container({
  width: 100,
  height: 100,
  child: Container({
    width: 200,  // 무시됨! (100px로 강제)
    height: 200
  })
})

// ✅ UnconstrainedBox로 제약 해제
Container({
  width: 100,
  height: 100,
  child: UnconstrainedBox({
    child: Container({
      width: 200,  // 적용됨 (오버플로우 발생할 수 있음)
      height: 200
    })
  })
})

문제 3: 중앙 정렬이 안 됨

// ❌ Container가 부모 크기에 맞춰짐
Row({
  children: [
    Container({
      color: 'red',
      child: Text('Hello')  // Container가 텍스트 크기만 가짐
    })
  ]
})

// ✅ 명시적 크기 지정 또는 Expanded 사용
Row({
  children: [
    Expanded({
      child: Container({
        color: 'red',
        alignment: Alignment.center,
        child: Text('Hello')
      })
    })
  ]
})

소스 코드 위치

  • packages/flitter/src/type/constraints.ts: BoxConstraints 클래스
  • packages/flitter/src/renderobject/RenderBox.ts: 제약 처리 로직
  • packages/flitter/src/component/Container.ts: Container의 제약 처리

핵심 정리

  1. 부모가 제약을 전달 → 자식이 크기 결정 → 부모에게 보고
  2. 제약 = 크기 범위 (min/max width/height)
  3. tight: 정확한 크기 강제, loose: 최대 크기만 제한
  4. 이해하면 레이아웃 문제 해결 쉬워짐

다음 장에서는 이러한 제약 시스템을 활용하여 실제로 RenderObject를 직접 구현해보겠습니다.