Android scaleType and Compose ContentScale
An ImageView uses fitCenter unless you say otherwise: aspect fit, centered. centerCrop is aspect fill, fitXY stretches. In Jetpack Compose the same choices are ContentScale.Fit, Crop and FillBounds.
The eight ImageView.ScaleType values
All measurements are inside the view's padding. The drawable's intrinsic size is in pixels after Android has applied its density scaling.
| XML value | Enum | Keeps ratio | What it does |
|---|---|---|---|
fitCenter | FIT_CENTER | yes | Aspect fit, centered. The default. |
fitStart | FIT_START | yes | Aspect fit, aligned to the left and top edges. |
fitEnd | FIT_END | yes | Aspect fit, aligned to the right and bottom edges. |
centerCrop | CENTER_CROP | yes | Aspect fill: both sides equal to or larger than the view, centered, overflow cropped. |
centerInside | CENTER_INSIDE | yes | Aspect fit, but never enlarges: a small image stays at its own size. |
center | CENTER | yes | No scaling, centered; cropped if larger than the view. |
fitXY | FIT_XY | no | Stretches to exactly the view's size. |
matrix | MATRIX | you decide | Draws with the matrix from setImageMatrix(). With the identity matrix: natural size, top-left. |
The formulas behind them: fitCenter scales by min(viewW / imgW, viewH / imgH), centerCrop by max(…), and centerInside by min(1, viewW / imgW, viewH / imgH). For center, centerCrop and centerInside, ImageView rounds the offset to whole pixels, so when the leftover space is an odd number of pixels, one side gets the extra pixel.
<ImageView
android:id="@+id/cover"
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/harbor"
android:contentDescription="@string/harbor_description"
android:scaleType="centerCrop" />In code: imageView.scaleType = ImageView.ScaleType.CENTER_CROP.
adjustViewBounds, and the trap in it
android:adjustViewBounds="true" lets the ImageView resize itself to the drawable's aspect ratio. It is the usual way to show an image full-width at its natural shape: fixed width, wrap_content height, and the view becomes exactly as tall as the image needs, with no empty bands.
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="@drawable/harbor"
android:contentDescription="@string/harbor_description" />Three details are worth knowing:
- It changes the scale type when set from code.
setAdjustViewBounds(true)also callssetScaleType(ScaleType.FIT_CENTER)(see the AOSP source ofImageView). If you set a scale type first and then turn on adjustViewBounds in code, your scale type is gone. In XML thescaleTypeattribute is applied afteradjustViewBounds, so it wins there. android:maxWidthandandroid:maxHeightonly take effect together with adjustViewBounds.- For apps targeting API level 17 or lower, adjustViewBounds can shrink the view to the drawable but does not always grow it to fill the available space.
Padding and cropToPadding
The scale type works on the area inside the padding. With centerCrop the overflow can still be drawn into the padding area, because by default the drawing is only clipped at the view's outer edge. Set android:cropToPadding="true" to clip at the padding instead.
A custom crop with MATRIX
centerCrop always crops equally from both sides. For a header where the top of a portrait must stay visible, compute the same scale yourself and choose where the leftover goes:
fun ImageView.cropAnchored(anchorX: Float = 0.5f, anchorY: Float = 0f) {
scaleType = ImageView.ScaleType.MATRIX
doOnLayout {
val d = drawable ?: return@doOnLayout
val vw = (width - paddingLeft - paddingRight).toFloat()
val vh = (height - paddingTop - paddingBottom).toFloat()
val s = maxOf(vw / d.intrinsicWidth, vh / d.intrinsicHeight) // aspect fill
imageMatrix = Matrix().apply {
setScale(s, s)
postTranslate((vw - d.intrinsicWidth * s) * anchorX,
(vh - d.intrinsicHeight * s) * anchorY)
}
}
}
// keep the top, crop only from the bottom
headerImage.cropAnchored(anchorY = 0f)doOnLayout is in androidx.core (core-ktx). Use minOf instead of maxOf for an anchored aspect fit. Call it again whenever the drawable changes.
Jetpack Compose: ContentScale
In Compose, Image takes a contentScale parameter whose default is ContentScale.Fit, and an alignment whose default is Alignment.Center.
| ContentScale | Behavior | Closest ImageView value |
|---|---|---|
Fit | Aspect fit (the default) | fitCenter |
Crop | Aspect fill, overflow cropped | centerCrop |
FillBounds | Stretch, ratio ignored | fitXY |
Inside | Fit if larger, natural size if smaller | centerInside |
None | Natural size | center |
FillWidth | Scale so the width matches; height may crop or leave space | needs a matrix |
FillHeight | Scale so the height matches; width may crop or leave space | needs a matrix |
Image(
painter = painterResource(R.drawable.harbor),
contentDescription = stringResource(R.string.harbor_description),
modifier = Modifier
.fillMaxWidth()
.aspectRatio(16f / 9f),
contentScale = ContentScale.Crop,
alignment = Alignment.TopCenter, // anchor the crop, no matrix needed
)Two differences from ImageView make Compose easier: Image clips to its bounds itself, and alignment anchors any scale mode, including Crop. Modifier.aspectRatio(ratio) gives the layout box a ratio, much like CSS aspect-ratio. Image loaders such as Coil's AsyncImage take the same contentScale parameter.
A note on density and "natural size"
center, centerInside, ContentScale.None and Inside depend on the drawable's intrinsic size, which Android scales by density bucket: a 300-pixel bitmap in drawable-mdpi is drawn 900 pixels wide on an xxhdpi screen. Put images in drawable-nodpi if they must not be scaled, or use the fitting modes, which do not care about the intrinsic size beyond its ratio.
The visualizer shows each mode with your own numbers and writes the XML or Compose code; for the same modes in iOS terms see aspect fit vs aspect fill.
Questions
What is the default scaleType of an Android ImageView?
fitCenter (ScaleType.FIT_CENTER): the image is scaled to fit inside the view with its aspect ratio kept, and centered.
What is the difference between centerCrop and fitCenter?
fitCenter scales by the smaller ratio so the whole image is visible, possibly with empty space. centerCrop scales by the larger ratio so the view is covered and the overflow is cropped equally from both sides.
Why did my scaleType change to fitCenter?
Calling setAdjustViewBounds(true) in code also sets the scale type to FIT_CENTER. Set the scale type after enabling adjustViewBounds, or set both in XML, where scaleType is applied last.
Want your own numbers? The visualizer draws any box and image, shows each step of the math and writes the code for six toolkits.