Flutter Constraint와 Layout

Flutter는 3단계를 거쳐 widget의 layout을 결정한다.

  1. Constraints go down : Parent widget이 child widget에 constraint를 제안한다.
  2. Sizes go up : Child widget은 constraint 범위 내의 size를 parent widget에 알려준다.
  3. Parent sets position : Parent widget은 자신의 alignment 정보와 child widget size를 사용해서 child widget의 position을 결정한다.

여기서 “constraint“란 size의 최대 ~ 최소값의 범위를 말한다. Flutter에서는 BoxConstraints class를 주로 사용하여 minWidth, minHeight, maxWidth, maxHeight을 정의한다.

const BoxConstraints({
    this.minWidth = 0.0,
    this.maxWidth = double.infinity,
    this.minHeight = 0.0,
    this.maxHeight = double.infinity,
});

여기서 주의할 점은, constraint는 size의 범위를 제한하는 것이므로 child widget의 size 또한 이 constraint 범위 안에 있어야 한다는 것이다. Child widget size가 parent가 제안한 constraint 범위를 벗어나면 “A RenderFlex overflowed…” error가 발생한다.

Tight vs loose constraints

Tight constraint

  • min == max인 경우로, exact size를 갖게 만든다.
    BoxConstraints.tight(Size size)
        : minWidth = size.width,
            maxWidth = size.width,
            minHeight = size.height,
            maxHeight = size.height;
    
  • App widget은 child widget을 전체 screen에 딱 맞게 만드는데, screen size에 대한 tight constraint가 적용된 것임

Loose constraint

  • min == 0 < max인 경우로, child widget이 범위 내에서 own size를 가질 수 있게 한다.
  • Center widget은 parent로부터 받은 tight constraint를 loose constraint로 바꿔서 child에 전달하는 목적이기도 함

Bounded vs Unbounded

Bounded constraint

  • max < infinity인 경우
  • Widget 크기가 min ~ max 범위 안에서 결정됨

Unbounded constraint

  • max == infinity인 경우
  • Size 제약이 없는 상태이므로 box는 가능한 최대 size로 커지려고 한다.
  • Column, Row 같은 Flex box 또는 ListView, ScrollView subclass들 같은 scrollable region이 unbounded constraint를 갖는다.
  • 발생할 수 있는 문제
    • ListView의 scroll 영역은 main axis 방향으로 무한대 크기를 허용함
    • children중 main axis와 같은 방향으로 크기가 무한대로 확장하는 widget이 있다면 unbound layout error가 발생한다.
      • e.g. 크기가 고정되지 않은 ListView 안에서 Column, Row 등 flexible widget 사용
    • 둘 중 하나는 SizedBox로 크기를 고정하거나 BoxConstraint로 제약을 추가해야 한다.

Flutter Widgets들의 Size 규칙

Flutter widget들이 size를 결정하는 규칙은 아래와 같다.

  1. 가능한 영역을 꽉 채우는 size를 사용하는 widget (e.g. Center, Align, ListView)
  2. Child widget과 동일한 size를 사용하는 widget (e.g. Transform, Opacity)
  3. Content size를 사용하는 widget (e.g. Image, Text)

특히, Container widget은 어떤 속성을 사용하는지에 따라 동작하는 방식이 달라진다.

  1. 기본적으로 가능한 영역을 꽉 채우는 size를 사용한다. (1번)
  2. width, height 등 size가 지정되면 해당 size를 사용한다. (3번)
  3. child widget을 갖는 경우, child widget size를 사용한다. (2번)

Reference