https://kotlinlang.org logo
#compose
Title
# compose
l

Lilly

04/14/2022, 12:07 AM
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

Colton Idle

04/14/2022, 12:38 AM
You probably want to add a spacer that fills up all extra space.
t

tad

04/14/2022, 12:38 AM
Spacer(Modifier.weight(1f))
☝️ 1
c

Colton Idle

04/14/2022, 12:39 AM
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

tad

04/14/2022, 12:41 AM
(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

Lilly

04/14/2022, 12:46 AM
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

Colton Idle

04/14/2022, 12:48 AM
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

tad

04/14/2022, 12:48 AM
Yeah, you'll need some constraint on the `Column`'s height to keep from filling the screen
c

Colton Idle

04/14/2022, 12:48 AM
Do you have an image of how you want it to look like?
l

Lilly

04/14/2022, 12:51 AM
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

tad

04/14/2022, 12:56 AM
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

Colton Idle

04/14/2022, 12:58 AM
Oh yeah, intrinsics will help you here! The docs are pretty decent: https://developer.android.com/jetpack/compose/layouts/intrinsic-measurements
t

tad

04/14/2022, 1:00 AM
Super nice, because it gets real messy with constraints (remember
Barrier
?)
l

Lilly

04/14/2022, 1:02 AM
@tad I gues you mean to put the modifier on the Column, the Row is just for the icon + "In Progress" text
t

tad

04/14/2022, 1:05 AM
Sorry, whatever container has both columns in it
l

Lilly

04/14/2022, 1:16 AM
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

tad

04/14/2022, 1:18 AM
The outer container should probably be a
Row
so the children will be aligned without overlapping
l

Lilly

04/14/2022, 1:18 AM
None of my Columns or Rows has set a height except the parent Column with IntrinsicSize
t

tad

04/14/2022, 1:19 AM
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

Lilly

04/14/2022, 1:21 AM
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

tad

04/14/2022, 1:21 AM
Can we see the entire function?
l

Lilly

04/14/2022, 1:24 AM
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

tad

04/14/2022, 1:34 AM
I wish my Preview worked
what happens if you move
.padding
to after
.height(IntrinsicSize.Min)
?
l

Lilly

04/14/2022, 1:39 AM
Already tried, no change 🤕
t

tad

04/14/2022, 1:47 AM
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

Lilly

04/14/2022, 1:52 AM
Wow you are awesome. Thanks for the investigation
t

tad

04/14/2022, 1:53 AM
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

Lilly

04/14/2022, 1:56 AM
Does the large space above the button belong to the button?
t

tad

04/14/2022, 1:56 AM
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

Lilly

04/14/2022, 2:19 AM
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

Chris Sinco [G]

04/14/2022, 7:36 AM
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

Lilly

04/14/2022, 10:02 AM
@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

Chris Sinco [G]

04/14/2022, 11:52 AM
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

Lilly

04/14/2022, 12:25 PM
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

Chris Sinco [G]

04/14/2022, 1:44 PM
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

Lilly

04/14/2022, 1:52 PM
ok thanks for your time, that helped me 🙂
👍 1
4 Views