Jetpack Compose Constraint Layout Constraints not linking as expected

1.5k views Asked by At

I'm using constrainAs with Jetpack Compose to constrain a list of options.

  1. To the left of the text there is a space, I believe this is caused by the fact that I constrain the text box to the start to the parent and the end to the start of the switch, but I need the text to wrap as shown in the second option so I think I need both of those constraints. I have tried several constraints but cannot figure out how to have the text left justified and have the wrapping. The problem is depicted in red in the image.

  2. Also, I cannot figure out how to have the same spacing between the title and the description. This is shown in blue in the picture. I have the description constrained to the bottom of the title but when it wraps the text box becomes larger and is moved up and because the text gets centered it creates different spacing.

I have attached an image and the code.

@Composable
fun SwitchRow(title: String, description: String, enabled: Boolean) {
    Box(modifier = Modifier
        .height(66.dp)
        .fillMaxWidth()
        .padding(top = 12.dp, start = 16.dp, end = 8.dp, bottom = 12.dp)
    ) {
        ConstraintLayout(
            modifier = Modifier
                .fillMaxWidth()
        ) {
            val (titleText, descriptionText, switch) = createRefs()
            Text(
                text = title,
                modifier = Modifier
                    .padding(bottom = 16.dp)
                    .constrainAs(titleText) {
                        start.linkTo(parent.start)
                        top.linkTo(parent.top)
                    },
                color = MyAppTheme.colors.ice,
                fontSize = 18.sp,
                fontFamily = FontFamily(Font(R.font.barlow_regular, FontWeight.Normal)),
                textAlign = TextAlign.Start
            )
            Text(
                text = description,
                modifier = Modifier
                    .wrapContentSize()
                    .constrainAs(descriptionText) {
                        start.linkTo(parent.start)
                        end.linkTo(switch.start)
                        top.linkTo(titleText.bottom)
                        bottom.linkTo(parent.bottom)
                        width = Dimension.fillToConstraints
                    },
                color = MyAppTheme.colors.chalk,
                fontSize = 14.sp,
                fontFamily = FontFamily(Font(R.font.barlow_regular, FontWeight.Normal)),
                maxLines = 2,
                textAlign = TextAlign.Start
            )

            val checkedState = remember { mutableStateOf(true) }
            Switch(modifier = Modifier
                .background(color = Color.Gray)
                .constrainAs(switch) {
                    top.linkTo(parent.top)
                    end.linkTo(parent.end)
                },
                enabled = enabled,
                checked = checkedState.value,
                onCheckedChange = { checkedState.value = it },
                colors = SwitchDefaults.colors(
                    checkedThumbColor = MyAppTheme.colors.envy,
                    checkedTrackColor = MyAppTheme.colors.darkerEnvy,
                    uncheckedThumbColor = MyAppTheme.colors.navy,
                    uncheckedTrackColor = MyAppTheme.colors.darkerNavy,
                ),
            )
        }
    }
}

enter image description here

2

There are 2 answers

0
Heet Changela On

I think below changes in your code will solve your problem, I am not use font style and colour same as yours.

To left/start alignment you have to remove .wrapContentSize() method.

Box(modifier = Modifier
    .height(66.dp)
    .fillMaxWidth()
    .padding(top = 12.dp, start = 16.dp, end = 8.dp, bottom = 12.dp)
) {
    ConstraintLayout(
        modifier = Modifier
            .fillMaxWidth()
    ) {
        val (titleText, descriptionText, switch) = createRefs()
        Text(
            text = title,
            modifier = Modifier
                .constrainAs(titleText) {
                    start.linkTo(parent.start)
                    top.linkTo(switch.top)
                },
            color = Color.Black,
            fontSize = 18.sp,
            textAlign = TextAlign.Start
        )
        Text(
            text = description,
            modifier = Modifier
                .constrainAs(descriptionText) {
                    start.linkTo(parent.start)
                    end.linkTo(switch.start)
                    top.linkTo(titleText.bottom)
                    width = Dimension.fillToConstraints
                },
            color = Color.Red,
            fontSize = 14.sp,
            textAlign = TextAlign.Start
        )

        val checkedState = remember { mutableStateOf(true) }
        Switch(modifier = Modifier
            .background(color = Color.Gray)
            .constrainAs(switch) {
                top.linkTo(parent.top)
                end.linkTo(parent.end)
                bottom.linkTo(parent.bottom)
            },
            enabled = enabled,
            checked = checkedState.value,
            onCheckedChange = { checkedState.value = it },
            colors = SwitchDefaults.colors(
                checkedThumbColor = Color.Green,
                checkedTrackColor = Color.Gray,
                uncheckedThumbColor = Color.Cyan,
                uncheckedTrackColor = Color.DarkGray,
            ),
        )
    }
}

enter image description here

But I think you have to change you function constraint structure as below code

@Composable
fun SwitchRow(title: String, description: String, enabled: Boolean) {
    Box(
        contentAlignment = Alignment.TopStart,
        modifier = Modifier
            .height(85.dp)
            .padding(8.dp)
    ) {
        ConstraintLayout(
            modifier = Modifier
                .fillMaxWidth()
        ) {
            val (descriptionText, switch) = createRefs()
            Column(
                horizontalAlignment = Alignment.Start,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(start = 25.dp)
                    .constrainAs(descriptionText) {
                        top.linkTo(parent.top)
                        start.linkTo(parent.start)
                        end.linkTo(switch.start)
                    },
            ) {
                Text(
                    text = title,
                    color = Color.Black,
                    fontSize = 18.sp,
                    textAlign = TextAlign.Start
                )
                Spacer(modifier = Modifier.height(2.dp))
                Text(
                    text = description,
                    color = Color.Red,
                    fontSize = 14.sp,
                    textAlign = TextAlign.Start,
                    maxLines = 2
                )
            }
            val checkedState = remember { mutableStateOf(true) }
            Switch(
                modifier = Modifier
                    .background(color = Color.Gray)
                    .constrainAs(switch) {
                        top.linkTo(parent.top)
                        end.linkTo(parent.end)
                    },
                enabled = enabled,
                checked = checkedState.value,
                onCheckedChange = { checkedState.value = it },
                colors = SwitchDefaults.colors(
                    checkedThumbColor = Color.Green,
                    checkedTrackColor = Color.Gray,
                    uncheckedThumbColor = Color.Cyan,
                    uncheckedTrackColor = Color.DarkGray,
                ),
            )
        }
    }
}

enter image description here

In both code you have to change padding and spacing as per your requirement.

4
Dolev Dublon On

enter image description here enter image description here

I copy and paste the code into my editor and it gave me a lot of errors I couldn't solve. so I couldn't make you a copy and past the answer ... sorry for that.

But! look carefully!

the only thing you need to think about are Row() and Columns()

Insert this mindset into your way of thinking and it will make your life easy. like CSS if you are familiar with web development (because I saw you wrote justify content).

look at this picture down bellow.

enter image description here

as you can see at the picture above there are two major properties of the rows, to Justify the content as you said you want to do. and you can list all the properties with ctrl + space

the first is horizontalArrangement = , and the second is verticalAlignment = as you can see in the code bellow.

            Row(
                horizontalArrangement = Arrangement.Start,  // the properties you are looking for  in a Row()
                verticalAlignment = Alignment.CenterVertically, // // the properties you are looking for  in a Row()
            )

this is very confusing because a Column has really similar properties to justify its content too!

     Column(
                    verticalArrangement = Arrangement.Center,
                    horizontalAlignment = Alignment.CenterHorizontally,
                )

which the first is verticalArrangement = and the second is horizontalAlignment = and they are different words so be in focus when dealing with this deign so you wont confuse your self!

I included the property of the border in every row and column so you can see my way of thinking.

pro tip! use a lazy column so that the window size can be dynamic to your use.

after you will play with this it will be easier for you to make.

enter image description here

the weight() property of the modifier solves this problem.

i gave the left column 85% and the Switch button 15% percent you will see in the code where to modify it if you need to do so.

I created for you the simplest example I can make to fit your need that you can modify for you if you are encountering any more problems feel free to comment and ask.


import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.lilmokeq.ui.theme.LilMokeQTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LilMokeQTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background
                ) {
                    NotificationCenter(
                    )
                }
            }
        }
    }
}

@Composable
fun NotificationCenter() {
    LazyColumn(
        modifier = Modifier
            .fillMaxWidth()
            .padding(top = 12.dp, start = 16.dp, end = 8.dp, bottom = 12.dp)
            .background(color = Color.White)
    ) {
        item {
            // create a row with a X icon on the left and a title
            // right next to it with a little padding
            Row(
                horizontalArrangement = Arrangement.Start,  // the properties you are looking for  in a Row()
                verticalAlignment = Alignment.CenterVertically, // // the properties you are looking for  in a Row()
                modifier = Modifier
                    .fillMaxWidth()
                    .background(color = Color.White)
                    .border(1.dp, Color.Black)

            ) {
                IconButton(
                    onClick = { /*TODO*/ }, modifier = Modifier.clip(CircleShape)
                ) {
                    Icon(
                        imageVector = Icons.Default.Close,
                        contentDescription = "Close",
                        tint = Color.Black
                    )
                }
                Text(
                    text = "Notifications Preferences",
                    fontSize = 20.sp,
                    fontWeight = FontWeight.Bold,
//                    fontFamily = FontFamily(Font(R.font.roboto)), // for some reason i have a problem with this line
                    modifier = Modifier.padding(start = 8.dp)
                )
            }

            Spacer(modifier = Modifier.height(8.dp))
            Card(
                elevation = 8.dp,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(start = 8.dp, end = 8.dp)
                    .background(color = Color.White)
            ) {
                // create a row that will contain two columns , the left column will contain the text "Your Account" and under it the text "important notifications about your account" and the right column will contain a switch button.
                Row(
                    horizontalArrangement = Arrangement.SpaceAround,
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.border(1.dp, Color.Black)
                ) {
                    Column(
                        // the left column
                        verticalArrangement = Arrangement.Top,
                        horizontalAlignment = Alignment.Start, // useful to justify content
                        modifier = Modifier
                            .background(color = Color.White)
                            .border(1.dp, Color.Black)
                            // set a width to the column
                            .weight(0.85f)

                    ) {
                        Text(
                            text = "Your Account",
                            fontSize = 20.sp,
                            fontWeight = FontWeight.Bold,
                            modifier = Modifier.padding(start = 8.dp)
                        )
                        Text(
                            text = "important notifications about your account" + " and your account settings" + " and more information about" + "interesting stuff" + "lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis  ",
                            fontSize = 14.sp,
                            fontWeight = FontWeight.Normal,
                            modifier = Modifier.padding(start = 8.dp, end = 8.dp)

                        )
                    }
                    Column(
                        // the right column
                        horizontalAlignment = Alignment.End,
                        modifier = Modifier
                            .background(color = Color.White)
                            .border(1.dp, Color.Black)
                            // set a width to the column
                            .weight(0.15f)

                    ) {
                        Switch(
                            checked = true,
                            onCheckedChange = { /*TODO*/ },
                            modifier = Modifier
                                .padding(end = 8.dp)
                                .border(1.dp, Color.Black)
                        )
                    }
                } // end of row
            } // end of card
            Spacer(modifier = Modifier.height(8.dp))
            Card(
                elevation = 8.dp,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(start = 8.dp, end = 8.dp)
                    .background(color = Color.White)
            ) {
                // create a row that will contain two columns , the left column will contain the text "Your Account" and under it the text "important notifications about your account" and the right column will contain a switch button.
                Row(
                    horizontalArrangement = Arrangement.SpaceAround,
                    verticalAlignment = Alignment.CenterVertically,
                    modifier = Modifier.border(1.dp, Color.Black)
                ) {
                    Column(
                        // the left column
                        verticalArrangement = Arrangement.Top,
                        horizontalAlignment = Alignment.Start, // useful to justify content
                        modifier = Modifier
                            .background(color = Color.White)
                            .border(1.dp, Color.Black)
                            // set a width to the column
                            .weight(0.85f)

                    ) {
                        Text(
                            text = "Second notification",
                            fontSize = 20.sp,
                            fontWeight = FontWeight.Bold,
                            modifier = Modifier.padding(start = 8.dp)
                        )
                        Text(
                            text = "important notifications about your account" + " and your account settings" + " and more information about" + "interesting stuff" + "lorem ipsum dolor sit amet, consectetur adipiscing elit,",
                            fontSize = 14.sp,
                            fontWeight = FontWeight.Normal,
                            modifier = Modifier.padding(start = 8.dp, end = 8.dp)

                        )
                    }
                    Column(
                        // the right column
                        horizontalAlignment = Alignment.End,
                        modifier = Modifier
                            .background(color = Color.White)
                            .border(1.dp, Color.Black)
                            // set a width to the column
                            .weight(0.15f)

                    ) {
                        Switch(
                            checked = true,
                            onCheckedChange = { /*TODO*/ },
                            modifier = Modifier
                                .padding(end = 8.dp)
                                .border(1.dp, Color.Black)
                        )
                    }
                } // end of row
            } // end of card

        }

    }
}



enter image description here

the final result ^

this playlist covers a lot of information. watch it to get more ideas about your design and how to implement them.

https://www.youtube.com/watch?v=cDabx3SjuOY&list=PLQkwcJG4YTCSpJ2NLhDTHhi6XBNfk9WiC&ab_channel=PhilippLackner