Is it possible to place 2 children as normal at th...
# compose
l
Is it possible to place 2 children as normal at the top and one child at the bottom in a
Column
? For example:
Copy code
Column(modifier = Modifier.fillMaxWidth().height(80.dp).padding(12.dp)) {
  Text("Text1")
  Text("Text2")

  // At bottom
  Button(onClick = {}) {
    Text("Click")
  }
}
c
You probably want to add a spacer that fills up all extra space.
t
Spacer(Modifier.weight(1f))
☝️ 1
c
And potentially remove the hardcoded height of the column as that'll break on a users device where they have large text enabled
☝️ 1
And if you didn't know weight is something that can be used that'll essentially tell the thing to take up the rest of available space when set to 1f
t
(unless you use
weight(1f, fill = false)
)
👍 1
and it doesn't matter what the weight value is set to, space is assigned to each weighted child in proportion to its weight value divided by the sum of all weight values
I suppose it can't be
0f
but I'm not sure
l
Thanks guys, the height was just for demonstration, my column has no height set. Unfortunately with
Spacer(Modifier.weight(1f))
it fills the whole screen.
weight(1f, fill = false)
does nothing and
0f
is not valid (can't be build because of an preview issue).
Copy code
Column(horizontalAlignment = Alignment.End) {
	CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
		Text(
			text = creationDate,
			textAlign = TextAlign.End,
			style = MaterialTheme.typography.caption
		)
	}
	Spacer(modifier = Modifier.height(8.dp))
	Row(verticalAlignment = Alignment.CenterVertically) {
		Icon(
			imageVector = iconRes(R.drawable.ic_baseline_circle_24),
			modifier = Modifier.size(12.dp),
			tint = Yellow600,
			contentDescription = null,
		)
		Spacer(modifier = Modifier.width(4.dp))
		Text(text = "In Progress", color = Yellow600)
	}
	Spacer(Modifier.weight(1f))
	Button(onClick = { /*TODO*/ }) {
		Text(text = "Upgrade")
	}
}
c
That seems to be what you said you wanted?
Is it possible to place 2 children as normal at the top and one child at the bottom in a
Column
?
t
Yeah, you'll need some constraint on the `Column`'s height to keep from filling the screen
c
Do you have an image of how you want it to look like?
l
ok I should refine my question: Is it possible to place 2 children as normal at the top and one child at the bottom in a
Column
while the height of the column is measured by its content (in my case the comment)
That's what I'm looking for. I currently just placed a
Spacer(Modifier.height(30.dp))
So do I have to set a height for the column? No other option?
t
I believe there is a solution for this using
IntrinsicSize
, one sec
Yeah, try
Modifier.height(IntrinsicSize.Min)
on the containing
Row
then keep our suggestion above
c
Oh yeah, intrinsics will help you here! The docs are pretty decent: https://developer.android.com/jetpack/compose/layouts/intrinsic-measurements
t
Super nice, because it gets real messy with constraints (remember
Barrier
?)
l
@tad I gues you mean to put the modifier on the Column, the Row is just for the icon + "In Progress" text
t
Sorry, whatever container has both columns in it
l
Close but still not optimal:
Copy code
Column(modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min)) {
    ...
	Column(horizontalAlignment = Alignment.End) {
		CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
			Text(
				text = creationDate,
				textAlign = TextAlign.End,
				style = MaterialTheme.typography.caption
			)
		}
		Spacer(modifier = Modifier.height(8.dp))
		Row(verticalAlignment = Alignment.CenterVertically) {
			Icon(
				imageVector = iconRes(R.drawable.ic_baseline_circle_24),
				modifier = Modifier.size(12.dp),
				tint = Yellow600,
				contentDescription = null,
			)
			Spacer(modifier = Modifier.width(4.dp))
			Text(text = "In Progress", color = Yellow600)
		}
		Spacer(Modifier.weight(1f))
		Button(onClick = { /*TODO*/ }) {
			Text(text = "Upgrade")
		}
	}
}
t
The outer container should probably be a
Row
so the children will be aligned without overlapping
l
None of my Columns or Rows has set a height except the parent Column with IntrinsicSize
t
That's fine, what we're trying to do is use the intrinsic size of the left column to constrain the height of the right column
l
You are kinda right. The outer container is a Column because I have a ProgressIndicator at the bottom. I currently moved the
.height(IntrinsicSize.Min)
to the Row with the left and right Column but unfortuantely same result as in screenshot above
t
Can we see the entire function?
l
Sure. I provided some preview data so you should be able to run the preview.
The IntrinsicSize.Min is not measured correctly somehow. I don't know where the extra space is coming from. Even when I remove
Spacer(Modifier.weight(1f, fill = false))
it has still this extra space. Might this be a bug?
t
I wish my Preview worked
what happens if you move
.padding
to after
.height(IntrinsicSize.Min)
?
l
Already tried, no change 🤕
t
It's the Button that messes it up
If I replace it with Text, I get this:
I think they added something recently that enforces a minimum touch target size of 48dp, which might be throwing off the
IntrinsicSize
measurement.
Lemme try wrapping it in a Box
l
Wow you are awesome. Thanks for the investigation
t
Here we go, with a couple more things showing borders:
Looks like the Button's intrinsic size is its text, but its layout is measured to be larger
l
Does the large space above the button belong to the button?
t
No, I replaced Spacer with a Box to show what it's measuring to
I've gotten around this before with
wrapContentSize
, lemme try that
here's my hack:
Copy code
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
                            Button(
                                onClick = { /*TODO*/ },
                                Modifier
                                    .height(0.dp)
                                    .wrapContentHeight(Alignment.Bottom, unbounded = true)
                                    .heightIn(ButtonDefaults.MinHeight)
                            ) {
                                Text(text = "Upgrade")
                            }
                        }
👍 1
anyway, good luck!
l
I will test it later, it's late here. Would you say this is a bug with the button component? Or is it correct behavior of the button? Thanks for you help ❤️
c
Did you try
Modifier.align(Alignment.Bottom)
for the Button? That should allow you to break alignment with the natural flow of siblings in the Column, but still keep the Column itself measured to the total height of its children.
The other option is wrapping the first two children in their own Column, then setting verticalArrangment to Arrangement.SpaceBetween on your original Column. Only issue is having nested Columns but that might what you need in the long run if content changes/grows
l
@Chris Sinco [G] Both suggestions do not work for me.
Modifier.align(Alignment.Bottom)
is not applicable:
fun Modifier.align(alignment: Alignment.Vertical): Modifier' can't be called in this context by implicit receiver. Use the explicit one if necessary
The second suggestion does not work because none of the columns has set a height and naturally the column wraps its content, so a
verticalArrangement
modifier has no effect. You missed that the
Button
should be at the Bottom of the parent
Row
and not at the Bottom of the right
Column
which height is constrained to its content. So to make it work the right Column has to fill up the available space of the
Row
while the left
Column
specifies the height of the Row by its content.
The behavior of the IntrinsicSize.Min seems buggy:
Copy code
@Preview
@Composable
fun TestPreview() {
    Column(modifier = Modifier.fillMaxWidth().border(width = 1.dp, color = Color.Red)) {
        Row(
            modifier = Modifier
                .height(IntrinsicSize.Min)
                .fillMaxWidth()
        ) {
            Column(modifier = Modifier.fillMaxWidth(0.6f)) {
                Text(modifier = Modifier.border(width = 1.dp, color = Color.Red), text = "Short Text")
            }
        }
    }
}
Where does the additional space below the
Text
come from?
c
I copied the full function you had up earlier (FirmwareListItem), and added weight modifiers to the two columns you have. That seems to get the result you want albeit some padding beneath the Button which is due to minimum touch target, which you can opt of if you want, or you can offset it manually as well.
This is the full code. IntrinsicSize does work as expected, what you need is
weight
to tell the measuring how to use the remaining free space. I also played around with removing that last Spacer you added to push the Button down, by adding verticalArrangement = Arrangement.SpaceBetween, which works since the Column now takes up the height of the tallest sibling (also works with Modifier.fillMaxHeight)
Copy code
@Preview
@Composable
private fun FirmwareListItem(
    progressState: Float = 0.8f,
    onItemClick: () -> Unit = {}
) {
    Surface(
        shape = RoundedCornerShape(12.dp),
    ) {
        Column(modifier = Modifier.fillMaxWidth()) {
            Row(
                modifier = Modifier
                    .padding(horizontal = 16.dp, vertical = 8.dp)
                    .fillMaxWidth().height(IntrinsicSize.Min),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Column(modifier = Modifier.weight(0.6f)
                ) {
                    Text(
                        modifier = Modifier.padding(start = 4.dp),
                        text = "Name",
                        fontSize = 8.sp,
                        style = MaterialTheme.typography.caption
                    )
                    Spacer(modifier = Modifier.height(2.dp))
                    Text(
                        text = "File name",
                        overflow = TextOverflow.Ellipsis,
                        style = MaterialTheme.typography.overline
                    )
                    Spacer(modifier = Modifier.height(12.dp))
                    Text(
                        modifier = Modifier.padding(start = 4.dp),
                        text = "Version for processor type on platform",
                        fontSize = 8.sp,
                        style = MaterialTheme.typography.caption
                    )
                    Spacer(modifier = Modifier.height(2.dp))
                    Text(
                        text = "Version information",
                        style = MaterialTheme.typography.subtitle1
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    Text(
                        modifier = Modifier.padding(start = 4.dp),
                        text = "Comment",
                        fontSize = 8.sp,
                        style = MaterialTheme.typography.caption
                    )
                    Spacer(modifier = Modifier.height(2.dp))
                    Text(
                        text = "asdkfj lakdsjf laskjd flaksdjf lk jalskdfj alksdj falskjdf lk alskdj falksdj flkasdj flaksdj flkasjd flkasjd flkasjd flkasdj",
                        style = MaterialTheme.typography.body2
                    )
                }
                Column(
                    Modifier.weight(0.4f),
                    horizontalAlignment = Alignment.End
                ) {
                    Text(
                        text = "Creation date",
                        textAlign = TextAlign.End,
                        style = MaterialTheme.typography.caption
                    )
                    Spacer(modifier = Modifier.height(8.dp))
                    Row(verticalAlignment = Alignment.CenterVertically) {
                        Icon(
                            imageVector = Icons.Default.Face,
                            modifier = Modifier.size(12.dp),
                            contentDescription = null,
                        )
                        Spacer(modifier = Modifier.width(4.dp))
                        Text(text = "In Progress")
                    }
                    Spacer(Modifier.weight(1f))
                    Button(onClick = { /*TODO*/ }) {
                        Text(text = "Upgrade")
                    }
                }
            }
            LinearProgressIndicator(modifier = Modifier.fillMaxWidth(), progress = progressState)
        }
    }
}
❤️ 1
I’m not sure why that very simplistic Preview of IntrinsicSize doesn’t work but we can look into that later
1
l
So as a rule of thumb, when I use IntrinsicSize with Columns or Rows, I have to set a height or set a weight, is this correct?
c
For siblings within a Row or Column that uses IntrinsicSize, that are not the tallest/widest, weight may have to be used so the siblings know how to divide the remaining space. Not sure that is a rule of thumb, but more something to consider if you want to use the remaining space created by the child that determines the intrinsic size
l
ok thanks for your time, that helped me 🙂
👍 1