How to force an image to draw outside of its boundaries in Jetpack Compose

Akbolat Sadvakassov
3 min readJan 19, 2023

--

Taken from here https://unsplash.com/photos/bGtETSIqIhk

Imagine a case when you need to put an image as in the picture below.

Looks clean, isn't?

And not like this:

I've found three ways to achieve it. But only one suited me because of the limited time.

  1. The first is manually draw the image bitmap inside Canvas using drawImage composable. But it requires an extra time. I did it, but my math calculations were okay only for my phone screen size, not universal. So I throw it away.
@Composable
private fun ImageWithoutTopBounds(@DrawableRes id: Int) {
val bitmap = ImageBitmap.imageResource(id = id)

Canvas(
modifier = Modifier
.fillMaxSize()
) {
val canvasWidth = size.width.toInt()
val canvasHeight = size.height.toInt()

val imageHeight = bitmap.height
val imageWidth = bitmap.width

// magic calculations

drawImage(
image = bitmap,
srcOffset = IntOffset(),
dstOffset = IntOffset(),
srcSize = IntSize(),
dstSize = IntSize(),
)
}
}

2. Another complex to math way is to use Image with custom ContentScale and offset. I failed this one too :(

@Composable
private fun ImageWithoutTopBounds(@DrawableRes id: Int) {
val painter = painterResource(id = id)
val calculatedYOffset = ...// another magic
val calculatedXYScale = ...

Image(
modifier = Modifier
.fillMaxSize()
.offset(y = calculatedYOffset),
painter = painter,
contentScale = object : ContentScale {
override fun computeScaleFactor(srcSize: Size, dstSize: Size): ScaleFactor =
ScaleFactor(calculatedXYScale.x, calculatedXYScale.y)
}
)
}

3. And the last solution which suited me 100% is to use things out of the box - wrapContentXXX with unbound set to true. Quote from docs:

Allow the content to measure at its desired size without regard for the incoming maximum constraints

While second parameter alignment will anchor it, Top/Bottom for wrapContentHeight and Start/End for wrapContentWidth or mix of both when its wrapContentSize.

@Composable
private fun ImageWithoutTopBounds(@DrawableRes id: Int) {
val painter = painterResource(id = id)
Image(
modifier = Modifier
.wrapContentSize(align = Alignment.BottomCenter, unbounded = true) // here it is
.align(Alignment.Center),
painter = painter,
contentScale = FixedScale(1f) // if your image too big
)
}

Here some other examples:

.wrapContentWidth(align = Alignment.Start, unbounded = true)
.wrapContentHeight(align = Alignment.Bottom, unbounded = true)
.wrapContentWidth(align = Alignment.End, unbounded = true)
.wrapContentHeight(align = Alignment.Top, unbounded = true)
.wrapContentSize(align = Alignment.TopCenter, unbounded = true)

Cheers :)

--

--