daniyelp
daniyelp

Reputation: 1265

Jetpack Compose - Make Image scale to the available size in Column/Row

Consider the following image:

enter image description here

I have a Column that has an Image together with another Column that has some Text elements. What I'm trying to do is to have the Image scale uniformly to the available space. I just can't make it work.

Column(
    modifier = Modifier
        .fillMaxSize()
        .background(Color.Cyan)
        .padding(horizontal = 16.dp),
    verticalArrangement = Arrangement.SpaceEvenly
) {
    Image(
        modifier = Modifier.fillMaxWidth(),
        painter = painterResource(R.drawable.rocket_boy),
        contentDescription = null,
        contentScale = ContentScale.Fit
    )
    Column(
        modifier = Modifier.fillMaxWidth(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        repeat(20) {
            Text(text = "${it}")
        }
    }
}

One of the things that I tried was setting the size of the Image to fillMaxSize and the weight of the second Column to 1f, but it didn't do anything. Maybe I need to have the size of the second Column fixed? Any help is very appreciated.

Upvotes: 7

Views: 40809

Answers (5)

Aakash
Aakash

Reputation: 23815

This is what worked for me:

Column(
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Image(
        modifier = Modifier
            .size(350.dp), // you can set this to whatever size you want your image to be
        painter = painterResource(id = R.drawable.android_superhero1),
        contentDescription = null
    )
    Column {
        repeat(20) {
            Text(text = it.toString())
        }
    }
}

This is with 350.dp (.size(350.dp)):

enter image description here

This is with 150.dp (.size(150.dp)):

enter image description here

Upvotes: 0

Yevhen Okhrimenko
Yevhen Okhrimenko

Reputation: 11

If we consider the height of the parent Column - as the sum of the heights of the Image and the nested Column - it can be argued that the height of your Image should be equal to the remainder of the height, after subtracting from the height of the parent Column - the height of the nested Column.

var textInColumnSize by remember { mutableStateOf(Size.Zero) }
var globalColumnSize by remember { mutableStateOf(Size.Zero) }
val imageHeight: Dp =
    LocalDensity.current.run { (globalColumnSize.height - 
    textInColumnSize.height).toDp() }

Column(
    modifier = Modifier
        .fillMaxSize()
        .onGloballyPositioned { coordinates ->
            globalColumnSize = coordinates.size.toSize()
        }
        .background(Color.Cyan)
        .padding(horizontal = 16.dp),
    horizontalAlignment = Alignment.CenterHorizontally,
    verticalArrangement = Arrangement.SpaceEvenly
) {

    Image(
        modifier = Modifier
            .fillMaxWidth()
            .height(imageHeight),
        painter = painterResource(R.drawable.rocket_boy),
        contentDescription = null,
    )
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .onGloballyPositioned { coordinates ->
                textInColumnSize = coordinates.size.toSize()
            },
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        repeat(10) {
            Text(text = "$it")
        }
    }
}

Upvotes: 1

Phil Dukhov
Phil Dukhov

Reputation: 88102

You haven't used Modifier.weight on the correct child. It should be applied to the view, which you need to fill the rest of the parent.

The parent will divide the vertical space remaining after measuring unweighted child elements and distribute it according to this weight.

Modifier.weight has argument fill with default parameter true. This default parameter works same as Modifier.fillMaxHeight() (in case of Column), if you don't need to fill all height available, you can specify false:

Column(
    modifier = Modifier
        .fillMaxSize()
        .background(Color.Cyan)
        .padding(horizontal = 16.dp),
    verticalArrangement = Arrangement.SpaceEvenly,
    horizontalAlignment = Alignment.CenterHorizontally,
) {
    val painter = painterResource(R.drawable.ic_redo)
    Image(
        modifier = Modifier.weight(1f, fill = false)
            .aspectRatio(painter.intrinsicSize.width / painter.intrinsicSize.height)
            .fillMaxWidth(),
        painter = painter,
        contentDescription = null,
        contentScale = ContentScale.Fit
    )
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxWidth()
    ) {
        repeat(20) {
            Text(text = "${it}")
        }
    }
}

Upvotes: 24

daniyelp
daniyelp

Reputation: 1265

Couldn't get it work right using a Column, but I succeeded using a ConstraintLayout and Nestor's help with the aspectRatio.

ConstraintLayout(
    modifier = Modifier.fillMaxSize()
) {
    val (button, text) = createRefs()

    val painter = painterResource(R.drawable.rocket_boy)
    Image(
        modifier = Modifier
            .aspectRatio(ratio = painter.intrinsicSize.width /
                    painter.intrinsicSize.height)
            .padding(16.dp)
            .constrainAs(text) {
                top.linkTo(parent.top)
                bottom.linkTo(button.top)
                height = Dimension.preferredWrapContent
                width = Dimension.preferredWrapContent
                start.linkTo(parent.start)
                end.linkTo(parent.end)
            },
        painter = painter,
        contentDescription = null,
        contentScale = ContentScale.Fit
    )

    Column(Modifier.constrainAs(button) {
        bottom.linkTo(parent.bottom, margin = 16.dp)
        top.linkTo(text.bottom)
        start.linkTo(parent.start)
        end.linkTo(parent.end)
        height = Dimension.wrapContent
    }) {
        repeat(5) {
            Text(text = "$it")
        }
    }
}

enter image description here enter image description here

But if someone thinks that it can still be done with a Column instead of a ConstraintLayout, he can post an answer and I may accept it.

Upvotes: 0

Nestor Perez
Nestor Perez

Reputation: 877

It seems to your image size is less than the screen max width, for that reason the container of the image fill the width, but the image remains small, if you fill the height on the image it scales correctly but the image container fills all space leaving below the list. You could try setting and aspect ratio to the modifier to prevent container from filling all available space:

...
val painter = painterResource(id = R.drawable.ic_dismiss_24)
Image(
  modifier = Modifier
                .aspectRatio(ratio = painter.intrinsicSize.height / 
  painter.intrinsicSize.width)
                .fillMaxWidth()
                .fillMaxHeight(),
  painter = painter,
  contentDescription = null,
  contentScale = ContentScale.Fit
)
...

result

Upvotes: 2

Related Questions