[Flutter] 제약조건 이해하기

Minji Jeong·2024년 1월 1일
0

Android

목록 보기
39/39
post-thumbnail

플러터를 공부한지 그리 오래되지는 않았으나, 깊이가 없는 상태에서 주먹구구식으로 코드를 짜다보니 화면을 구현할 때 RenderFlex Overflowed 에러를 마주한적이 굉장히 많습니다. 특히 화면이 복잡할수록 디바이스 크기별로 대응가능한 Responsible UI를 구현하는것이 더욱더 까다롭고, 그에 따라 플러터 위젯의 제약조건에 대해 어느정도 지식이 없으면 RenderFlex Overflowed를 해결하는데 어려움을 겪을 수 밖에 없다는 것을 깨달았습니다.

이러한 저의 무지함에 비롯된 빈번한 RenderFlex Overflowed 에러에 대한 이해도를 높이고, 좀 더 빠르게 이슈를 해결해보고자 하는 의지에 본 포스팅을 작성하게 되었습니다. 플러터의 제약조건이란 무엇인지 대략 감을 잡는것을 목표로 하여 작성했습니다.

플러터 제약조건에 대한 공식 문서를 확인하고 싶다면 다음 링크를 클릭해주세요.

Flutter - Understanding constraints

Constraints go down. Sizes go up. Parent sets position

플러터에서는 위 문장을 강조하면서, 이 규칙을 알지 못하면 플러터의 레이아웃을 이해할 수 없다고 명시하고 있습니다.

Flutter Constraints의 핵심 개념

Constraints go down

위젯은 자신의 제약 조건을 부모 위젯으로부터 받아옵니다. 제약조건에는 min/max width, min/max height 4가지가 있습니다.

Sizes go up

→ 부모 위젯은 자식 위젯들에게 제약조건을 알려줍니다. 그리고 각각의 자식 위젯에게 원하는 사이즈를 물어본 후 자식 위젯들을 차례차례 배치합니다.

Parent sets position

→ 마지막으로 자식 위젯들은 제약조건을 받아온 부모 위젯에게 자신의 사이즈를 알려줍니다.

Limitations

플러터의 레이아웃 엔진은 one-pass 프로세스로 설계되었습니다. 이는 플러터가 위젯을 매우 효율적으로 배치하지만, 다음의 몇가지 제한사항이 있음을 의미합니다.

  1. 위젯은 부모에게 받은 제약 조건 내에서만 자신의 사이즈를 결정할 수 있습니다. 이는 즉 일반적으로 위젯이 원하는 크기를 가질 수 없음을 의미합니다.
  2. 위젯의 위치를 결정하는 것은 위젯의 부모이기 때문에 위젯은 화면에서 자신의 위치를 알 수 없고 결정하지도 않습니다.
  3. 부모 위젯의 사이즈와 위치 또한 부모 위젯의 부모 위젯에 의존합니다 → 전체 위젯 트리를 고려하지 않고 특정 위젯의 사이즈와 위치를 정확히 정의하는 것은 불가능합니다.
  4. 자식 위젯이 부모와 다른 크기를 원하지만 부모가 이를 정렬할 정보가 충분하지 않은 경우 자식 크기는 무시될 수 있습니다. 때문에 정렬 방식은 구체적이여야합니다.

플러터에서 위젯들은 자신의 RenderBox에 그려집니다. RenderBox의 크기는 부모 위젯으로부터 받은 제약조건에 의해 결정됩니다.

✔️ RenderBox

https://api.flutter.dev/flutter/rendering/RenderBox-class.html

  • 렌더트리 내의 시각적 요소를 렌더링하기 위한 기본 클래스입니다.
  • 각 RenderBox의 크기는 너비와 높이로 표현되며, 각 RenderBox에는 왼쪽 위 모서리가 (0.0)에 배치되는 자체 좌표계가 있습니다. 따라서 RenderBox의 오른쪽 하단 모서리는 (width, height)에 있습니다.

Container의 경우 크기가 고정되지 않으면 확장 가능한 최대 크기로 커지지만, 크기가 고정되면 고정된 크기만큼 확장됩니다.

Row 와 Column(flex box)도 주어진 제약조건에 따라 크기를 확장합니다. 공식 문서에서 이 부분에 Flex 섹션을 태그해놓아서 해당 부분에 대해 잠깐 짚고 넘어가봅니다.

✔️ Flex

https://docs.flutter.dev/ui/layout/constraints#flex

  • 화면의 주축(가로 또는 세로)을 따라 사용가능한 공간을 분배할 수 있도록 하는 위젯입니다. Flex를 사용하면 자식이 배치되는 축(main axis)를 제어할 수 있습니다. 기본 축을 미리 알고 있다면 대표적인 flex box 모델인 Row 또는 Column을 사용하는것이 가독성에 더 좋습니다.
  • 주축 방향으로 사용 가능한 공간을 채우기 위해 자식 위젯을 확장하고자 한다면 자식 위젯을 Expanded 감싸주는 것이 좋습니다.

Row와 Column 같은 위젯들은 주어진 제약조건이 bounded인가 unbounded인가에 따라 다르게 동작합니다. 제약조건이 bounded일 때 자신의 주축 방향으로 가능한 한 크게 확장합니다. unbounded일 때는 자식 위젯들의 크기에 맞춰집니다. 이때 각 위젯의 flex 값은 0으로 설정되어야 합니다. 즉 flex box가 다른 flex box나 스크롤뷰에 있을 때 그의 자식 위젯으로 Expanded를 사용할 수 없습니다. 자식 위젯인 Expanded가 부모 위젯의 제약조건(unbounded)으로 인해 자신의 크기를 무제한으로 확장하려 하기 때문입니다(사용한다면 RenderFlex children have non-zero flex but incoming height constraints are unbounded 를 마주하게 될 겁니다).

플러터의 제약조건을 더욱 더 이해하기 위해 공식문서에 있는 3가지의 예제와 제가 직접 커스텀한 예제를 가져왔습니다. 예제에 나와있는 각 위젯들은 너비와 높이 모두 double.infinity(너비와 높이가 double.infinity로 설정된 경우 화면을 꽉 채웁니다) 값을 가진 ConstrainedBox 의 자식 위젯으로 배치됩니다.

Expanded(
	child: ConstrainedBox(
	  constraints: BoxConstraints.tightFor(
       width: double.infinity, height: double.infinity),
     child: widget.examples[count - 1]),
),

예제 5

Center(
  child: Container(
      width: double.infinity, height: double.infinity, color: red),
)
  • 부모 위젯인 Center는 widthFactor, heightFactor가 정의되지 않았으므로 부모의 제약 조건에 따라 화면 크기만큼 확장됩니다.
  • Center는 Container에게 원하는 크기로 지정될 수 있지만 화면보다 커질 순 없다고 알려줍니다. Container는 사이즈가 double.infinity로 설정되어 있기 때문에 무한한 크기를 원하지만, 화면보다 커질 수 없기 때문에 화면을 채울 뿐입니다.

예제 6

Center(
  child: Container(color: red),
)
  • 부모 위젯인 Center는 widthFactor, heightFactor가 정의되지 않았으므로 부모의 제약 조건에 따라 화면 크기만큼 확장됩니다.
  • Center는 Container에게 원하는 크기로 지정할 수 있지만 화면보다 커질 수는 없다고 알려줍니다. Container는 자식 위젯이 없고 크기가 고정되어 있지 않기 때문에 가능한만큼 최대로 확장되어 화면을 채웁니다.

예제 7

Center(
  child: Container(
    color: red,
    child: Container(color: green, width: 30, height: 30),
  ),
)
  • 부모 위젯인 Center는 widthFactor, heightFactor가 정의되지 않았으므로 부모의 제약 조건에 따라 화면 크기만큼 확장됩니다.
  • Center는 Red Container에게 원하는 크기로 지정될 수 있지만 화면보다 클 수 없음을 알려줍니다. Red Container는 고정된 크기가 없지만 자식 위젯을 갖고 있으므로 자식과 동일한 크기를 갖기로 결정합니다.
  • Red Container는 자식 위젯에게 원하는 크기로 지정될 수 있지만 화면보다 클 수는 없음을 알려줍니다.
  • 자식 위젯인 Green Container는 고정된 크기를 갖고 있고, 이 크기가 되기를 원합니다. 위에서 언급했듯이 고정된 크기를 가지지 않은 Red Container 역시 자식과 동일한 크기를 갖게 됩니다. 이렇게 되면 Green Container가 Red Container를 완전히 덮게 되어 아래의 이미지가 됩니다.

커스텀 예제

Center(
  child: Container(
    width: 300,
    height: 300,
    color: red,
    child: Container(color: green, width: 30, height: 30),
  ),
)
  • 부모 위젯인 Center는 widthFactor, heightFactor가 정의되지 않았으므로 부모의 제약 조건에 따라 화면 크기만큼 확장됩니다.
  • Center는 Red Container에게 원하는 크기로 지정될 수 있지만 화면보다 클 수 없음을 알려줍니다. Red Container는 고정된 크기를 가지므로 원하는 크기만큼 커질 수 있습니다.
  • Green Container는 고정된 크기를 갖고 있고, 이 크기가 되기를 원합니다. 하지만 부모 위젯인 Red Container의 크기를 따라야 하므로 Red Container의 크기만큼 확장됩니다. 이렇게 되면 Green Container가 Red Container를 완전히 덮게 되어 아래의 이미지가 됩니다.

정리

  • 부모 위젯의 크기가 정해지지 않았을 때 → 자식 위젯은 고정된 크기를 가질 수 있으며, 부모 위젯 역시 자식 위젯의 크기를 따릅니다.
  • 부모 위젯의 크기가 정해졌을 때 → 자식 위젯의 크기가 고정되었더라도 부모 위젯의 크기를 따릅니다.
  • 만약 고정된 크기가 화면의 크기를 넘을 경우 → Renderflex overflowed 에러가 발생합니다.

References

https://docs.flutter.dev/ui/layout/constraints
https://jinhan38.com/146
https://bsscco.github.io/posts/2019-03-13-flutter-dealing-with-box-constraints/
https://nayotutorial.tistory.com/63

profile
Mobile Software Engineer

0개의 댓글