1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:math' as math;
6
7import 'package:flutter/foundation.dart';
8
9import 'box.dart';
10import 'debug_overflow_indicator.dart';
11import 'layer.dart';
12import 'layout_helper.dart';
13import 'object.dart';
14
15/// How the child is inscribed into the available space.
16///
17/// See also:
18///
19/// * [RenderFlex], the flex render object.
20/// * [Column], [Row], and [Flex], the flex widgets.
21/// * [Expanded], the widget equivalent of [tight].
22/// * [Flexible], the widget equivalent of [loose].
23enum FlexFit {
24 /// The child is forced to fill the available space.
25 ///
26 /// The [Expanded] widget assigns this kind of [FlexFit] to its child.
27 tight,
28
29 /// The child can be at most as large as the available space (but is
30 /// allowed to be smaller).
31 ///
32 /// The [Flexible] widget assigns this kind of [FlexFit] to its child.
33 loose,
34}
35
36/// Parent data for use with [RenderFlex].
37class FlexParentData extends ContainerBoxParentData<RenderBox> {
38 /// The flex factor to use for this child.
39 ///
40 /// If null or zero, the child is inflexible and determines its own size. If
41 /// non-zero, the amount of space the child's can occupy in the main axis is
42 /// determined by dividing the free space (after placing the inflexible
43 /// children) according to the flex factors of the flexible children.
44 int? flex;
45
46 /// How a flexible child is inscribed into the available space.
47 ///
48 /// If [flex] is non-zero, the [fit] determines whether the child fills the
49 /// space the parent makes available during layout. If the fit is
50 /// [FlexFit.tight], the child is required to fill the available space. If the
51 /// fit is [FlexFit.loose], the child can be at most as large as the available
52 /// space (but is allowed to be smaller).
53 FlexFit? fit;
54
55 @override
56 String toString() => '${super.toString()}; flex=$flex; fit=$fit';
57}
58
59/// How much space should be occupied in the main axis.
60///
61/// During a flex layout, available space along the main axis is allocated to
62/// children. After allocating space, there might be some remaining free space.
63/// This value controls whether to maximize or minimize the amount of free
64/// space, subject to the incoming layout constraints.
65///
66/// See also:
67///
68/// * [Column], [Row], and [Flex], the flex widgets.
69/// * [Expanded] and [Flexible], the widgets that controls a flex widgets'
70/// children's flex.
71/// * [RenderFlex], the flex render object.
72/// * [MainAxisAlignment], which controls how the free space is distributed.
73enum MainAxisSize {
74 /// Minimize the amount of free space along the main axis, subject to the
75 /// incoming layout constraints.
76 ///
77 /// If the incoming layout constraints have a large enough
78 /// [BoxConstraints.minWidth] or [BoxConstraints.minHeight], there might still
79 /// be a non-zero amount of free space.
80 ///
81 /// If the incoming layout constraints are unbounded, and any children have a
82 /// non-zero [FlexParentData.flex] and a [FlexFit.tight] fit (as applied by
83 /// [Expanded]), the [RenderFlex] will assert, because there would be infinite
84 /// remaining free space and boxes cannot be given infinite size.
85 min,
86
87 /// Maximize the amount of free space along the main axis, subject to the
88 /// incoming layout constraints.
89 ///
90 /// If the incoming layout constraints have a small enough
91 /// [BoxConstraints.maxWidth] or [BoxConstraints.maxHeight], there might still
92 /// be no free space.
93 ///
94 /// If the incoming layout constraints are unbounded, the [RenderFlex] will
95 /// assert, because there would be infinite remaining free space and boxes
96 /// cannot be given infinite size.
97 max,
98}
99
100/// How the children should be placed along the main axis in a flex layout.
101///
102/// See also:
103///
104/// * [Column], [Row], and [Flex], the flex widgets.
105/// * [RenderFlex], the flex render object.
106enum MainAxisAlignment {
107 /// Place the children as close to the start of the main axis as possible.
108 ///
109 /// If this value is used in a horizontal direction, a [TextDirection] must be
110 /// available to determine if the start is the left or the right.
111 ///
112 /// If this value is used in a vertical direction, a [VerticalDirection] must be
113 /// available to determine if the start is the top or the bottom.
114 start,
115
116 /// Place the children as close to the end of the main axis as possible.
117 ///
118 /// If this value is used in a horizontal direction, a [TextDirection] must be
119 /// available to determine if the end is the left or the right.
120 ///
121 /// If this value is used in a vertical direction, a [VerticalDirection] must be
122 /// available to determine if the end is the top or the bottom.
123 end,
124
125 /// Place the children as close to the middle of the main axis as possible.
126 center,
127
128 /// Place the free space evenly between the children.
129 spaceBetween,
130
131 /// Place the free space evenly between the children as well as half of that
132 /// space before and after the first and last child.
133 spaceAround,
134
135 /// Place the free space evenly between the children as well as before and
136 /// after the first and last child.
137 spaceEvenly,
138}
139
140/// How the children should be placed along the cross axis in a flex layout.
141///
142/// See also:
143///
144/// * [Column], [Row], and [Flex], the flex widgets.
145/// * [RenderFlex], the flex render object.
146enum CrossAxisAlignment {
147 /// Place the children with their start edge aligned with the start side of
148 /// the cross axis.
149 ///
150 /// For example, in a column (a flex with a vertical axis) whose
151 /// [TextDirection] is [TextDirection.ltr], this aligns the left edge of the
152 /// children along the left edge of the column.
153 ///
154 /// If this value is used in a horizontal direction, a [TextDirection] must be
155 /// available to determine if the start is the left or the right.
156 ///
157 /// If this value is used in a vertical direction, a [VerticalDirection] must be
158 /// available to determine if the start is the top or the bottom.
159 start,
160
161 /// Place the children as close to the end of the cross axis as possible.
162 ///
163 /// For example, in a column (a flex with a vertical axis) whose
164 /// [TextDirection] is [TextDirection.ltr], this aligns the right edge of the
165 /// children along the right edge of the column.
166 ///
167 /// If this value is used in a horizontal direction, a [TextDirection] must be
168 /// available to determine if the end is the left or the right.
169 ///
170 /// If this value is used in a vertical direction, a [VerticalDirection] must be
171 /// available to determine if the end is the top or the bottom.
172 end,
173
174 /// Place the children so that their centers align with the middle of the
175 /// cross axis.
176 ///
177 /// This is the default cross-axis alignment.
178 center,
179
180 /// Require the children to fill the cross axis.
181 ///
182 /// This causes the constraints passed to the children to be tight in the
183 /// cross axis.
184 stretch,
185
186 /// Place the children along the cross axis such that their baselines match.
187 ///
188 /// Because baselines are always horizontal, this alignment is intended for
189 /// horizontal main axes. If the main axis is vertical, then this value is
190 /// treated like [start].
191 ///
192 /// For horizontal main axes, if the minimum height constraint passed to the
193 /// flex layout exceeds the intrinsic height of the cross axis, children will
194 /// be aligned as close to the top as they can be while honoring the baseline
195 /// alignment. In other words, the extra space will be below all the children.
196 ///
197 /// Children who report no baseline will be top-aligned.
198 baseline,
199}
200
201bool? _startIsTopLeft(Axis direction, TextDirection? textDirection, VerticalDirection? verticalDirection) {
202 // If the relevant value of textDirection or verticalDirection is null, this returns null too.
203 switch (direction) {
204 case Axis.horizontal:
205 switch (textDirection) {
206 case TextDirection.ltr:
207 return true;
208 case TextDirection.rtl:
209 return false;
210 case null:
211 return null;
212 }
213 case Axis.vertical:
214 switch (verticalDirection) {
215 case VerticalDirection.down:
216 return true;
217 case VerticalDirection.up:
218 return false;
219 case null:
220 return null;
221 }
222 }
223}
224
225typedef _ChildSizingFunction = double Function(RenderBox child, double extent);
226
227/// Displays its children in a one-dimensional array.
228///
229/// ## Layout algorithm
230///
231/// _This section describes how the framework causes [RenderFlex] to position
232/// its children._
233/// _See [BoxConstraints] for an introduction to box layout models._
234///
235/// Layout for a [RenderFlex] proceeds in six steps:
236///
237/// 1. Layout each child with a null or zero flex factor with unbounded main
238/// axis constraints and the incoming cross axis constraints. If the
239/// [crossAxisAlignment] is [CrossAxisAlignment.stretch], instead use tight
240/// cross axis constraints that match the incoming max extent in the cross
241/// axis.
242/// 2. Divide the remaining main axis space among the children with non-zero
243/// flex factors according to their flex factor. For example, a child with a
244/// flex factor of 2.0 will receive twice the amount of main axis space as a
245/// child with a flex factor of 1.0.
246/// 3. Layout each of the remaining children with the same cross axis
247/// constraints as in step 1, but instead of using unbounded main axis
248/// constraints, use max axis constraints based on the amount of space
249/// allocated in step 2. Children with [Flexible.fit] properties that are
250/// [FlexFit.tight] are given tight constraints (i.e., forced to fill the
251/// allocated space), and children with [Flexible.fit] properties that are
252/// [FlexFit.loose] are given loose constraints (i.e., not forced to fill the
253/// allocated space).
254/// 4. The cross axis extent of the [RenderFlex] is the maximum cross axis
255/// extent of the children (which will always satisfy the incoming
256/// constraints).
257/// 5. The main axis extent of the [RenderFlex] is determined by the
258/// [mainAxisSize] property. If the [mainAxisSize] property is
259/// [MainAxisSize.max], then the main axis extent of the [RenderFlex] is the
260/// max extent of the incoming main axis constraints. If the [mainAxisSize]
261/// property is [MainAxisSize.min], then the main axis extent of the [Flex]
262/// is the sum of the main axis extents of the children (subject to the
263/// incoming constraints).
264/// 6. Determine the position for each child according to the
265/// [mainAxisAlignment] and the [crossAxisAlignment]. For example, if the
266/// [mainAxisAlignment] is [MainAxisAlignment.spaceBetween], any main axis
267/// space that has not been allocated to children is divided evenly and
268/// placed between the children.
269///
270/// See also:
271///
272/// * [Flex], the widget equivalent.
273/// * [Row] and [Column], direction-specific variants of [Flex].
274class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>,
275 RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>,
276 DebugOverflowIndicatorMixin {
277 /// Creates a flex render object.
278 ///
279 /// By default, the flex layout is horizontal and children are aligned to the
280 /// start of the main axis and the center of the cross axis.
281 RenderFlex({
282 List<RenderBox>? children,
283 Axis direction = Axis.horizontal,
284 MainAxisSize mainAxisSize = MainAxisSize.max,
285 MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
286 CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
287 TextDirection? textDirection,
288 VerticalDirection verticalDirection = VerticalDirection.down,
289 TextBaseline? textBaseline,
290 Clip clipBehavior = Clip.none,
291 }) : _direction = direction,
292 _mainAxisAlignment = mainAxisAlignment,
293 _mainAxisSize = mainAxisSize,
294 _crossAxisAlignment = crossAxisAlignment,
295 _textDirection = textDirection,
296 _verticalDirection = verticalDirection,
297 _textBaseline = textBaseline,
298 _clipBehavior = clipBehavior {
299 addAll(children);
300 }
301
302 /// The direction to use as the main axis.
303 Axis get direction => _direction;
304 Axis _direction;
305 set direction(Axis value) {
306 if (_direction != value) {
307 _direction = value;
308 markNeedsLayout();
309 }
310 }
311
312 /// How the children should be placed along the main axis.
313 ///
314 /// If the [direction] is [Axis.horizontal], and the [mainAxisAlignment] is
315 /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the
316 /// [textDirection] must not be null.
317 ///
318 /// If the [direction] is [Axis.vertical], and the [mainAxisAlignment] is
319 /// either [MainAxisAlignment.start] or [MainAxisAlignment.end], then the
320 /// [verticalDirection] must not be null.
321 MainAxisAlignment get mainAxisAlignment => _mainAxisAlignment;
322 MainAxisAlignment _mainAxisAlignment;
323 set mainAxisAlignment(MainAxisAlignment value) {
324 if (_mainAxisAlignment != value) {
325 _mainAxisAlignment = value;
326 markNeedsLayout();
327 }
328 }
329
330 /// How much space should be occupied in the main axis.
331 ///
332 /// After allocating space to children, there might be some remaining free
333 /// space. This value controls whether to maximize or minimize the amount of
334 /// free space, subject to the incoming layout constraints.
335 ///
336 /// If some children have a non-zero flex factors (and none have a fit of
337 /// [FlexFit.loose]), they will expand to consume all the available space and
338 /// there will be no remaining free space to maximize or minimize, making this
339 /// value irrelevant to the final layout.
340 MainAxisSize get mainAxisSize => _mainAxisSize;
341 MainAxisSize _mainAxisSize;
342 set mainAxisSize(MainAxisSize value) {
343 if (_mainAxisSize != value) {
344 _mainAxisSize = value;
345 markNeedsLayout();
346 }
347 }
348
349 /// How the children should be placed along the cross axis.
350 ///
351 /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
352 /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
353 /// [verticalDirection] must not be null.
354 ///
355 /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
356 /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
357 /// [textDirection] must not be null.
358 CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment;
359 CrossAxisAlignment _crossAxisAlignment;
360 set crossAxisAlignment(CrossAxisAlignment value) {
361 if (_crossAxisAlignment != value) {
362 _crossAxisAlignment = value;
363 markNeedsLayout();
364 }
365 }
366
367 /// Determines the order to lay children out horizontally and how to interpret
368 /// `start` and `end` in the horizontal direction.
369 ///
370 /// If the [direction] is [Axis.horizontal], this controls the order in which
371 /// children are positioned (left-to-right or right-to-left), and the meaning
372 /// of the [mainAxisAlignment] property's [MainAxisAlignment.start] and
373 /// [MainAxisAlignment.end] values.
374 ///
375 /// If the [direction] is [Axis.horizontal], and either the
376 /// [mainAxisAlignment] is either [MainAxisAlignment.start] or
377 /// [MainAxisAlignment.end], or there's more than one child, then the
378 /// [textDirection] must not be null.
379 ///
380 /// If the [direction] is [Axis.vertical], this controls the meaning of the
381 /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
382 /// [CrossAxisAlignment.end] values.
383 ///
384 /// If the [direction] is [Axis.vertical], and the [crossAxisAlignment] is
385 /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
386 /// [textDirection] must not be null.
387 TextDirection? get textDirection => _textDirection;
388 TextDirection? _textDirection;
389 set textDirection(TextDirection? value) {
390 if (_textDirection != value) {
391 _textDirection = value;
392 markNeedsLayout();
393 }
394 }
395
396 /// Determines the order to lay children out vertically and how to interpret
397 /// `start` and `end` in the vertical direction.
398 ///
399 /// If the [direction] is [Axis.vertical], this controls which order children
400 /// are painted in (down or up), the meaning of the [mainAxisAlignment]
401 /// property's [MainAxisAlignment.start] and [MainAxisAlignment.end] values.
402 ///
403 /// If the [direction] is [Axis.vertical], and either the [mainAxisAlignment]
404 /// is either [MainAxisAlignment.start] or [MainAxisAlignment.end], or there's
405 /// more than one child, then the [verticalDirection] must not be null.
406 ///
407 /// If the [direction] is [Axis.horizontal], this controls the meaning of the
408 /// [crossAxisAlignment] property's [CrossAxisAlignment.start] and
409 /// [CrossAxisAlignment.end] values.
410 ///
411 /// If the [direction] is [Axis.horizontal], and the [crossAxisAlignment] is
412 /// either [CrossAxisAlignment.start] or [CrossAxisAlignment.end], then the
413 /// [verticalDirection] must not be null.
414 VerticalDirection get verticalDirection => _verticalDirection;
415 VerticalDirection _verticalDirection;
416 set verticalDirection(VerticalDirection value) {
417 if (_verticalDirection != value) {
418 _verticalDirection = value;
419 markNeedsLayout();
420 }
421 }
422
423 /// If aligning items according to their baseline, which baseline to use.
424 ///
425 /// Must not be null if [crossAxisAlignment] is [CrossAxisAlignment.baseline].
426 TextBaseline? get textBaseline => _textBaseline;
427 TextBaseline? _textBaseline;
428 set textBaseline(TextBaseline? value) {
429 assert(_crossAxisAlignment != CrossAxisAlignment.baseline || value != null);
430 if (_textBaseline != value) {
431 _textBaseline = value;
432 markNeedsLayout();
433 }
434 }
435
436 bool get _debugHasNecessaryDirections {
437 if (firstChild != null && lastChild != firstChild) {
438 // i.e. there's more than one child
439 switch (direction) {
440 case Axis.horizontal:
441 assert(textDirection != null, 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.');
442 case Axis.vertical:
443 break;
444 }
445 }
446 if (mainAxisAlignment == MainAxisAlignment.start ||
447 mainAxisAlignment == MainAxisAlignment.end) {
448 switch (direction) {
449 case Axis.horizontal:
450 assert(textDirection != null, 'Horizontal $runtimeType with $mainAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
451 case Axis.vertical:
452 break;
453 }
454 }
455 if (crossAxisAlignment == CrossAxisAlignment.start ||
456 crossAxisAlignment == CrossAxisAlignment.end) {
457 switch (direction) {
458 case Axis.horizontal:
459 break;
460 case Axis.vertical:
461 assert(textDirection != null, 'Vertical $runtimeType with $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
462 }
463 }
464 return true;
465 }
466
467 // Set during layout if overflow occurred on the main axis.
468 double _overflow = 0;
469 // Check whether any meaningful overflow is present. Values below an epsilon
470 // are treated as not overflowing.
471 bool get _hasOverflow => _overflow > precisionErrorTolerance;
472
473 /// {@macro flutter.material.Material.clipBehavior}
474 ///
475 /// Defaults to [Clip.none].
476 Clip get clipBehavior => _clipBehavior;
477 Clip _clipBehavior = Clip.none;
478 set clipBehavior(Clip value) {
479 if (value != _clipBehavior) {
480 _clipBehavior = value;
481 markNeedsPaint();
482 markNeedsSemanticsUpdate();
483 }
484 }
485
486 @override
487 void setupParentData(RenderBox child) {
488 if (child.parentData is! FlexParentData) {
489 child.parentData = FlexParentData();
490 }
491 }
492
493 bool get _canComputeIntrinsics => crossAxisAlignment != CrossAxisAlignment.baseline;
494
495 double _getIntrinsicSize({
496 required Axis sizingDirection,
497 required double extent, // the extent in the direction that isn't the sizing direction
498 required _ChildSizingFunction childSize, // a method to find the size in the sizing direction
499 }) {
500 if (!_canComputeIntrinsics) {
501 // Intrinsics cannot be calculated without a full layout for
502 // baseline alignment. Throw an assertion and return 0.0 as documented
503 // on [RenderBox.computeMinIntrinsicWidth].
504 assert(
505 RenderObject.debugCheckingIntrinsics,
506 'Intrinsics are not available for CrossAxisAlignment.baseline.',
507 );
508 return 0.0;
509 }
510 if (_direction == sizingDirection) {
511 // INTRINSIC MAIN SIZE
512 // Intrinsic main size is the smallest size the flex container can take
513 // while maintaining the min/max-content contributions of its flex items.
514 double totalFlex = 0.0;
515 double inflexibleSpace = 0.0;
516 double maxFlexFractionSoFar = 0.0;
517 RenderBox? child = firstChild;
518 while (child != null) {
519 final int flex = _getFlex(child);
520 totalFlex += flex;
521 if (flex > 0) {
522 final double flexFraction = childSize(child, extent) / _getFlex(child);
523 maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction);
524 } else {
525 inflexibleSpace += childSize(child, extent);
526 }
527 final FlexParentData childParentData = child.parentData! as FlexParentData;
528 child = childParentData.nextSibling;
529 }
530 return maxFlexFractionSoFar * totalFlex + inflexibleSpace;
531 } else {
532 // INTRINSIC CROSS SIZE
533 // Intrinsic cross size is the max of the intrinsic cross sizes of the
534 // children, after the flexible children are fit into the available space,
535 // with the children sized using their max intrinsic dimensions.
536
537 // Get inflexible space using the max intrinsic dimensions of fixed children in the main direction.
538 final double availableMainSpace = extent;
539 int totalFlex = 0;
540 double inflexibleSpace = 0.0;
541 double maxCrossSize = 0.0;
542 RenderBox? child = firstChild;
543 while (child != null) {
544 final int flex = _getFlex(child);
545 totalFlex += flex;
546 late final double mainSize;
547 late final double crossSize;
548 if (flex == 0) {
549 switch (_direction) {
550 case Axis.horizontal:
551 mainSize = child.getMaxIntrinsicWidth(double.infinity);
552 crossSize = childSize(child, mainSize);
553 case Axis.vertical:
554 mainSize = child.getMaxIntrinsicHeight(double.infinity);
555 crossSize = childSize(child, mainSize);
556 }
557 inflexibleSpace += mainSize;
558 maxCrossSize = math.max(maxCrossSize, crossSize);
559 }
560 final FlexParentData childParentData = child.parentData! as FlexParentData;
561 child = childParentData.nextSibling;
562 }
563
564 // Determine the spacePerFlex by allocating the remaining available space.
565 // When you're overconstrained spacePerFlex can be negative.
566 final double spacePerFlex = math.max(0.0, (availableMainSpace - inflexibleSpace) / totalFlex);
567
568 // Size remaining (flexible) items, find the maximum cross size.
569 child = firstChild;
570 while (child != null) {
571 final int flex = _getFlex(child);
572 if (flex > 0) {
573 maxCrossSize = math.max(maxCrossSize, childSize(child, spacePerFlex * flex));
574 }
575 final FlexParentData childParentData = child.parentData! as FlexParentData;
576 child = childParentData.nextSibling;
577 }
578
579 return maxCrossSize;
580 }
581 }
582
583 @override
584 double computeMinIntrinsicWidth(double height) {
585 return _getIntrinsicSize(
586 sizingDirection: Axis.horizontal,
587 extent: height,
588 childSize: (RenderBox child, double extent) => child.getMinIntrinsicWidth(extent),
589 );
590 }
591
592 @override
593 double computeMaxIntrinsicWidth(double height) {
594 return _getIntrinsicSize(
595 sizingDirection: Axis.horizontal,
596 extent: height,
597 childSize: (RenderBox child, double extent) => child.getMaxIntrinsicWidth(extent),
598 );
599 }
600
601 @override
602 double computeMinIntrinsicHeight(double width) {
603 return _getIntrinsicSize(
604 sizingDirection: Axis.vertical,
605 extent: width,
606 childSize: (RenderBox child, double extent) => child.getMinIntrinsicHeight(extent),
607 );
608 }
609
610 @override
611 double computeMaxIntrinsicHeight(double width) {
612 return _getIntrinsicSize(
613 sizingDirection: Axis.vertical,
614 extent: width,
615 childSize: (RenderBox child, double extent) => child.getMaxIntrinsicHeight(extent),
616 );
617 }
618
619 @override
620 double? computeDistanceToActualBaseline(TextBaseline baseline) {
621 if (_direction == Axis.horizontal) {
622 return defaultComputeDistanceToHighestActualBaseline(baseline);
623 }
624 return defaultComputeDistanceToFirstActualBaseline(baseline);
625 }
626
627 int _getFlex(RenderBox child) {
628 final FlexParentData childParentData = child.parentData! as FlexParentData;
629 return childParentData.flex ?? 0;
630 }
631
632 FlexFit _getFit(RenderBox child) {
633 final FlexParentData childParentData = child.parentData! as FlexParentData;
634 return childParentData.fit ?? FlexFit.tight;
635 }
636
637 double _getCrossSize(Size size) {
638 switch (_direction) {
639 case Axis.horizontal:
640 return size.height;
641 case Axis.vertical:
642 return size.width;
643 }
644 }
645
646 double _getMainSize(Size size) {
647 switch (_direction) {
648 case Axis.horizontal:
649 return size.width;
650 case Axis.vertical:
651 return size.height;
652 }
653 }
654
655 @override
656 @protected
657 Size computeDryLayout(covariant BoxConstraints constraints) {
658 if (!_canComputeIntrinsics) {
659 assert(debugCannotComputeDryLayout(
660 reason: 'Dry layout cannot be computed for CrossAxisAlignment.baseline, which requires a full layout.',
661 ));
662 return Size.zero;
663 }
664 FlutterError? constraintsError;
665 assert(() {
666 constraintsError = _debugCheckConstraints(
667 constraints: constraints,
668 reportParentConstraints: false,
669 );
670 return true;
671 }());
672 if (constraintsError != null) {
673 assert(debugCannotComputeDryLayout(error: constraintsError));
674 return Size.zero;
675 }
676
677 final _LayoutSizes sizes = _computeSizes(
678 layoutChild: ChildLayoutHelper.dryLayoutChild,
679 constraints: constraints,
680 );
681
682 switch (_direction) {
683 case Axis.horizontal:
684 return constraints.constrain(Size(sizes.mainSize, sizes.crossSize));
685 case Axis.vertical:
686 return constraints.constrain(Size(sizes.crossSize, sizes.mainSize));
687 }
688 }
689
690 FlutterError? _debugCheckConstraints({required BoxConstraints constraints, required bool reportParentConstraints}) {
691 FlutterError? result;
692 assert(() {
693 final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight;
694 final bool canFlex = maxMainSize < double.infinity;
695 RenderBox? child = firstChild;
696 while (child != null) {
697 final int flex = _getFlex(child);
698 if (flex > 0) {
699 final String identity = _direction == Axis.horizontal ? 'row' : 'column';
700 final String axis = _direction == Axis.horizontal ? 'horizontal' : 'vertical';
701 final String dimension = _direction == Axis.horizontal ? 'width' : 'height';
702 DiagnosticsNode error, message;
703 final List<DiagnosticsNode> addendum = <DiagnosticsNode>[];
704 if (!canFlex && (mainAxisSize == MainAxisSize.max || _getFit(child) == FlexFit.tight)) {
705 error = ErrorSummary('RenderFlex children have non-zero flex but incoming $dimension constraints are unbounded.');
706 message = ErrorDescription(
707 'When a $identity is in a parent that does not provide a finite $dimension constraint, for example '
708 'if it is in a $axis scrollable, it will try to shrink-wrap its children along the $axis '
709 'axis. Setting a flex on a child (e.g. using Expanded) indicates that the child is to '
710 'expand to fill the remaining space in the $axis direction.',
711 );
712 if (reportParentConstraints) { // Constraints of parents are unavailable in dry layout.
713 RenderBox? node = this;
714 switch (_direction) {
715 case Axis.horizontal:
716 while (!node!.constraints.hasBoundedWidth && node.parent is RenderBox) {
717 node = node.parent! as RenderBox;
718 }
719 if (!node.constraints.hasBoundedWidth) {
720 node = null;
721 }
722 case Axis.vertical:
723 while (!node!.constraints.hasBoundedHeight && node.parent is RenderBox) {
724 node = node.parent! as RenderBox;
725 }
726 if (!node.constraints.hasBoundedHeight) {
727 node = null;
728 }
729 }
730 if (node != null) {
731 addendum.add(node.describeForError('The nearest ancestor providing an unbounded width constraint is'));
732 }
733 }
734 addendum.add(ErrorHint('See also: https://flutter.dev/unbounded-constraints'));
735 } else {
736 return true;
737 }
738 result = FlutterError.fromParts(<DiagnosticsNode>[
739 error,
740 message,
741 ErrorDescription(
742 'These two directives are mutually exclusive. If a parent is to shrink-wrap its child, the child '
743 'cannot simultaneously expand to fit its parent.',
744 ),
745 ErrorHint(
746 'Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose fits for the flexible '
747 'children (using Flexible rather than Expanded). This will allow the flexible children '
748 'to size themselves to less than the infinite remaining space they would otherwise be '
749 'forced to take, and then will cause the RenderFlex to shrink-wrap the children '
750 'rather than expanding to fit the maximum constraints provided by the parent.',
751 ),
752 ErrorDescription(
753 'If this message did not help you determine the problem, consider using debugDumpRenderTree():\n'
754 ' https://flutter.dev/debugging/#rendering-layer\n'
755 ' http://api.flutter.dev/flutter/rendering/debugDumpRenderTree.html',
756 ),
757 describeForError('The affected RenderFlex is', style: DiagnosticsTreeStyle.errorProperty),
758 DiagnosticsProperty<dynamic>('The creator information is set to', debugCreator, style: DiagnosticsTreeStyle.errorProperty),
759 ...addendum,
760 ErrorDescription(
761 "If none of the above helps enough to fix this problem, please don't hesitate to file a bug:\n"
762 ' https://github.com/flutter/flutter/issues/new?template=2_bug.yml',
763 ),
764 ]);
765 return true;
766 }
767 child = childAfter(child);
768 }
769 return true;
770 }());
771 return result;
772 }
773
774 _LayoutSizes _computeSizes({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
775 assert(_debugHasNecessaryDirections);
776
777 // Determine used flex factor, size inflexible items, calculate free space.
778 int totalFlex = 0;
779 final double maxMainSize = _direction == Axis.horizontal ? constraints.maxWidth : constraints.maxHeight;
780 final bool canFlex = maxMainSize < double.infinity;
781
782 double crossSize = 0.0;
783 double allocatedSize = 0.0; // Sum of the sizes of the non-flexible children.
784 RenderBox? child = firstChild;
785 RenderBox? lastFlexChild;
786 while (child != null) {
787 final FlexParentData childParentData = child.parentData! as FlexParentData;
788 final int flex = _getFlex(child);
789 if (flex > 0) {
790 totalFlex += flex;
791 lastFlexChild = child;
792 } else {
793 final BoxConstraints innerConstraints;
794 if (crossAxisAlignment == CrossAxisAlignment.stretch) {
795 switch (_direction) {
796 case Axis.horizontal:
797 innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
798 case Axis.vertical:
799 innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
800 }
801 } else {
802 switch (_direction) {
803 case Axis.horizontal:
804 innerConstraints = BoxConstraints(maxHeight: constraints.maxHeight);
805 case Axis.vertical:
806 innerConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
807 }
808 }
809 final Size childSize = layoutChild(child, innerConstraints);
810 allocatedSize += _getMainSize(childSize);
811 crossSize = math.max(crossSize, _getCrossSize(childSize));
812 }
813 assert(child.parentData == childParentData);
814 child = childParentData.nextSibling;
815 }
816
817 // Distribute free space to flexible children.
818 final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
819 double allocatedFlexSpace = 0.0;
820 if (totalFlex > 0) {
821 final double spacePerFlex = canFlex ? (freeSpace / totalFlex) : double.nan;
822 child = firstChild;
823 while (child != null) {
824 final int flex = _getFlex(child);
825 if (flex > 0) {
826 final double maxChildExtent = canFlex ? (child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex) : double.infinity;
827 late final double minChildExtent;
828 switch (_getFit(child)) {
829 case FlexFit.tight:
830 assert(maxChildExtent < double.infinity);
831 minChildExtent = maxChildExtent;
832 case FlexFit.loose:
833 minChildExtent = 0.0;
834 }
835 final BoxConstraints innerConstraints;
836 if (crossAxisAlignment == CrossAxisAlignment.stretch) {
837 switch (_direction) {
838 case Axis.horizontal:
839 innerConstraints = BoxConstraints(
840 minWidth: minChildExtent,
841 maxWidth: maxChildExtent,
842 minHeight: constraints.maxHeight,
843 maxHeight: constraints.maxHeight,
844 );
845 case Axis.vertical:
846 innerConstraints = BoxConstraints(
847 minWidth: constraints.maxWidth,
848 maxWidth: constraints.maxWidth,
849 minHeight: minChildExtent,
850 maxHeight: maxChildExtent,
851 );
852 }
853 } else {
854 switch (_direction) {
855 case Axis.horizontal:
856 innerConstraints = BoxConstraints(
857 minWidth: minChildExtent,
858 maxWidth: maxChildExtent,
859 maxHeight: constraints.maxHeight,
860 );
861 case Axis.vertical:
862 innerConstraints = BoxConstraints(
863 maxWidth: constraints.maxWidth,
864 minHeight: minChildExtent,
865 maxHeight: maxChildExtent,
866 );
867 }
868 }
869 final Size childSize = layoutChild(child, innerConstraints);
870 final double childMainSize = _getMainSize(childSize);
871 assert(childMainSize <= maxChildExtent);
872 allocatedSize += childMainSize;
873 allocatedFlexSpace += maxChildExtent;
874 crossSize = math.max(crossSize, _getCrossSize(childSize));
875 }
876 final FlexParentData childParentData = child.parentData! as FlexParentData;
877 child = childParentData.nextSibling;
878 }
879 }
880
881 final double idealSize = canFlex && mainAxisSize == MainAxisSize.max ? maxMainSize : allocatedSize;
882 return _LayoutSizes(
883 mainSize: idealSize,
884 crossSize: crossSize,
885 allocatedSize: allocatedSize,
886 );
887 }
888
889 @override
890 void performLayout() {
891 assert(_debugHasNecessaryDirections);
892 final BoxConstraints constraints = this.constraints;
893 assert(() {
894 final FlutterError? constraintsError = _debugCheckConstraints(
895 constraints: constraints,
896 reportParentConstraints: true,
897 );
898 if (constraintsError != null) {
899 throw constraintsError;
900 }
901 return true;
902 }());
903
904 final _LayoutSizes sizes = _computeSizes(
905 layoutChild: ChildLayoutHelper.layoutChild,
906 constraints: constraints,
907 );
908
909 final double allocatedSize = sizes.allocatedSize;
910 double actualSize = sizes.mainSize;
911 double crossSize = sizes.crossSize;
912 double maxBaselineDistance = 0.0;
913 if (crossAxisAlignment == CrossAxisAlignment.baseline) {
914 RenderBox? child = firstChild;
915 double maxSizeAboveBaseline = 0;
916 double maxSizeBelowBaseline = 0;
917 while (child != null) {
918 assert(() {
919 if (textBaseline == null) {
920 throw FlutterError('To use CrossAxisAlignment.baseline, you must also specify which baseline to use using the "textBaseline" argument.');
921 }
922 return true;
923 }());
924 final double? distance = child.getDistanceToBaseline(textBaseline!, onlyReal: true);
925 if (distance != null) {
926 maxBaselineDistance = math.max(maxBaselineDistance, distance);
927 maxSizeAboveBaseline = math.max(
928 distance,
929 maxSizeAboveBaseline,
930 );
931 maxSizeBelowBaseline = math.max(
932 child.size.height - distance,
933 maxSizeBelowBaseline,
934 );
935 crossSize = math.max(maxSizeAboveBaseline + maxSizeBelowBaseline, crossSize);
936 }
937 final FlexParentData childParentData = child.parentData! as FlexParentData;
938 child = childParentData.nextSibling;
939 }
940 }
941
942 // Align items along the main axis.
943 switch (_direction) {
944 case Axis.horizontal:
945 size = constraints.constrain(Size(actualSize, crossSize));
946 actualSize = size.width;
947 crossSize = size.height;
948 case Axis.vertical:
949 size = constraints.constrain(Size(crossSize, actualSize));
950 actualSize = size.height;
951 crossSize = size.width;
952 }
953 final double actualSizeDelta = actualSize - allocatedSize;
954 _overflow = math.max(0.0, -actualSizeDelta);
955 final double remainingSpace = math.max(0.0, actualSizeDelta);
956 late final double leadingSpace;
957 late final double betweenSpace;
958 // flipMainAxis is used to decide whether to lay out
959 // left-to-right/top-to-bottom (false), or right-to-left/bottom-to-top
960 // (true). The _startIsTopLeft will return null if there's only one child
961 // and the relevant direction is null, in which case we arbitrarily decide
962 // to flip, but that doesn't have any detectable effect.
963 final bool flipMainAxis = !(_startIsTopLeft(direction, textDirection, verticalDirection) ?? true);
964 switch (_mainAxisAlignment) {
965 case MainAxisAlignment.start:
966 leadingSpace = 0.0;
967 betweenSpace = 0.0;
968 case MainAxisAlignment.end:
969 leadingSpace = remainingSpace;
970 betweenSpace = 0.0;
971 case MainAxisAlignment.center:
972 leadingSpace = remainingSpace / 2.0;
973 betweenSpace = 0.0;
974 case MainAxisAlignment.spaceBetween:
975 leadingSpace = 0.0;
976 betweenSpace = childCount > 1 ? remainingSpace / (childCount - 1) : 0.0;
977 case MainAxisAlignment.spaceAround:
978 betweenSpace = childCount > 0 ? remainingSpace / childCount : 0.0;
979 leadingSpace = betweenSpace / 2.0;
980 case MainAxisAlignment.spaceEvenly:
981 betweenSpace = childCount > 0 ? remainingSpace / (childCount + 1) : 0.0;
982 leadingSpace = betweenSpace;
983 }
984
985 // Position elements
986 double childMainPosition = flipMainAxis ? actualSize - leadingSpace : leadingSpace;
987 RenderBox? child = firstChild;
988 while (child != null) {
989 final FlexParentData childParentData = child.parentData! as FlexParentData;
990 final double childCrossPosition;
991 switch (_crossAxisAlignment) {
992 case CrossAxisAlignment.start:
993 case CrossAxisAlignment.end:
994 childCrossPosition = _startIsTopLeft(flipAxis(direction), textDirection, verticalDirection)
995 == (_crossAxisAlignment == CrossAxisAlignment.start)
996 ? 0.0
997 : crossSize - _getCrossSize(child.size);
998 case CrossAxisAlignment.center:
999 childCrossPosition = crossSize / 2.0 - _getCrossSize(child.size) / 2.0;
1000 case CrossAxisAlignment.stretch:
1001 childCrossPosition = 0.0;
1002 case CrossAxisAlignment.baseline:
1003 if (_direction == Axis.horizontal) {
1004 assert(textBaseline != null);
1005 final double? distance = child.getDistanceToBaseline(textBaseline!, onlyReal: true);
1006 if (distance != null) {
1007 childCrossPosition = maxBaselineDistance - distance;
1008 } else {
1009 childCrossPosition = 0.0;
1010 }
1011 } else {
1012 childCrossPosition = 0.0;
1013 }
1014 }
1015 if (flipMainAxis) {
1016 childMainPosition -= _getMainSize(child.size);
1017 }
1018 switch (_direction) {
1019 case Axis.horizontal:
1020 childParentData.offset = Offset(childMainPosition, childCrossPosition);
1021 case Axis.vertical:
1022 childParentData.offset = Offset(childCrossPosition, childMainPosition);
1023 }
1024 if (flipMainAxis) {
1025 childMainPosition -= betweenSpace;
1026 } else {
1027 childMainPosition += _getMainSize(child.size) + betweenSpace;
1028 }
1029 child = childParentData.nextSibling;
1030 }
1031 }
1032
1033 @override
1034 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
1035 return defaultHitTestChildren(result, position: position);
1036 }
1037
1038 @override
1039 void paint(PaintingContext context, Offset offset) {
1040 if (!_hasOverflow) {
1041 defaultPaint(context, offset);
1042 return;
1043 }
1044
1045 // There's no point in drawing the children if we're empty.
1046 if (size.isEmpty) {
1047 return;
1048 }
1049
1050 _clipRectLayer.layer = context.pushClipRect(
1051 needsCompositing,
1052 offset,
1053 Offset.zero & size,
1054 defaultPaint,
1055 clipBehavior: clipBehavior,
1056 oldLayer: _clipRectLayer.layer,
1057 );
1058
1059 assert(() {
1060 final List<DiagnosticsNode> debugOverflowHints = <DiagnosticsNode>[
1061 ErrorDescription(
1062 'The overflowing $runtimeType has an orientation of $_direction.',
1063 ),
1064 ErrorDescription(
1065 'The edge of the $runtimeType that is overflowing has been marked '
1066 'in the rendering with a yellow and black striped pattern. This is '
1067 'usually caused by the contents being too big for the $runtimeType.',
1068 ),
1069 ErrorHint(
1070 'Consider applying a flex factor (e.g. using an Expanded widget) to '
1071 'force the children of the $runtimeType to fit within the available '
1072 'space instead of being sized to their natural size.',
1073 ),
1074 ErrorHint(
1075 'This is considered an error condition because it indicates that there '
1076 'is content that cannot be seen. If the content is legitimately bigger '
1077 'than the available space, consider clipping it with a ClipRect widget '
1078 'before putting it in the flex, or using a scrollable container rather '
1079 'than a Flex, like a ListView.',
1080 ),
1081 ];
1082
1083 // Simulate a child rect that overflows by the right amount. This child
1084 // rect is never used for drawing, just for determining the overflow
1085 // location and amount.
1086 final Rect overflowChildRect;
1087 switch (_direction) {
1088 case Axis.horizontal:
1089 overflowChildRect = Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0);
1090 case Axis.vertical:
1091 overflowChildRect = Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow);
1092 }
1093 paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints);
1094 return true;
1095 }());
1096 }
1097
1098 final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>();
1099
1100 @override
1101 void dispose() {
1102 _clipRectLayer.layer = null;
1103 super.dispose();
1104 }
1105
1106 @override
1107 Rect? describeApproximatePaintClip(RenderObject child) {
1108 switch (clipBehavior) {
1109 case Clip.none:
1110 return null;
1111 case Clip.hardEdge:
1112 case Clip.antiAlias:
1113 case Clip.antiAliasWithSaveLayer:
1114 return _hasOverflow ? Offset.zero & size : null;
1115 }
1116 }
1117
1118
1119 @override
1120 String toStringShort() {
1121 String header = super.toStringShort();
1122 if (!kReleaseMode) {
1123 if (_hasOverflow) {
1124 header += ' OVERFLOWING';
1125 }
1126 }
1127 return header;
1128 }
1129
1130 @override
1131 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1132 super.debugFillProperties(properties);
1133 properties.add(EnumProperty<Axis>('direction', direction));
1134 properties.add(EnumProperty<MainAxisAlignment>('mainAxisAlignment', mainAxisAlignment));
1135 properties.add(EnumProperty<MainAxisSize>('mainAxisSize', mainAxisSize));
1136 properties.add(EnumProperty<CrossAxisAlignment>('crossAxisAlignment', crossAxisAlignment));
1137 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
1138 properties.add(EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: null));
1139 properties.add(EnumProperty<TextBaseline>('textBaseline', textBaseline, defaultValue: null));
1140 }
1141}
1142
1143class _LayoutSizes {
1144 const _LayoutSizes({
1145 required this.mainSize,
1146 required this.crossSize,
1147 required this.allocatedSize,
1148 });
1149
1150 final double mainSize;
1151 final double crossSize;
1152 final double allocatedSize;
1153}
1154