Getting started with jetpack compose – ConstraintLayout

ConstraintLayout can help place composable relative to others on the screen and is an alternative to using multiple nested Row, Column, Box, and custom layout elements. ConstraintLayout is useful when implementing larger layouts with more complicated alignment requirements. – Android Developers


If you’re new to Compose, I highly recommend going through the following articles:

Getting started with jetpack compose – Basic components

Getting started with jetpack compose – Layouts

Getting started with jetpack compose – Modifiers

Getting started with jetpack compose – Scaffold layout

Getting started with jetpack compose – Theming


To use ConstraintLayout in Compose, you need to add this dependency in your build.gradle:

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0-beta02"

Steps to setup constraintLayout in jetpack compose

  • Create reference for each composable in constraintLayout using createRefs() or createRef().
  • Constraints are provided using the constrainAs modifier which takes the reference as a parameter and lets you specify its constraints in the body lambda.
  • Constraints are specified using linkTo or other helpful methods.
  • parent is an existing reference that can be used to specify constraints towards the ConstraintLayout composable itself.

Create constraintLayout

As mentioned earlier, First need to crate reference of each composable using createRefs() inside a constraintLayout.

 ConstraintLayout() {
        val (textTopStart, textTopCenter, textTopEnd, textCenterStart,textCenter, textCenterEnd, textBottomStart, textBottomCenter, textBottomEnd) = createRefs()
     ....
}

Next, create a composable and add a constraint using constraintAs modifier. Also, specify constraints to the composable using linkTo().

Text(text = "Top start", Modifier.constrainAs(textTopStart) {
            top.linkTo(parent.top, 8.dp)
            start.linkTo(parent.start, 8.dp)
        })

Here’s an example of a composable using a ConstraintLayout:

@Composable
fun ConstraintLayoutLinkToParentExample() {

    ConstraintLayout(modifier = Modifier
        .fillMaxWidth()
        .height(200.dp)) {

        val (textTopStart, textTopCenter, textTopEnd, textCenterStart,textCenter, textCenterEnd, textBottomStart, textBottomCenter, textBottomEnd) = createRefs()

        Text(text = "Top start", Modifier.constrainAs(textTopStart) {
            top.linkTo(parent.top, 8.dp)
            start.linkTo(parent.start, 8.dp)
        })

        Text(text = "Top Center", Modifier.constrainAs(textTopCenter) {
            top.linkTo(parent.top, 8.dp)
            start.linkTo(parent.start, 8.dp)
            end.linkTo(parent.end, 8.dp)
        })

        Text(text = "Top End", Modifier.constrainAs(textTopEnd) {
            top.linkTo(parent.top, 8.dp)
            end.linkTo(parent.end, 8.dp)
        })

        Text(text = "Center start", Modifier.constrainAs(textCenterStart) {
            top.linkTo(parent.top, 8.dp)
            bottom.linkTo(parent.bottom, 8.dp)
        })

        Text(text = "Center", Modifier.constrainAs(textCenter) {
            top.linkTo(parent.top, 8.dp)
            bottom.linkTo(parent.bottom, 8.dp)
            start.linkTo(parent.start, 8.dp)
            end.linkTo(parent.end, 8.dp)
        })

        Text(text = "Center end", Modifier.constrainAs(textCenterEnd) {
            top.linkTo(parent.top, 8.dp)
            bottom.linkTo(parent.bottom, 8.dp)
            end.linkTo(parent.end, 8.dp)
        })


        Text(text = "Bottom start", Modifier.constrainAs(textBottomStart) {
            start.linkTo(parent.start, 8.dp)
            bottom.linkTo(parent.bottom, 8.dp)
        })

        Text(text = "Bottom Center", Modifier.constrainAs(textBottomCenter) {
            bottom.linkTo(parent.bottom, 8.dp)
            start.linkTo(parent.start, 8.dp)
            end.linkTo(parent.end, 8.dp)
        })

        Text(text = "Bottom end", Modifier.constrainAs(textBottomEnd) {
            bottom.linkTo(parent.bottom, 8.dp)
            end.linkTo(parent.end, 8.dp)
        })
    }
}

In the above code, I have created 9 different composable and aligned them in different parts of the constraint layout. the output of the above code.

ConstraintLayout link to the parent

ConstraintLayout using constraintSet

In the ConstraintLayout example, constraints are specified inline, with a modifier in the composable they’re applied to. If you want to decouple the constraint from the constraintLatyout, then we need to use constraintSet.

  • create constraintSet using composable references and constraints.
  • create constraintLayout by passing constraintSet as a parameter.
  • Assign references created in the ConstraintSet to composables using the layoutId modifier.

We Will see the point in detail here,

First, need to create constraintSet.

val constraintSet = ConstraintSet {
        val buttonLogin = createRefFor("buttonLogin")
        val inputUsername = createRefFor("inputUsername")
        val inputPassword = createRefFor("inputPassword")

        constrain(inputUsername) {
            top.linkTo(parent.top, 32.dp)
            start.linkTo(parent.start, 16.dp)
            end.linkTo(parent.end, 16.dp)
        }

        constrain(inputPassword) {
            top.linkTo(inputUsername.bottom, 8.dp)
            start.linkTo(parent.start, 16.dp)
            end.linkTo(parent.end, 16.dp)
        }

        constrain(buttonLogin) {
            top.linkTo(inputPassword.bottom, 16.dp)
            start.linkTo(parent.start, 16.dp)
            end.linkTo(parent.end, 16.dp)
        }

    }

In the above code, First I have created references for the composable like buttonLogin, inputUsername.

Then, created constraints for the references. for the inputUsername , set top to parent and start to parent and end to parent. like that we need to create constraints for all the references.

Next, we need to pass the constraintSet as a parameter for the constraintLayout.

ConstraintLayout(constraintSet) {
        .....
    }

Finally, Assign references created in the ConstraintSet to composable using the layoutId modifier.

ConstraintLayout(constraintSet, modifier = Modifier
        .fillMaxWidth()
        .height(250.dp)) {
        val context = LocalContext.current
        var username by remember {
            mutableStateOf("")
        }
        var password by remember {
            mutableStateOf("")
        }
        TextField(value = username,  onValueChange = { username = it } , label = { Text(text = ("Username"))},  modifier =  Modifier.layoutId("inputUsername"))

        TextField(value = password, onValueChange = { password = it }, label = { Text("Password")},        visualTransformation = PasswordVisualTransformation(),
            modifier = Modifier.layoutId("inputPassword"))

        Button(onClick = { Toast.makeText(context, "Username $username , Password: $password ", Toast.LENGTH_SHORT).show()}, Modifier.layoutId("buttonLogin")) {
            Text(text = "Login")
        }
    }

We need to apply the created constraints by adding layoutId modifier to the composable. For the first TextField(), I have applied the inputUsername constraints.

Output for the above code,

ConstraintSet

Guildline

A guideline is an invisible view through which we can constraint the composables. The guideline can be aligned using percentage, offset, and padding. As guidelines are invisible, we can create responsive UI using them.

Types of guidelines

  • createGuidelineFromTop – Creates a guideline at a specific offset/percentage from the top of the ConstraintLayout.
  • createGuidelineFromRight – Creates a guideline at a width offset/percentage from the right of the ConstraintLayout.
  • createGuidelineFromLeft – Creates a guideline at a width offset/percentage from the left of the ConstraintLayout.
  • createGuidelineFromBottom – Creates a guideline at a specific offset/percentage from the bottom of the ConstraintLayout.
        // guideline 20 percentage from top
        val guildLineFromTop = createGuidelineFromTop(0.2f)
        // guideline 20 Dp from top
        val guildLineFromTop = createGuidelineFromTop(20.dp)

Let’s create a guideline of 20 percentage from the top of the contraintlayout. and link the guideline to composable.

@Composable
fun ConstraintLayoutGuideLine() {

    ConstraintLayout(modifier = Modifier
        .fillMaxWidth()
        .height(100.dp)
        .padding(12.dp)) {

        val (input) = createRefs()
        val guildLineFromTop = createGuidelineFromTop(0.2f)

        Text(text = "GuildLine From top and start", modifier = Modifier.constrainAs(input) {
            top.linkTo(guildLineFromTop, 8.dp)
            start.linkTo(parent.start, 8.dp)
            end.linkTo(parent.end, 8.dp)
        } )
    }
}
Guildeline

Also, we can set multiple guidelines for the composable. Below, I have created two guidelines, one from the top of the parent layout and the other one is from the start of the parent layout.

@Composable
fun ConstraintLayoutGuideLine() {

    ConstraintLayout(modifier = Modifier
        .fillMaxWidth()
        .height(100.dp)
        .padding(12.dp)) {

        val (input) = createRefs()
        val guildLineFromTop = createGuidelineFromTop(0.2f)
        val guildLineFromStart = createGuidelineFromStart(50.dp)

        Text(text = "GuildLine From top and start", modifier = Modifier.constrainAs(input) {
            top.linkTo(guildLineFromTop, 8.dp)
            start.linkTo(guildLineFromStart, 8.dp)
        } )
    }
}

Barrier

A Barrier is similar to a Guideline. But the barrier has reference to the views that you wish to use to form a “barrier” against. If anyone of the views grows, the barrier will adjust its size to the largest height or width of the referenced items.

Types of barriers

We can create four different barriers.

  • createStartBarrier(vararg elements: ConstrainedLayoutReference)
  • createEndBarrier(vararg elements: ConstrainedLayoutReference)
  • createTopBarrier(vararg elements: ConstrainedLayoutReference)
  • createBottomBarrier(vararg elements: ConstrainedLayoutReference)

Let’s see in the example,

First, create a reference for the components.

        val (input1, input2, input3) = createRefs()

Next, create a barrier using the reference of the components.

        val barrier = createEndBarrier(input1, input2)

here, I have created a createEndBarrier referencing input1, and input2.

finally, add the barrier to another component, that always wants to be the end of the barrier.

@Composable
fun BarrierExample() {
    ConstraintLayout(modifier = Modifier
        .fillMaxWidth()
        .padding(12.dp)) {
        val (input1, input2, input3) = createRefs()

        val barrier = createEndBarrier(input1, input2)

        Text(text = "Iuput 1 Large", modifier = Modifier.constrainAs(input1) {
            top.linkTo(parent.top, 8.dp)
            start.linkTo(parent.start, 8.dp)
        } )

        Text(text = "Iuput 2", modifier = Modifier.constrainAs(input2) {
            top.linkTo(input1.bottom, 8.dp)
            start.linkTo(parent.start, 8.dp)
        } )

        Text(text = "Iuput 3", modifier = Modifier.constrainAs(input3) {
            top.linkTo(input1.bottom, 8.dp)
            start.linkTo(barrier, 8.dp)
        } )
    }
}

the output of the above code,

Barrier

Chains

Chains allow us to align the views vertically or horizontally and also control the space between them, along with how the views use the space.

Type of chains

createHorizontalChain(vararg elements: ConstrainedLayoutReference,chainStyle: ChainStyle =ChainStyle.Spread) -create horizontal chain.

createVerticalChain(vararg elements: ConstrainedLayoutReference,chainStyle: ChainStyle =ChainStyle.Spread) – create vertical chain.

ChainStyle

we have three different types of chain styles.

  • Spread : The views are evenly distributed.
  • Spread inside : The first and last views are aligned to the constraints on each end of the chain, and the rest are evenly distributed.
  • Packed : The views are packed together.

Let’s see the example,

First, I am creating three buttons with references inside a constaintLayout.

ConstraintLayout(modifier = Modifier
        .fillMaxWidth()
        .padding(12.dp)) {
        val (button1, button2, button3) = createRefs()
    ....
}

Next, I am creating a horizontal chain with all the button references. also, I am using Spread inside chain style.

@Composable
fun ChainExample() {
    ConstraintLayout(modifier = Modifier
        .fillMaxWidth()
        .padding(12.dp)) {
        val (button1, button2, button3) = createRefs()

        createHorizontalChain(button1, button2, button3, chainStyle = ChainStyle.SpreadInside)


        Button(onClick = { }, modifier = Modifier.constrainAs(button1) {
            centerHorizontallyTo(parent)
        }) {
            Text(text = "Button 1")
        }

        Button(onClick = { }, modifier = Modifier.constrainAs(button2) {
            centerVerticallyTo(parent)
        }) {
            Text(text = "Button 2")
        }

        Button(onClick = { }, modifier = Modifier.constrainAs(button3) {
            centerHorizontallyTo(parent)
        }) {
            Text(text = "Button 3")
        }
    }
}

chain output with spread inside style,

chain with spread inside

chain with spread style,

chain with spread

chain with packed style,

Chain with packed

That’s all about the constraintLayout.

You can download this example on GitHub.

Leave a Reply

Your email address will not be published. Required fields are marked *