- 개발
플러터 위젯 파헤치기 - 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가지로 구분할 수 있습니다.
widthFactor가 있는 경우는 자식 위젯 width * widthFactor로 width 축소 (heightFactor도 마찬가지)
부모의 uncontraints가 unbounded인 경우, 자식 위젯의 크기와 일치
그 외, 확장할 수 있을 만큼 확장
자식 위젯의 크기가 필요할때, 자식 위젯이 없는 경우는 자식의 크기를 0으로 간주합니다.
Align 위젯의 RenderObject
코드를 보면, 다음과 같습니다.
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,
));
}
}
}
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하게 정해져도 자식 위젯의 크기를 다시 재조정 할 수 있습니다.
FractionallySizedBox
A widget that sizes its child to a fraction of the total available space.
FractionallySizedBoxs는 가용 공간에 대해 비율을 지정하여 자신의 크기를 결정합니다. 위젯이 받는 인자 다음과 같습니다.
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에 적용된다는 차이가 있습니다.
동작을 자세하게 분석하기 전에 코드 먼저 살펴보겠습니다.
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));
}
}
(...)
}
먼저 특이한 점은, 자식 위젯에 전하는 constraints를 FractionallySizedBox에서 한번 가공한다는 것입니다.
getInnerConstraints
코드를 해석하면, widthFactor나 heightFactor가 없는 경우는 부모의 constraints를 그대로 활용하지만, 있는 경우는, constraints.max** 에 각 fraction을 곱해 constraints를 tight
하게 만듭니다. 자식 위젯
은 FractionSizedBox에 의해 크기가 정해지게 되는 것이지요.
주의해야 할 부분이 있습니다. widthFactor, heightFactor는 자기 자신의 크기가 아닌
, 자식 위젯의 크기
를 결정하는 요소입니다.
(...)
child!.layout(_getInnerConstraints(constraints), parentUsesSize: true);
size = constraints.constrain(child!.size);
(...)
performLayout
의 코드를 보면 위와 같이 구현되어 있습니다. 자식 위젯에 widthFactor(or hieghtFactor)로 정해진 tight한 constraints를 전달하여 크기를 결정하는 모습을 볼 수 있습니다.
단 이렇게 정한 자식의 크기를 곧 자신의 크기로 사용하진 않습니다. 부모의 constraints를 준수하는 한에서 자식의 크기를 사용하지요. (자식이 없는 경우는 자식의 사이즈를 0으로 간주합니다)
즉 widthFactor가 본 위젯의 크기를 결정한다고 단정할 수 없습니다.
아래 예시를 통해 좀 더 살펴겠습니다.
Flexible로 가용 공간만큼 확장되어야 하는데, heightFactor로 인해 수축된 모습을 볼 수 있습니다.
그러나 Flexible에 fit을 tight하게 설정하면 FractionallySizedBox는 hieghtFactor만큼 수축하지 않습니다.
FractionallySizedBox는 항상 부모의 constraints를 준수한다는걸 생각해야 합니다. 부모의 constraints.minHeight이 constraints.maxHeight만큼 크기 때문에 이를 전달받은 FractionallSizedBox는 확장될 수밖에 없습니다.
그러나 자식 위젯은 확실히 heightFactor에 의해 크기가 결정됩니다.
빨간색 박스를 보면 알 수 있듯, FractionallySizedBox의 heightFactor에 의해 높이가 결정되었습니다.
FractionallySizedBox는 자식 위젯과 크기가 같지 않을 수 있네요. 그래서 alignment도 추가로 받나 봅니다.
마치며
..가 아니지
Center
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 위젯들이 더 풍부해졌네요~
[Flutterjs]: https://637c944f61fbd5154eb3e034-xffoezwcie.chromatic.com/?path=/docs/widget-fractionallysizedbox--docs