Issue
I have a "simple" layout in Compose, where in a Column
there are two elements:
- top image
- a grid of 4 squares underneath, each square containing some text
I'd like the layout to have the following behaviour:
- set the maximum height of the image to be
screenWidth
- set the minimum height of the image to be
200.dp
- the image should always be in a square container (cropping of the image is fine)
- let the grid "grow" as much as it needs to, to wrap around the content, making the image shrink as necessary
This means that if the text in the squares is short, the image will cover a large square on the top. But if any of the square text is really, long, I want the whole grid to scale up and shrink the image. These are the desirable outcomes:
- When text is short enough
- When a piece of text is really long
I have tried this with ConstraintLayout
in Compose, but I can't get the squares to scale properly.
With a Column
, I can't get the options to grow with large content - the text just gets truncated and the image remains a massive square.
These are the components I'd built:
// the screen
Column {
Box(modifier = Modifier
.heightIn(min = 200.dp, max = screenWidth)
.aspectRatio(1f)
.border(BorderStroke(1.dp, Color.Green))
.align(Alignment.CenterHorizontally),
) {
Image(
painter = painterResource(id = R.drawable.puppy),
contentDescription = null,
contentScale = ContentScale.Crop
)
}
OptionsGrid(choicesList, modifier = Modifier.heightIn(max = screenHeight - 200.dp))
}
@Composable
fun OptionsGrid(choicesList: List<List<String>>, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.border(1.dp, Color.Blue)
.padding(top = 4.dp, bottom = 4.dp)
.fillMaxHeight(),
verticalArrangement = Arrangement.Center
) {
choicesList.forEach { choicesPair ->
Row(modifier = Modifier.weight(0.5f)) {
choicesPair.forEach { choice ->
Box(
modifier = Modifier
.padding(4.dp)
.background(Color.White)
.weight(0.5f)
) {
Option(choice = choice)
}
}
}
}
}
}
@Composable
fun Option(choice: String) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Yellow)
.border(BorderStroke(1.dp, Color.Red)),
contentAlignment = Alignment.Center
) {
Text(
text = choice,
modifier = Modifier.padding(8.dp),
textAlign = TextAlign.Center,
)
}
}
Do I need a custom layout for this? I suppose what's happening here is that the Column
is measuring the image first, letting it be its maximum height, because there is space for that on the screen, and then when measuring the grid, it gives it the remaining space and nothing more.
So I'd need a layout which starts measuring from the bottom?
Solution
Here's how you can do it without custom layout.
You need your image size to be calculated after
OptionsGrid
. In this case you can useModifier.weight(1f, fill = false)
: it forces all the views withoutModifier.weight
to be layout before any weighted elements.Modifier.weight
will override yourModifier.heightIn
, but we can restrict it size from the other side: usingModifier.layout
onOptionsGrid
. Using this modifier we can override constraints applied to the view.p.s.
Modifier.heightIn(max = screenWidth)
is redundant, as views are not gonna grow more than screen size anyway, unless the width constraint is overridden, for example, with a scroll view..height(IntrinsicSize.Min)
will stopOptionsGrid
from growing more than needed. Note that is should be placed afterModifier.layout
, as it sets height constraint to infinity. See why modifiers order matters.
val choicesList = listOf(
listOf(
LoremIpsum(if (flag) 100 else 1).values.first(),
"Short stuff",
),
listOf(
"Medium length text",
"Hi",
),
)
Column {
Box(
modifier = Modifier
.weight(1f, fill = false)
.aspectRatio(1f)
.border(BorderStroke(1.dp, Color.Green))
.align(Alignment.CenterHorizontally)
) {
Image(
painter = painterResource(id = R.drawable.profile),
contentDescription = null,
contentScale = ContentScale.Crop
)
}
OptionsGrid(
choicesList,
modifier = Modifier
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints.copy(
// left 200.dp for min image height
maxHeight = constraints.maxHeight - 200.dp.roundToPx(),
// occupy all height except full image square in case of smaller text
minHeight = constraints.maxHeight - constraints.maxWidth,
))
layout(placeable.width, placeable.height) {
placeable.place(0, 0)
}
}
.height(IntrinsicSize.Min)
)
}
Result:
Answered By - Philip Dukhov
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.