thumbnail thumbnail
  • 개발

플러터 위젯 파헤치기 - 3: ConstrainedBox , OverflowBox, SizedBox, ConstraintsTransformBox 등등

Moon

플러터 위젯 파헤칠 목록

Row, Column, Flexible, Expanded, Spacer

Align, Center

ClipPath, ClipOval, ClipRect

OverflowedBox, ConstrainedBox, ConstraintsBox, UnConstrainedBox, ConstraintsTransformBox, SizedBox

Stack, Postioned

IndexedStack

Padding

Container, DocoratedBox, Contraint Box, Colored Box,

Layout Builder

Baseline

AspectRatio

Gestures Detector

OffState

Opacity

Overflow Bar

Grid, Grid Tile, GridTileBar

Visibility

Wrap

Transform

 

들어가며

여태껏 flutter에서 Container 아니면 SizedBox만 썼는데 차트와 같은 복잡한 위젯을 그릴려고 하니까 다른 위젯들에 대한 학습이 절실한 시점입니다ㅜ. 그래서 오늘은 Box 특집! Constraints를 다루는 Box들에 대해 알아보겠습니다!

ConstraintsTransformBox

A container widget that applies an arbitrary transform to its constraints, and sizes its child using the resulting BoxConstraints, optionally clipping, or treating the overflow as an error. This container sizes its child using a BoxConstraints created by applying constraintsTransform to its own constraints. This container will then attempt to adopt the same size, within the limits of its own constraints. If it ends up with a different size, it will align the child based on alignment. If the container cannot expand enough to accommodate the entire child, the child will be clipped if clipBehavior is not Clip.none.

이 위젯은 부모로부터 전달되는 ConstraintsconstraintsTransform을 통해 변환해서 넘겨 자식의 크기를 결정한다. 위젯의 크기는 자식 위젯의 크기과 같되 부모의 constraints에 제한된다.

아래는 위젯 클래스의 코드이다.

Dart
class ConstraintsTransformBox extends SingleChildRenderObjectWidget { const ConstraintsTransformBox({ super.key, super.child, this.textDirection, this.alignment = Alignment.center, required this.constraintsTransform, this.clipBehavior = Clip.none, String debugTransformType = '', }) final TextDirection? textDirection; final AlignmentGeometry alignment; final BoxConstraintsTransform constraintsTransform; final Clip clipBehavior; }
flutter basic.dart 2529 라인

props로 clipBehavior와 alignment, textDirection을 받는다. 자식이 부모모다 크면 clip, 작으면 align을 적용한다. 기본적으로 alignment는 center, clipBehavior는 none이다.

 

ConstrainedBox

ConstrainedBox를 쓸일이 있을까 싶지만, 사실 Container도 내부적으로 ConstrainedBox를 쓸만큼 자주 쓰는 위젯이다. 인자로 받는 constraints를 자식에게 추가적으로 전달한다. 즉 부모의 constraints와 인자로 받은 constraints 의 교집합을 자식에게 전해 자식의 크기를 결정한다. 위젯의 크기도 자식의 크기에 맞춘다.

Dart
class ConstrainedBox extends SingleChildRenderObjectWidget { ConstrainedBox({ super.key, required this.constraints, super.child, }) /// The additional constraints to impose on the child. final BoxConstraints constraints; }

 

UnconstrainedBox

A widget that imposes no constraints on its child, allowing it to render at its "natural" size. This allows a child to render at the size it would render if it were alone on an infinite canvas with no constraints. This container will then attempt to adopt the same size, within the limits of its own constraints. If it ends up with a different size, it will align the child based on alignment. If the box cannot expand enough to accommodate the entire child, the child will be clipped.

자식에게 전달하는 constraints를 풀어준다. 인자로 constrainedAxis를 받는다. ConstrainedAxis.vertical이면 horizontal 방향의 contraints를 해제한다. 그 외에도 clipBehavior, alignment 인자를 받는다.

내부적으로 ConstraintsTransform을 쓰고 있다.

Dart
class UnconstrainedBox extends StatelessWidget { const UnconstrainedBox({ super.key, this.child, this.textDirection, this.alignment = Alignment.center, this.constrainedAxis, this.clipBehavior = Clip.none, }) Widget build(BuildContext context) { return ConstraintsTransformBox( textDirection: textDirection, alignment: alignment, clipBehavior: clipBehavior, constraintsTransform: _axisToTransform(constrainedAxis), child: child, ); } BoxConstraintsTransform _axisToTransform(Axis? constrainedAxis) { if (constrainedAxis != null) { switch (constrainedAxis) { case Axis.horizontal: return ConstraintsTransformBox.heightUnconstrained; case Axis.vertical: return ConstraintsTransformBox.widthUnconstrained; } } else { return ConstraintsTransformBox.unconstrained; } } }

 

LimitedBox

LimitedBox는 maxWidth, maxHeight 인자를 통해 상한 constraints를 지정한다.

Dart
class LimitedBox extends SingleChildRenderObjectWidget { const LimitedBox({ super.key, this.maxWidth = double.infinity, this.maxHeight = double.infinity, super.child, }) final double maxWidth; final double maxHeight; }

주의할 점은 ScrollingView와 같은 위젯이 부모로 있어 부모로부터 받은 contraints가 bounded되지 않을 경우만 동작한다는 점이다. 즉 일반적인 경우에 LimitedBox는 쓰나 안쓰나 별다른 차이가 없다.

아래는 ConstrainedBox와 LimitedBox를 비교한 예시이다.

ConstrainedBox(maxWidth: 100)
LimitedBox(maxWidth: 100)

 

SizedBox

SizedBox는 정확한 크기를 지정하고 싶을 때 쓰는 위젯이다. SizedBox를 처음 접했을때 당황했던 부분은 자식위젯의 크기를 아무리 작게 지정해도 부모 SizedBox의 크기만큼 확장하는 동작이었다.

왜 그런 동작이었는지는 코드를 보니 알 수 있었다.

Dart
class SizedBox extends SingleChildRenderObjectWidget { const SizedBox({ super.key, this.width, this.height, super.child }); RenderConstrainedBox createRenderObject(BuildContext context) { return RenderConstrainedBox( additionalConstraints: _additionalConstraints, ); } BoxConstraints get _additionalConstraints { return BoxConstraints.tightFor(width: width, height: height); } }
flutter basic.dart 2338 라인

SizedBox는 내부적으로 RenderConstrainedBox를 활용해 renderObject를 생성하고 있었다. 그런데 이럴거면 차라리 StatelessWidget으로 만들고 ConstrainedBox를 직접 사용하면 어떨까 하는데 일단 flutter에서는 이렇게 구현되어 있었다.

보면 알겠지만 constraints를 tight 하게 만들어 자식에게 전달하기 때문에 자식은 Align 위젯 같은걸로 감싸져 있지 않는다면 SizedBox만큼 확장하게 된다.

 

OverflowBox

A widget that imposes different constraints on its child than it gets from its parent, possibly allowing the child to overflow the parent.

이 위젯도 ConstraintsTransformBox처럼 자식에게 전달하는 constraints를 변형한다. 하지만 ConstraintsTransformBox와는 조금 다르게 동작해서 대체될 순 없다.

Dart
class OverflowBox extends SingleChildRenderObjectWidget { /// Creates a widget that lets its child overflow itself. const OverflowBox({ super.key, this.alignment = Alignment.center, this.minWidth, this.maxWidth, this.minHeight, this.maxHeight, super.child, }); void performLayout() { if (child != null) { child?.layout(_getInnerConstraints(constraints), parentUsesSize: true); alignChild(); } } }

 

OverflowSizedBox

아직 작성 못했습니다 ㅎ..

마치며

이번에는 constraints와 관련된 다양한 위젯들에 대해 알아보았습니다. 언뜻 봤을때는 모두 ConstrainedBoxTransform으로 대체가능할줄 알았는데 세부 동작들을 살펴보니 각각이 별개의 동작을 하고 있었습니다. Flutterjs를 구현할때도 이점을 유의해서 구현했습니다.

특히 alignment를 prop으로 받는 위젯들을 단순히 Container처럼 Align 위젯으로 덮어서 만들라고 했다가 큰일날뻔 했습니다. Align위젯의 RenderObject는 RenderAligningShiftedBox가 아니라 그걸 상속받은 RenderPositionedBox 더라구요. 동작은 자식 위젯을 정렬하는 것 이외에 부모의 constraints를 loosen() 하는 역할도 수행하고 있었습니다.

이런 차이를 알게되니 Container에서 alignment의 유무로 자식 위젯이 팽창하거나 안하거나 하는 동작의 이유를 알게되었습니다. 이부분은 Container 를 알아볼때 더 자세하게 이야기 하도록 하겠습니다 😊

 

참고자료