In Flutter, how does AspectRatio choose its size from its constraints, and why can a 16:9 AspectRatio used as a GridView tile do nothing?
answer
- width first, height derived
- falls back to maxHeight if width infinite
- tight constraints win
- grid tiles are tight
- both unbounded is an error
basics
~20 sAspectRatio takes the largest width allowed, derives the height from the ratio, then adjusts to fit the constraints. Under tight constraints, such as a GridView tile, it must take the forced size and the ratio is ignored.
solid answer
~50 s`AspectRatio` computes a size in one pass, without laying the child out twice. If its constraints are **tight**, it simply takes `constraints.smallest` — there is no freedom, so the ratio is ignored. Otherwise it starts from `maxWidth` and sets `height = width / aspectRatio`; if `maxWidth` is infinite it starts from `maxHeight` instead. It then clamps against the max and min constraints, re-deriving the other side each time, and gives up on the ratio only when the constraints allow no matching size. A `GridView` tile gets tight constraints from its delegate, so an `AspectRatio` placed directly as the tile does nothing; the delegate's `childAspectRatio` (default 1.0) or `mainAxisExtent` sets the tile's shape. Inside the tile, wrapped in a `Column`, the widget works, because the `Column` gives it unbounded height. With both axes unbounded, it throws a debug error.
code
dart · 23 linesimport 'package:flutter/material.dart';
class LectureTile extends StatelessWidget {
const LectureTile({super.key, required this.title, required this.thumbnail});
final String title;
final ImageProvider thumbnail;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
AspectRatio(
aspectRatio: 16 / 9,
child: Image(image: thumbnail, fit: BoxFit.cover),
),
const SizedBox(height: 8),
Text(title, maxLines: 2, overflow: TextOverflow.ellipsis),
],
);
}
}go deeper
Know that AspectRatio(aspectRatio: 16 / 9) sizes a child to that ratio and that the value is width divided by height.
Walk through the width-first algorithm, the infinite-width fallback, the tight-constraints shortcut and the both-unbounded error.
Diagnose ratio bugs from the constraints: tight grid tiles, FittedBox's unbounded child, min constraints that override the ratio.
Encourage media components that own their aspect ratio internally so feature screens do not fight grid and list constraints.
## What AspectRatio is for `AspectRatio` sizes its child to a **width-to-height ratio**: `16 / 9` for a video thumbnail, `1.0` for a square avatar. The ratio is a `double` that must be positive and finite. It is a normal single-child layout widget: it receives constraints from its parent, chooses a size and hands that size to its child as **tight** constraints. ## The algorithm The render object, `RenderAspectRatio`, runs a short fixed sequence — no extra layout pass: 1. If the constraints are **tight**, return `constraints.smallest`. The parent has already decided the size. 2. Start with `width = maxWidth`. If it is finite, `height = width / aspectRatio`. 3. If `maxWidth` is infinite, start from the height instead: `height = maxHeight`, `width = height * aspectRatio`. 4. If `width > maxWidth`, clamp the width and re-derive the height. 5. If `height > maxHeight`, clamp the height and re-derive the width. 6. Apply the same steps for `minWidth` and `minHeight`. 7. Finally `constraints.constrain(...)` the result. If no size satisfies both the ratio and the constraints, the constraints win. In debug mode, if **both** width and height are unbounded, it throws a `FlutterError` saying it has unbounded constraints and does not know how much space to take. | Incoming constraints | Result for `16 / 9` | |---|---| | tight 300 x 300 | 300 x 300; the ratio is ignored | | width 0..320, height unbounded | 320 x 180 | | width unbounded, height 0..90 | 160 x 90 | | width 0..320, height 0..90 | 160 x 90, since 180 exceeds the max height | | both unbounded | debug error | ## Why it "does nothing" in a grid In a lecture-archive app you might render 16:9 video thumbnails in a `GridView`. Each tile is laid out with **tight** constraints computed by the grid delegate: the cross-axis extent from the column count, and the main-axis extent from `childAspectRatio`, which defaults to **1.0**, or from `mainAxisExtent`. Wrap a tile in `AspectRatio(aspectRatio: 16 / 9)` and step 1 fires: the tile is square anyway. Two fixes: - Set the tile shape on the delegate, `childAspectRatio: 16 / 9`, when the tile is only the thumbnail. - Make the tile a `Column` with the thumbnail in an `AspectRatio` and the title below. A `Column` gives its children unbounded height, so step 2 applies: full width, height derived. The tile height from the delegate must then leave room for the title. ## Other places it surprises people - **Inside `FittedBox`**, the child is laid out with fully unbounded constraints, so `AspectRatio` hits the both-unbounded case. The docs suggest a `SizedBox` of explicit size instead; the `FittedBox` then scales it. - **Inside a `Row`** without `Expanded`, width is unbounded. It then uses the height; if height is also unbounded, it fails. - **With a `ConstrainedBox` above it**, the min constraints can force a size that breaks the ratio, and step 6 accepts that. ## Interview summary Say "largest width, derived height, clamp, and tight constraints win", then use the grid tile as the example of tight constraints. That shows you know the ratio is a preference inside the constraint system, not an override of it.
- What size does AspectRatio(aspectRatio: 2) choose when its constraints are width 0 to 100 and height 0 to 100?100 by 50. It starts from the maximum width, 100, and derives a height of 50, which fits inside the height limit, so nothing needs clamping.
- What happens with AspectRatio(aspectRatio: 0.5) under the same 100 by 100 maximum?It starts at width 100, derives height 200, which exceeds the maximum height, so it clamps height to 100 and re-derives width as 50. The result is 50 by 100, still at the requested ratio.
saying these in an interview costs you the question
- AspectRatio always enforces its ratio, whatever the constraints.
- AspectRatio lays its child out twice to find the right size.
- Wrapping a GridView tile in AspectRatio changes the tile's shape.
- AspectRatio derives width from height first by default.
- AspectRatio works fine when both width and height are unbounded.