How to achieve this layout in Jetpack Compose - android

I'm trying to use the new Jetpack Compose UI framework, but I'm running into an issue. I'd like to achieve this layout, which in xml is pretty easy to achieve:
But I can't figure out how to make the vertical divider take up the available vertical space, without specifying a fixed height. This code that I've tried doesn't seem to work:
#Composable
fun ListItem(item: PlateUI.Plate) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(8.dp),
elevation = 2.dp
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Code")
Text(text = item.code)
}
Spacer(
modifier = Modifier
.preferredWidth(1.dp)
.background(color = MaterialTheme.colors.onSurface.copy(0.12f))
)
Spacer(modifier = Modifier.weight(1f))
Text(
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 34.dp),
text = item.name
)
Spacer(modifier = Modifier.weight(1f))
}
}
}
I keep getting this result:
I also tried with ConstraintLayout, but it still didn't work
#Composable
fun ListItem(item: PlateUI.Plate) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(8.dp),
elevation = 2.dp
) {
ConstraintLayout(
modifier = Modifier.fillMaxWidth(),
) {
val(column, divider, text) = createRefs()
Column(
modifier = Modifier
.padding(8.dp)
.constrainAs(column){
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
},
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Code")
Text(text = item.code)
}
Spacer(
modifier = Modifier
.preferredWidth(1.dp)
.background(color = MaterialTheme.colors.onSurface.copy(0.12f))
.constrainAs(divider){
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(column.end)
}
)
Text(
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 34.dp)
.constrainAs(text){
start.linkTo(divider.end)
end.linkTo(parent.end)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
},
text = item.name
)
}
}
}
But nothing seems to work. Is this a bug, a missing feature or am I just missing something?
EDIT: Apparently the real problem is that the divider doesn't know how to measure when the Surface doesn't have a fixed height, setting height equal to some number solves the issue, but then the view doesn't adapt to the content height anymore, so this can't be the solution

You can apply:
the modifier .height(IntrinsicSize.Max) to the Row
the modifiers .width(1.dp).fillMaxHeight() to the Spacer
You can read more about the Intrinsic measurements here.
Something like:
Row(
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max),
verticalAlignment = Alignment.CenterVertically
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
....
) {
Text(text = "....")
}
Spacer(
modifier = Modifier
.width(1.dp)
.fillMaxHeight()
.background(color = MaterialTheme.colors.onSurface.copy(0.12f))
)
Text(...)
}

You can set Intrinsic.Max for the preferredHeight of the Row, then set the Spacer to fill max height. You can read more on Intrinsics in this codelab section.
#Composable
fun ListItem() {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(8.dp),
elevation = 2.dp
) {
Row(
modifier = Modifier.fillMaxWidth().preferredHeight(IntrinsicSize.Max),
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Code")
Text(text = "2456")
}
Spacer(
modifier = Modifier
.preferredWidth(1.dp)
.fillMaxHeight()
.background(color = Color.Black.copy(0.12f))
)
Spacer(modifier = Modifier.weight(1f))
Text(
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 34.dp),
text = "Some name"
)
Spacer(modifier = Modifier.weight(1f))
}
}
}

I've solved it using constraint layout:
Box(modifier = Modifier.padding(Dp(50f))) {
ConstraintLayout(
modifier = Modifier
.border(width = Dp(1f), color = Color.Black)
.fillMaxWidth()
) {
val (left, divider, right) = createRefs()
Column(
modifier = Modifier
.padding(horizontal = Dp(20f))
.constrainAs(left) {
width = Dimension.wrapContent
start.linkTo(parent.start)
top.linkTo(parent.top)
end.linkTo(divider.start)
bottom.linkTo(parent.bottom)
}
) {
Text(text = "Code")
Text(text = "A12")
}
Box(
modifier = Modifier
.width(Dp(1f))
.background(Color.Black)
.constrainAs(divider) {
width = Dimension.wrapContent
height = Dimension.fillToConstraints
start.linkTo(left.end)
top.linkTo(parent.top)
end.linkTo(right.start)
bottom.linkTo(parent.bottom)
}
)
Box(
modifier = Modifier
.constrainAs(right) {
width = Dimension.fillToConstraints
start.linkTo(divider.end)
top.linkTo(parent.top)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
}
) {
Text(
text = "Test",
modifier = Modifier
.padding(vertical = Dp(100f))
.align(Alignment.Center)
)
}
}
}
The key part is using that modifier height = Dimension.fillToConstraints

There are plenty of solutions here, but I thought I could demonstrate the ConstraintLayout approach and add a helpful usage of the IntrinsicSize enum that solves one of the issues (needing an adaptive height for the composable). Interestingly, either IntrinsicSize.Max or IntrinsicSize.Min will yield the desired behavior.
I used most of your code. The key differences are:
declares a guideline (my value passed in for the fraction does not produce the exact result you were looking for, but can be adjusted easily (use a fraction slightly smaller than .2) This can be useful if you expect wrapContent to alter your left Text to vary the location of a spacer, but would prefer a consistent spacer location across a list of these items.
others have mentioned, spacer modifier should include .fillMaxHeight()
define the height of the surface wrapper to be .height(IntrinsicSize.Min) docs ref here: https://developer.android.com/jetpack/compose/layout#intrinsic-measurements
divider start is constrained to the guideline
had to change the Spacer modifier to access the width, instead of preferredWidth
#Composable
fun ListItem(item: Plate) {
Surface(
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
shape = RoundedCornerShape(8.dp),
elevation = 2.dp
) {
ConstraintLayout(
modifier = Modifier.fillMaxWidth(),
) {
val guideline = createGuidelineFromStart(0.2f)
val(column, divider, text) = createRefs()
Column(
modifier = Modifier
.padding(8.dp)
.constrainAs(column){
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(guideline)
},
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Code")
Text(text = item.code)
}
Spacer(
modifier = Modifier
.constrainAs(divider){
top.linkTo(column.top)
bottom.linkTo(column.bottom)
start.linkTo(guideline)
}
.width(1.dp)
.fillMaxHeight()
.background(color = MaterialTheme.colors.onSurface.copy(0.12f))
)
Text(
modifier = Modifier
.padding(horizontal = 8.dp, vertical = 34.dp)
.constrainAs(text){
start.linkTo(divider.end)
end.linkTo(parent.end)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
},
text = item.name
)
}
}
}

I think Row layout is enough.
#Preview(showBackground = true, heightDp = 100)
#Composable
fun ListItem(item: PlateUI.Plate = PlateUI.Plate()) {
Card(
shape = RoundedCornerShape(8.dp)
) {
Row(
modifier = Modifier
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.padding(8.dp),
text = "Code\n${item.code}",
textAlign = TextAlign.Center
)
Box(
Modifier
.fillMaxHeight()
.width(1.dp)
.background(color = MaterialTheme.colors.onSurface.copy(0.12f))
)
Text(
modifier = Modifier
.weight(1f)
.padding(8.dp),
text = item.name,
textAlign = TextAlign.Center
)
}
}
}

Related

Jetpack Compose Row item height fillMaxHeight does not work with aspect ratio

I am trying to build the following layout:
I have the following code so far:
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(8.dp)
) {
//First Row
WidgetComposed(
Modifier
.fillMaxWidth()
.height(300.dp),
bgColor = Color.Red,
widgetName = "Widget 1"
)
Spacer(modifier = Modifier.height(8.dp))
// Second Row
Row(modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(), horizontalArrangement = Arrangement.SpaceBetween) {
WidgetComposed(
Modifier
.fillMaxWidth(0.6f)
.aspectRatio(2f / 3f, true),
bgColor = Color.Green,
widgetName = "Widget 2 (2:3)"
)
Spacer(modifier = Modifier.width(8.dp))
Column(modifier = Modifier
.fillMaxHeight()) {
WidgetComposed(
Modifier
.fillMaxWidth()
.weight(1f),
bgColor = Color.Blue,
widgetName = "Widget 3"
)
Spacer(modifier = Modifier.height(8.dp))
WidgetComposed(
Modifier
.fillMaxWidth()
.weight(1f),
bgColor = Color.Cyan,
widgetName = "Widget 4"
)
}
}
It gives me this:
The column with Widget 3 and 4 does not fill the height of the parent row. If I put hardcoded height value it works.
Aspect ration and 60% width is important to keep for Widget 2.
WidgetCompose is just a card and a box:
#Composable
fun WidgetComposed(modifier: Modifier = Modifier, bgColor: Color, widgetName: String) {
Card(modifier = modifier, shape = RoundedCornerShape(8.dp), backgroundColor = bgColor) {
Box(modifier = Modifier.padding(16.dp), contentAlignment = Alignment.Center) {
Text(text = widgetName)
}
}
}
How can I make the column with Widget 3 and 4 automatically fill the available height?
Preferably without using .onGloballyPositioned modifier
You can use:
Row(
modifier = Modifier
.fillMaxWidth()
.height(IntrinsicSize.Min),
horizontalArrangement = Arrangement.SpaceBetween
) {
WidgetComposed(
Modifier
.weight(0.6f)
.aspectRatio(2f / 3f, true),
//...
)
Spacer(modifier = Modifier.width(8.dp).fillMaxHeight())
Column(
modifier = Modifier
.weight(0.4f)
.fillMaxHeight()
) {
//..
}
}

How to place items in row and one each above like position absolute with JetpackCompose?

I need to make layout that looks like this:
So I need to position two avatars in row while first avatar is going over second one. Also, icon for back is positioned on top of first avatar. I need basically something like position absolute but can not find how to do this with Jetpack compose.
You can use Box and zIndex to reach this result,
try this code you will get a result like that
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.background(Color.Yellow)
.padding(20.dp)
) {
Box {
Box(
contentAlignment = Alignment.TopStart,
modifier = Modifier
.height(30.dp)
.zIndex(3f)
) {
Icon(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = "ArrowBack",
modifier = Modifier
.size(20.dp)
.clip(RoundedCornerShape(20.dp))
.background(Color.Gray)
.zIndex(2f)
.border(1.dp, Color.White, RoundedCornerShape(20.dp))
.padding(3.dp)
)
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.padding(start = 10.dp)
.size(30.dp)
.clip(RoundedCornerShape(20.dp))
.background(Color.Gray)
.zIndex(2f)
.border(1.dp, Color.White, RoundedCornerShape(20.dp))
.padding(6.dp)
) {
Icon(
imageVector = Icons.Rounded.Person,
contentDescription = "Person",
modifier = Modifier
.size(30.dp)
)
}
Box(
modifier = Modifier
.padding(start = 30.dp)
.size(30.dp)
.clip(RoundedCornerShape(20.dp))
.background(Color.Gray)
.zIndex(1f)
.border(1.dp, Color.White, RoundedCornerShape(20.dp))
.padding(6.dp)
) {
Icon(
imageVector = Icons.Rounded.Person,
contentDescription = "Person",
modifier = Modifier
.size(30.dp)
)
}
}
Spacer(modifier = Modifier.width(6.dp))
Text(text = "17 other answers")
}

Android Compose resize image on available space

I'm new to Android Jetpack Compose and have the following layout problem:
It's a simple example to clarify my issue. I want that Button header to stay on top and this two text areas at the bottom.
The image should take the rest of the available space, depending on the screen size. I don't want a scrollbar or white areas at the top/bottom.
At the moment the image has a fixed size. How do I have to adjust the layout so that the image automatically adapts to the available space?
Here's the code:
#Composable
fun Greeting() {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
Spacer(
modifier = Modifier
.height(32.dp)
.fillMaxSize()
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp)
) {
Button(onClick = { /*TODO*/ }) {
Text(text = "Back")
}
Button(onClick = { /*TODO*/ }) {
Text(text = "Next")
}
}
Spacer(modifier = Modifier.height(24.dp))
Image(painter = painterResource(id = R.drawable.waterfall), contentDescription = "Instant post demo image", Modifier.height(200.dp))
Spacer(modifier = Modifier.height(24.dp))
Text(text = text, fontSize = 14.sp, modifier = Modifier.padding(start = 16.dp, end = 16.dp))
Spacer(modifier = Modifier.height(16.dp))
Text(text = text, fontSize = 14.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 16.dp, end = 16.dp)
)
}
}
Thx in advance

Column vertical SpaceBetween not taking into account

Can not understand why in this card, in the column, the spacebetween vertical alignment is not taking into account. I'm expecting the Title to be on top, and the "View Details" button to be at the bottom of the Column
fun UserCard(image:Int, text:String, actionButtonLabel:String){
Card(
elevation = 4.dp,
modifier = Modifier
.padding(12.dp)
.fillMaxWidth()
.wrapContentHeight()
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.padding(8.dp)
.border(width = 1.dp, color = Color.Blue)
.padding(12.dp)
) {
Image(
painter = painterResource(id = image),
contentDescription = "",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(120.dp)
.clip(CircleShape)
)
Column(
modifier= Modifier.fillMaxHeight(),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.SpaceBetween, // <-- seems not to be taking into account
) {
Title(text)
Button(onClick = { }) {
Text(text = actionButtonLabel)
}
}
}
}
}
example of call of UserCard
UserCard(
image= R.drawable.iron,
text = "Live after death",
actionButtonLabel = "View details"
)
You can apply an intrinsic measurements to the Row container and the fillMaxHeight() Modifier to the Column.
Your issue is the height of the column.
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.height(IntrinsicSize.Min)
//..
){
/* Image */
Column(
modifier = Modifier
.fillMaxHeight(),
horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.SpaceBetween){
//...
}
}

How to Split screen height in half in Jetpack Compose?

I want to split my screen in half horizontally in Jetpack Compose like this:
#Composable
fun Splash(alpha: Float) {
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp
val screenWidth = configuration.screenWidthDp.dp
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.cat2))
Box(
modifier = Modifier
.background(Blue)
.height(screenHeight / 2)
.padding(8.dp),
contentAlignment = Alignment.TopCenter
) {
Column() {
Text(text = "Example", fontSize = 44.sp)
}
}
Box(
modifier = Modifier
.background(Red)
.height(screenHeight / 2)
.padding(8.dp),
contentAlignment = Alignment.BottomCenter
){
Column {
Text(text = "Example", textAlign = TextAlign.End, color = Grey, fontSize = 12.sp)
}
}
}
I can get screen height with LocalConfiguration.current in dp and I set my top box and bottom box alignments as Alignment.TopCenter and Alignment.BottomCenter respectively but it didn't work. Second box(Red one) stays on top of the blue one.
You can wrap your Boxes with a Column and set Modifier.weight(1f) for each box to set both of them at same height with
#Composable
fun Splash() {
Column(modifier =Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Blue)
.weight(1f)
.padding(8.dp),
contentAlignment = Alignment.TopCenter
) {
Column() {
Text(text = "Example", fontSize = 44.sp)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.background(Red)
.weight(1f)
.padding(8.dp),
contentAlignment = Alignment.Center
){
Column {
Text(text = "Example", textAlign = TextAlign.End, color = DarkGray, fontSize = 12.sp)
}
}
}
}
or wrap with a BoxWithConstraints which returns Contraints, maxWidth, maxHeight and use Modifier.align to one Box to top and other one to Bottom. BoxWithConstraints is useful for getting measurement parameters and set them as children Modifiers.
#Composable
fun Splash2() {
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Blue)
.height(maxHeight/2)
.align(Alignment.TopStart)
.padding(8.dp),
contentAlignment = Alignment.TopCenter
) {
Column() {
Text(text = "Example", fontSize = 44.sp)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.background(Red)
.align(Alignment.BottomStart)
.height(maxHeight/2)
.padding(8.dp),
contentAlignment = Alignment.Center
){
Column {
Text(text = "Example", textAlign = TextAlign.End, color = DarkGray, fontSize = 12.sp)
}
}
}
}
The simplest way to set a height equal to the half-screen height is with a fractional in the modifier.
Column(
modifier = Modifier
.padding(10.dp)
.fillMaxHeight(0.5f)
){ //Content}
The best way to implement your image is this way:
Column(
Modifier
.fillMaxSize()
.padding(8.dp)
) {
Row(
Modifier
.fillMaxWidth()
.weight(1f)
.background(Blue),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
Text(text = "Example", fontSize = 44.sp)
}
Row(
Modifier
.fillMaxWidth()
.weight(1f)
.background(Red),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
Text(text = "Example", fontSize = 44.sp)
}
}

Categories

Resources