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