aspectfit

Flutter BoxFit: contain, cover, fill and the rest

BoxFit.contain is aspect fit, BoxFit.cover is aspect fill and BoxFit.fill stretches. If you leave fit out, an Image uses scaleDown, which shrinks large images and never enlarges small ones.

The seven values

BoxFitScaleResult
fillwidth and height separatelyFills the box, distorts the ratio.
containmin(boxW / imgW, boxH / imgH)As large as possible while showing the whole image.
covermax(boxW / imgW, boxH / imgH)As small as possible while covering the whole box.
fitWidthboxW / imgWFull width shown; vertically cropped or with space.
fitHeightboxH / imgHFull height shown; horizontally cropped or with space.
none1Natural size, aligned, the rest discarded.
scaleDownmin(1, contain scale)Like contain if that shrinks the image, otherwise like none.
fit / contain
3003003002005050
fill / cover
300300450300
stretch / fill
300300300300
contain, cover and fill: a 1200 × 800 image in a 300 × 300 box.
fit width
300300300450−75−75
fit height
3003002003005050
fitWidth and fitHeight with a 600 × 900 (portrait) image: fitWidth crops 75 from top and bottom, fitHeight leaves 50 on each side.
Dart
SizedBox(
  width: double.infinity,
  height: 220,
  child: Image.asset('assets/harbor.jpg', fit: BoxFit.cover),
)

Why the default is scaleDown

Image.fit is nullable, and its documentation points to paintImage, which applies fit ??= centerSlice == null ? BoxFit.scaleDown : BoxFit.fill. So an Image without fit shrinks a large picture to fit but leaves a small one at its natural size, centered. That is why a small asset inside a large box looks "not scaled": pass fit: BoxFit.contain to enlarge it. DecorationImage follows the same rule. FittedBox is different: its fit defaults to BoxFit.contain.

BoxFit needs a box

An Image sizes itself to the image when its parent allows any size, and then every fit looks the same. Give it tight constraints: a SizedBox, width and height on the Image itself, an Expanded, or an AspectRatio:

Dart
AspectRatio(
  aspectRatio: 16 / 9,
  child: Image.network(photoUrl, fit: BoxFit.cover),
)

cover in Image vs FittedBox

For an Image, Flutter works out which part of the source is visible (the "source size" from applyBoxFit) and paints only that part into the box, so cover never spills out. FittedBox scales a child widget instead, and its clipBehavior defaults to Clip.none; with BoxFit.cover the child is drawn beyond the box unless you clip:

Dart
FittedBox(
  fit: BoxFit.cover,
  clipBehavior: Clip.hardEdge,   // without this the overflow is painted
  child: SizedBox(width: 1200, height: 800, child: MapPreview()),
)

For rounded corners on an image, wrap it in ClipRRect(borderRadius: BorderRadius.circular(12), child: …).

Alignment, and how it maps to an anchor

alignment defaults to Alignment.center. Flutter's Alignment(x, y) runs from −1 to 1, so the offset of the drawn image is:

Formula
offsetX = (boxW - drawnW) * (x + 1) / 2
offsetY = (boxH - drawnH) * (y + 1) / 2

Alignment.topCenter is (0, −1): with cover it keeps the top of the image and crops from the bottom. This is the same as an anchor of 0.5, 0 in the visualizer, or object-position: center top in CSS.

Dart
Image.asset(
  'assets/portrait.jpg',
  fit: BoxFit.cover,
  alignment: Alignment.topCenter,
  width: double.infinity,
  height: 200,
)

Background images with DecorationImage

A BoxDecoration can paint an image behind a container's child, with the same fit and alignment:

Dart
Container(
  height: 180,
  decoration: const BoxDecoration(
    image: DecorationImage(
      image: AssetImage('assets/harbor.jpg'),
      fit: BoxFit.cover,
    ),
  ),
  child: const Align(
    alignment: Alignment.bottomLeft,
    child: Padding(padding: EdgeInsets.all(16), child: Text('Harbor')),
  ),
)

applyBoxFit for custom painting

In a CustomPainter, applyBoxFit gives you the two sizes Flutter uses internally: the part of the source to read and the size to draw it at. Alignment.inscribe places a size inside a rectangle:

Dart
void paintFitted(Canvas canvas, Rect box, ui.Image image) {
  final imageSize = Size(image.width.toDouble(), image.height.toDouble());
  final fitted = applyBoxFit(BoxFit.cover, imageSize, box.size);
  final src = Alignment.center.inscribe(fitted.source, Offset.zero & imageSize);
  final dst = Alignment.center.inscribe(fitted.destination, box);
  canvas.drawImageRect(image, src, dst, Paint());
}

For cover the destination is the whole box and the source is the visible part of the image, which is the "visible source" figure the visualizer reports.

Logical pixels and asset variants

none and scaleDown depend on the image's natural size in logical pixels. With resolution-aware assets (2.0x/, 3.0x/ folders) Flutter divides the pixel size by the variant's scale, so a 1200-pixel-wide file in 3.0x/ has a natural width of 400. The other fits only use the ratio.

The same seven behaviors in other toolkits are compared on the home page; for Android's version see scaleType and ContentScale.

Questions

What is the difference between BoxFit.contain and BoxFit.cover?

contain scales by the smaller of the two ratios so the whole image is visible inside the box. cover scales by the larger ratio so the box is completely covered and the image's overflow is not shown.

What BoxFit does Image use by default?

If fit is null, Image paints with BoxFit.scaleDown (or BoxFit.fill when centerSlice is set). FittedBox defaults to BoxFit.contain.

Why does BoxFit.cover overflow in a FittedBox?

FittedBox does not clip by default (clipBehavior is Clip.none). Set clipBehavior: Clip.hardEdge. An Image with fit: BoxFit.cover does not need this because it only paints the visible part of the source.