cozy logo Moon
thumbnail thumbnail
profile
  • 개발

플러터 위젯 파헤치기 - 4: Align, FractionallySizedBox, Center

2023년 04월 27일 좋아요 0 댓글 0 조회수 240

플러터 위젯 파헤칠 목록

Row, Column, Flexible, Expanded, Spacer

Align, Center, FractionallySizedBox

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

 

들어가며

안녕하세요~ 자바스크립트 라이브러리 Flutterjs를 만들기 위해 오늘도 위젯들을 분석해 보았습니다. 오늘 살펴볼 위젯은 Align, FractionallySizedBox 그리고 Center인데요. Align 과 FractionallySizedBox를 같이 살펴보는게 뜬금없다고 생각할 수 있지만 여기에는 기가막힌 공통점이 있습니다.

그럼 지금부터 살펴보도록 하겠습니다!

Align

A widget that aligns its child within itself and optionally sizes itself based on the child's size. For example, to align a box at the bottom right, you would pass this box a tight constraint that is bigger than the child's natural size, with an alignment of Alignment.bottomRight This widget will be as big as possible if its dimensions are constrained and widthFactor  and heightFactor  are null. If a dimension is unconstrained and the corresponding size factor is null then the widget will match its child's size in that dimension. If a size factor is non-null then the corresponding dimension of this widget will be the product of the child's dimension and the size factor. For example if widthFactor is 2.0 then the width of this widget will always be twice its child's width.

Align 위젯은 크게 alignment, widthFactor, heightFactor를 인자로 받아 자신의 크기 및 자식 위젯의 위치를 결정합니다.

Align 위젯의 크기를 결정하는 조건은 꽤나 까다로운데요. 조건은 크게 3가지로 구분할 수 있습니다.

  • 1

    widthFactor가 있는 경우는 자식 위젯 width * widthFactor로 width 축소 (heightFactor도 마찬가지)

  • 2

    부모의 uncontraints가 unbounded인 경우, 자식 위젯의 크기와 일치

  • 3

    그 외, 확장할 수 있을 만큼 확장

  • 자식 위젯의 크기가 필요할때, 자식 위젯이 없는 경우는 자식의 크기를 0으로 간주합니다.

    Align 위젯의 RenderObject 코드를 보면, 다음과 같습니다.

    Dart
    class RenderPositionedBox extends RenderAligningShiftedBox { RenderPositionedBox({ super.child, double? widthFactor, double? heightFactor, super.alignment, super.textDirection, }) double? _widthFactor double? _heightFactor Alignment alignment (...중략) void performLayout() { final BoxConstraints constraints = this.constraints; final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity; final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity; if (child != null) { child!.layout(constraints.loosen(), parentUsesSize: true); size = constraints.constrain(Size( shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity, shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity, )); alignChild(); } else { size = constraints.constrain(Size( shrinkWrapWidth ? 0.0 : double.infinity, shrinkWrapHeight ? 0.0 : double.infinity, )); } } }
    flutter shifted_box.dart 386 라인

    performLayout 을 보면 bool shrinkWrap** 조건을 통해 자신의 사이즈를 결정하는 것을 볼 수 있습니다.

    위 코드를 해석하면 widthFactor와 heightFactor가 없고, 부모의 constraints의 maxWidth, maxHeight이 finite 하다면 위젯의 크기는 constraints.biggest로 정해짐을 알 수 있습니다.

    (constraints.constrain(Size(double.infinity, double.infinity))constraints.biggest와 같다.)

    Align 위젯은 별도의 InstrinsicWidth(or height) 오버라이딩을 하지 않습니다. 즉 RenderBox를 상속받은 RenderPositionedBox는 InstrinsicSize가 곧 자식 위젯의 InstrinsicSize와 일치합니다.

    Align 위젯은 다른 위젯과 다르게 한가지 특이한 점이 있는데요. 바로 child.layout에 부모의 constraints를 넘겨 줄 때, constraints.loosen() 을 적용해서 전달하는 부분입니다.

    이를 이용하면, 부모 위젯에 의해 constraints가 tight하게 정해져도 자식 위젯의 크기를 다시 재조정 할 수 있습니다.

    posting img
    부모의 SizedBox가 Align 위젯을 화면 꽉차게 확장시키지만, 자식 위젯은 다시 크기를 설정할 수 있다.

     

    FractionallySizedBox

    A widget that sizes its child to a fraction of the total available space.

    FractionallySizedBoxs는 가용 공간에 대해 비율을 지정하여 자신의 크기를 결정합니다. 위젯이 받는 인자 다음과 같습니다.

    Dart
    class FractionallySizedBox extends SingleChildRenderObjectWidget { const FractionallySizedBox({ super.key, this.alignment = Alignment.center, this.widthFactor, this.heightFactor, super.child, }) final double? widthFactor; final double? heightFactor; final AlignmentGeometry alignment; }

    눈치채셨나요?? FractionallySizedBox는 공교롭게도 Align 위젯과 동일한 인자를 받고 있습니다.

    FractionallySizedBox의 widthFactor는 자식 위젯의 크기가 아니라 가용 공간 즉, 부모의 constraints.maxWidth에 적용된다는 차이가 있습니다.

    posting img
    전체 화면의 0.5배 만큼 줄어준 컨테이너 위젯

    동작을 자세하게 분석하기 전에 코드 먼저 살펴보겠습니다.

    Dart
    class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox { (...) BoxConstraints _getInnerConstraints(BoxConstraints constraints) { double minWidth = constraints.minWidth; double maxWidth = constraints.maxWidth; if (_widthFactor != null) { final double width = maxWidth * _widthFactor!; minWidth = width; maxWidth = width; } double minHeight = constraints.minHeight; double maxHeight = constraints.maxHeight; if (_heightFactor != null) { final double height = maxHeight * _heightFactor!; minHeight = height; maxHeight = height; } return BoxConstraints( minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight, ); } void performLayout() { if (child != null) { child!.layout(_getInnerConstraints(constraints), parentUsesSize: true); size = constraints.constrain(child!.size); alignChild(); } else { size = constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero)); } } (...) }
    flutter shifted_box.dart 1052 라인

    먼저 특이한 점은, 자식 위젯에 전하는 constraints를 FractionallySizedBox에서 한번 가공한다는 것입니다.

    getInnerConstraints 코드를 해석하면, widthFactor나 heightFactor가 없는 경우는 부모의 constraints를 그대로 활용하지만, 있는 경우는, constraints.max** 에 각 fraction을 곱해 constraints를 tight 하게 만듭니다. 자식 위젯은 FractionSizedBox에 의해 크기가 정해지게 되는 것이지요.

    주의해야 할 부분이 있습니다. widthFactor, heightFactor는 자기 자신의 크기가 아닌, 자식 위젯의 크기를 결정하는 요소입니다.

    Dart
    (...) child!.layout(_getInnerConstraints(constraints), parentUsesSize: true); size = constraints.constrain(child!.size); (...)

    performLayout의 코드를 보면 위와 같이 구현되어 있습니다. 자식 위젯에 widthFactor(or hieghtFactor)로 정해진 tight한 constraints를 전달하여 크기를 결정하는 모습을 볼 수 있습니다.

    단 이렇게 정한 자식의 크기를 곧 자신의 크기로 사용하진 않습니다. 부모의 constraints를 준수하는 한에서 자식의 크기를 사용하지요. (자식이 없는 경우는 자식의 사이즈를 0으로 간주합니다)

    즉 widthFactor가 본 위젯의 크기를 결정한다고 단정할 수 없습니다.

    아래 예시를 통해 좀 더 살펴겠습니다.

    posting img

    Flexible로 가용 공간만큼 확장되어야 하는데, heightFactor로 인해 수축된 모습을 볼 수 있습니다.

    그러나 Flexible에 fit을 tight하게 설정하면 FractionallySizedBox는 hieghtFactor만큼 수축하지 않습니다.

    posting img
    하염없이 확장되어 버린 FractionallySizedBox… heightFactor가 무색하다

    FractionallySizedBox는 항상 부모의 constraints를 준수한다는걸 생각해야 합니다. 부모의 constraints.minHeight이 constraints.maxHeight만큼 크기 때문에 이를 전달받은 FractionallSizedBox는 확장될 수밖에 없습니다.

    그러나 자식 위젯은 확실히 heightFactor에 의해 크기가 결정됩니다.

    posting img

    빨간색 박스를 보면 알 수 있듯, FractionallySizedBox의 heightFactor에 의해 높이가 결정되었습니다.

    FractionallySizedBox는 자식 위젯과 크기가 같지 않을 수 있네요. 그래서 alignment도 추가로 받나 봅니다.

    posting img
    alignment가 topCenter인 모습

    마치며

    ..가 아니지

     

    Center

    Dart
    class Center extends Align { /// Creates a widget that centers its child. const Center({ super.key, super.widthFactor, super.heightFactor, super.child }); }

    Center 위젯은 사실 Align 위젯에 alignment: Alignment.center 를 부여 한 것입니다. Align의 디폴트값이 Alignment.center이니 Align 위젯에 아무런 alignment를 설정하지 않는 것과 동일하지요.

    진짜 마치며..

    이번에는 Align, FractionallySizedBox, Center 위젯에 대해 알아보았습니다. 사실 FractionaalySizedBox의 intrinsicHeight, instrinsicWidth 부분도 살펴보려고 했는데 현재 창업 준비로 바빠.. 그 내용까진 미처 담지 못했네요 ㅠㅠ

    어쨌거나 이번 탐구로 저의 Flutterjs 위젯들이 더 풍부해졌네요~

    posting img

    참고자료

    관심 가질만한 포스트