Chapter 4. Constraint Layouts: Draw Up a Blueprint

image

You don’t build a house without a blueprint.

And some layouts use blueprints to make sure they look exactly the way you want. In this chapter, we’ll introduce you to Android’s constraint layout: a flexible way of designing more complex UIs. You’ll discover how constraints and bias let you position and size your views, irrespective of screen size and orientation. You’ll find out how to keep views in their place with guidelines and barriers. Finally, you’ll learn how to pack or spread views with chains and flows. Let’s get designing…

Nested layouts revisited

In the previous chapter, you learned that you can nest layouts to build more complex screens. The following code, for example, uses nested linear layouts to display a text view and edit text in a horizontal row, with an edit text below them:

image

Nesting layouts comes at a price

The downside to nested layouts is that building complex layouts in this way can be inefficient, make your code harder to read and maintain, and can also slow down your app.

When Android displays a layout on the device screen, it first checks the structure of the layout file, and uses this to build a hierarchy of views. For the nested layout shown on the previous page, for example, it builds a hierarchy of views that contains two linear layouts, two edit texts, and a text view:

image

Android uses the view hierarchy to help it figure out where each view should be placed on the device screen. Each view needs to be measured, laid out, and drawn on the screen, and Android needs to make sure each view has enough space for its contents, and that any weights are taken into account.

If the layout contains nested layouts, the view hierarchy is more complex, and Android may need to make multiple passes in order to figure out how views need to be arranged. If the layouts are deeply nested, this can lead to bottlenecks in your code, and can leave you with a mass of code that’s difficult to read and maintain.

If you have a more complex UI like this, an alternative to using nested layouts is to use a constraint layout.

Each view in the layout needs to be initialized, measured, laid out, and drawn. In deeply nested layouts, this can slow down your app.

Introducing the constraint layout

A constraint layout is more complex than a linear or frame layout, but it’s way more flexible. It’s also much more efficient for complex UIs as it gives you a flatter view hierarchy, which means that Android has less processing to do at runtime.

You design constraint layouts VISUALLY

Another advantage of using constraint layouts is that they’re specifically designed to work with Android Studio’s design editor. Unlike linear and frame layouts where you usually hack directly in XML, you build constraint layouts visually. You drag and drop views into the design editor’s blueprint, and give it instructions for how each view should be displayed:

Use a constraint layout to build flexible UIs without nesting layouts.

image

Unlike linear and frame layouts, constraint layouts are part of a suite of libraries known as Android Jetpack. You may have already heard something about Jetpack, but what is it?

Constraint layouts are part of Android Jetpack

Android Jetpack is a collection of libraries that help you follow best practice, reduce boilerplate code, and make your coding life easier. It includes constraint layouts, navigation, the Room persistence library (which helps you build databases) and lots, lots more.

Here are some of our favorite Jetpack components; you’ll find out how to use them in later chapters:

image

Another great thing about Jetpack is that it lets you write code that works consistently across new and old versions of Android. This is great news for your users, as it means that you can include exciting new Android features that will work on older devices.

An example of this is AppCompatActivity, which you’ve already been using to write activity code. We didn’t mention it earlier, but AppCompatActivity is part of Android Jetpack. It adds new features to activities across new and old versions of Android without you having to worry about backward compatibility.

Note

Yes! You’ve already been using part of Android Jetpack without knowing it. You’ll learn more about using Jetpack through the rest of this book.

In this chapter, you’re going to learn how to use constraint layouts. Let’s run through what we’re going to do.

Here’s what we’re going to do

We’re going to break learning about constraint layouts into two main parts:

  1. How to position and size a single view.

    You’ll learn how to use constraints and bias to control how and where a single view is shown in its layout.

    image
  2. How to position and size multiple views.

    You’ll then apply your knowledge to multiple views, and learn more advanced techniques using guidelines, barriers, chains, and flows.

    image

Create a new project

We’re going to use a new project for the app we’re going to build, so create one now using the same steps you used in the previous chapters. Choose the Empty Activity option, enter a name of “My Constraint Layout” and a package name of “com.hfad.myconstraintlayout”, and accept the default save location. Make sure the language is set to Kotlin and the minimum SDK is API 21 so it will run on most Android devices.

Now that we’ve created the project, let’s make sure that it has been set up to use constraint layouts.

Use Gradle to include Jetpack libraries

image

To make sure that all the Jetpack libraries—including constraint layouts—work across all versions of Android, they’re not included in the main Android SDK. Instead, you have to add any libraries you need using Gradle. This is a build tool that’s used to compile code, configure apps, and fetch any extra libraries that your project requires.

Every time you create a new project, Android Studio creates two Gradle files named build.gradle.

The first version of build.gradle lives in the project folder, and specifies the basic settings of your app, such as what version of the Gradle plug-in to use.

The second version of build.gradle lives in the project’s app folder. It’s where the majority of the app’s properties are set, such as the API level.

Behind the scenes, every Android Studio project uses Gradle as its build tool.

The project build.gradle needs a Google repository line

Every project needs to know where to find any extra Jetpack libraries it needs, and this is done by adding a reference to the Google repository in the project’s build.gradle file. Android Studio usually does this for you, but you can make sure it’s there by opening the file MyConstraintLayout/build.gradle, and looking for the following line (in bold) in the repositories section under allprojects:

image

The app build.gradle includes the constraint layout’s library

To use constraint layouts, a reference to its library needs to be included in the app’s build.gradle file. Android Studio should have already added this for you, but you can double-check by opening the file MyConstraintLayout/app/build.gradle, and looking for the following line (in bold) in the dependencies section:

image

If the file doesn’t include this line, add it now, and click on the Sync Now option that appears in the code editor. This syncs any changes you’ve made with the rest of your project, and adds the library.

Let’s add a constraint layout to activity_main.xml

Now that your project is all set up to use constraint layouts, let’s start using one.

You add a constraint layout to a layout file using an <androidx.constraintlayout.widget.ConstraintLayout> element. We’re going to use one in the layout file activity_main.xml, so open this file in the app/src/main/res/layout folder, and make sure its code looks like this:

image

Show the layout in the blueprint

We’re going to add views to the layout using the design editor’s blueprint. Switch to the design editor by clicking on the Design option, click on the Select Design Surface button in the editor’s toolbar, and select the Blueprint option. This shows you a blueprint of the layout like this:

image
image

Add a button to the blueprint

We’re going to add a button to the layout. To do this, go to the design editor’s palette, find the Button component (it’s usually in the Common section), and drag it to the blueprint. You can place the button anywhere in the blueprint, just so long as it appears in its main area like this:

image

Position views using constraints

With a constraint layout, you don’t specify where views should be positioned by dropping them on the blueprint in a particular place. Instead, you specify placement by defining constraints. A constraint is a connection or attachment that tells the layout where the view should be positioned. You can use a constraint to attach a view to the start edge of the layout, for example, or underneath another view.

We’ll add a horizontal constraint to the button

To see how this works, let’s add a constraint to attach the button to the left edge of the layout.

First, make sure the button’s selected by clicking it. When you select a view, a bounding box is drawn around it, and handles are added to its corners and sides. The handles in the corners let you resize the view, and the handles on the sides let you add constraints:

image

To add a constraint, you click on one of the view’s constraint handles and drag it to whatever you want to attach it to. In this case, we’re going to attach the left edge of the button to the left edge of the layout, so click on the left constraint handle and drag it to the left edge of the blueprint:

image

This adds the constraint, and pulls the button over to the left:

image

That’s how you add a horizontal constraint. Let’s see what happens when you add a vertical one.

Add a vertical constraint too

We’ll use a vertical constraint to attach the button to the top of the layout. To do this, click on the button’s top constraint handle, and drag it to the top of the blueprint. This adds the vertical constraint, which pulls the button up.

image

Use opposing constraints to center views

As you’ve learned, you can use constraints to attach a view to the edge of the blueprint. Each constraint works like a spring that pulls the view to the blueprint’s edge.

If you want to position views in the center of the blueprint, you can do so by adding constraints to opposite sides of the view. To center a button horizontally, for example, you add one constraint that pulls the view to the left, and another that pulls it to the right like this:

image

The two constraints pull the button in opposite directions, which centers it horizontally like so:

image

You can also center a view vertically by adding constraints to its top and bottom edges. And if you wanted to center it horizontally and vertically, you’d add constraints to all four edges like this:

image

You can delete constraints you no longer need

You can remove any constraints you no longer need by selecting them in the blueprint, and deleting them. If you have a button that’s centered in the middle of the blueprint, for example, you delete the constraint that’s attached to its bottom edge:

image

Deleting this constraint means that the button is no longer being pulled toward the bottom of the blueprint. The top constraint pulls the button to the top so that it’s only centered horizontally, and not vertically:

image

Another way of removing constraints you no longer need is to use the constraint widget tool. Let’s see how this works.

Remove constraints with the constraint widget

The constraint widget is displayed in the Attributes panel at the side of the design editor. It appears when you select a view, and displays a diagram featuring the view’s constraints, and the size of any margins.

Note

You’ll find out more about the Attributes panel a few pages ahead.

To delete a constraint in the constraint widget, select the view in the blueprint you want to remove the constraint from, then click on the constraint’s handle in the constraint widget. The constraint is removed, and the view is repositioned in the blueprint.

image

You can use it to add margins too

You may have noticed that each of the constraints in the constraint widget has a number next to it. This is used to set the margin size for that edge of the view so that there’s space between the view and the layout’s edge. To change the size of a view’s left and top margins to 24dp, for example, you’d update their values in the diagram to 24:

image
image

You can set a default size for any new margins using the Default Margins button in the design editor’s toolbar. Setting this to 24dp, for example, means that any new constraints that get added will automatically include a margin of 24dp.

image

Changes to the blueprint appear in the XML

When you add views to a blueprint, and specify constraints and margins, they get added to the layout’s underlying XML. To see this, switch to the layout’s code view. Your code should look something like this (but don’t worry if it’s slightly different):

image

As you can see, the XML now includes a button. Does its code look familiar to you? If so, nice catch—it includes attributes that you learned about in Chapter 3.

The button’s width, height, and margins are specified in exactly the same way as before, and if you want to, you can change their values in the XML instead of using the design editor.

The only unfamiliar code is the two lines that specify the view’s constraints on its start and top edges:

image

Similar code is generated if you add constraints to the button’s remaining edges.

Now that you’ve had a glimpse of what constraint layout XML looks like switch back to the design editor, and we’ll look at some more techniques you can use to position views.

Views can have bias

As you learned earlier, you can add constraints to opposite sides of a view. This centers the view by default, but you can also control its position relative to each side by changing its bias. This tells Android what the proportionate length of each constraint should be on either side of the view.

To see this in action, let’s change the button’s horizontal bias so that it’s positioned off-center. First, make sure that the button includes constraints on its left and right sides like this:

image

Then select the button so the constraint widget is displayed.

Underneath the widget’s diagram of the view, you should see a slider with a number in it. This is a percentage of the view’s horizontal bias.

image

To change the bias, simply move the slider. If, say, you move the slider to the left so that the number changes to 30, it moves the button in the blueprint to the left as well:

image

The view maintains this relative position irrespective of screen size and orientation. Let’s try this out by taking the app for a test drive.

Images Test Drive

When we run the app, a button appears off-center toward the top of the screen. It maintains the same relative position when we rotate the device.

image

You’ve now learned various techniques to control a view’s position on the screen. Next up, how to change its size.

You can change a view’s size

As you might expect, you can change a view’s size in a constraint layout by updating its layout_width and layout_height attributes. You can do this in the layout’s XML, or in the design editor’s Attributes panel.

The Attributes panel is displayed to the side of the blueprint. When you select a view, it shows you all the attributes that have already been declared (such as layout_width and layout_height), and lets you set ones that haven’t.

Make the view just big enough

Just like with linear and frame layouts, you make a view just large enough to display its contents by setting its layout_width and layout_height properties to wrap_content. If the view is a button, for example, it makes the button just large enough to hold its text:

image

Match the view’s constraints

If you’ve added constraints to opposite sides of your view, you can make the view match the size of its constraints. You do this by setting its layout_width and/or layout_height to 0dp: set layout_width to 0dp to make the view match its horizontal constraints, and set layout_height to 0dp to make it match its vertical ones.

In the example below, we’ve set the button’s layout_width to 0dp so that the button matches its horizontal constraints:

image

Now that you’ve seen how to resize a view, try experimenting with the different techniques, then have a go at the exercise on the next page.

BE the Constraint

image

Your job is to play like you’re the constraint layout and draw the constraints that are needed to produce each layout. You also need to specify the layout_width, layout_height, and bias (when needed) for each view. We’ve completed the first one for you.

image
image

BE the Constraint Solution

image

Your job is to play like you’re the constraint layout and draw the constraints that are needed to produce each layout. You also need to specify the layout_width, layout_height, and bias (when needed) for each view. We’ve completed the first one for you.

image
image

Most layouts need multiple views

image

So far you’ve seen how to position and size a single view in a constraint layout. Most of the time, however, your layout will need to contain multiple views that are laid out relative to one another.

To see how this works, first make sure that your constraint layout includes a single button with two constraints: one that connects its top edge to the top of the blueprint, and another that connects its left edge to the blueprint’s left side. Its layout_width and layout_height properties should be set to wrap_content, and the margins for these edges should be set to 24dp.

After you’ve made these changes, the button should be positioned in the blueprint’s top-left corner like this:

image

Add a second button to the blueprint

Next, add a second button to the blueprint by dragging one from the palette, and placing it somewhere underneath the first button like this:

image

The blueprint now includes two buttons. Let’s find out how to position them relative to one another.

You can connect views to other views

As you already know, constraints let you attach a view to the edge of its blueprint. You can also use constraints to connect two views together, and this is used to specify how they should be displayed relative to one another.

To see how this works, select the second button in the blueprint, then draw a constraint that goes from the second button’s top edge to the first button’s bottom edge like this:

image

When the constraint is added, it pulls the button up so that it’s connected to the first, and displayed underneath it:

image

The constraint means that the second button will always be positioned under the first, irrespective of the first button’s position on the device screen.

Once you’ve positioned two views in this way, the next thing you might want to do is make sure that they’re aligned. Let’s find out how to do this.

You can align views too

The simplest way of aligning two views is to use the Align button in the design editor’s toolbar.

To see how this works, let’s left-align the two buttons in the blueprint so that their left edges line up. First, select both buttons by holding down the Shift key as you click on each one. Then click on the Align button to open up a set of alignment options like this:

image

Click on the Left Edges option to left-align the two buttons. This adds a constraint to the blueprint that connects their left edges together like this:

image

Align views using guidelines

Another technique you can use to align views is a guideline. This is a fixed line you add to the blueprint that you can use to constrain views. It’s only visible in the design editor, so users don’t see it when they run the app.

Let’s explore how guidelines work by adding one to the blueprint. Click on the Guidelines button in the design editor’s toolbar, and choose the option to add a vertical guideline. This places a vertical guideline in the blueprint:

image

Once the guideline has been added, you can move it elsewhere by dragging it. You can set it to be either a fixed distance from the blueprint’s edge, or a fixed percentage:

image

You can then use constraints to attach views to the guideline like this:

image

Guidelines have a fixed position

Guidelines are either positioned a fixed distance from the blueprint’s edge, or a fixed percentage between the two. They stay in that position when the app runs, so they’re a useful way of aligning views.

In some situations, you need something that’s a little more flexible. As an example, suppose you have a layout that includes two multi-line edit texts, side by side, with a button underneath like this:

image

The edit texts expand vertically as the user enters text. You want the button to move as the views change size so that it’s always positioned beneath them like so:

image

So how can you build this sort of layout?

Create a movable barrier

To create layouts like this, you can use a barrier. This is like a guideline, except that it doesn’t have a fixed position. Instead, it forms a barrier against views, and moves when they change size. This repositions any views that are constrained to the barrier.

In the example on the previous page, the two edit texts are placed above a horizontal barrier, and the button is constrained beneath it. As the edit texts expand, the barrier moves down and repositions the button:

image

Let’s build a layout that uses a barrier

To see how barriers work, let’s create this example.

First, delete any views, and make sure the blueprint includes a vertical guideline positioned at 50%. Then drag two multi-line edit texts from the palette, and position them on either side of the guideline.

Note

You can usually find these in the “Text” part of the palette, listed as “Multiline Text.”

Next, add vertical constraints to constrain each view to the top of the blueprint, and horizontal constraints to position each one between the blueprint’s edge and the guideline.

Finally, change the layout_width of each edit text to 0dp so that it matches its horizontal constraints, and set their layout_heights to “wrap_content” so that the views can expand.

When you’re done, the blueprint should look something like this:

image

Add a horizontal barrier

We need to add a horizontal barrier to the blueprint. To do this, click on the Guidelines button in the design editor toolbar, and choose the option to add a horizontal barrier:

image

This creates the horizontal barrier.

Place the barrier beneath the views

We want the barrier to move down as the two edit text views expand. To do this, go to the layout’s component tree panel, and drag the two edit text components onto the barrier:

image

This doesn’t change the position of the edit text views in the blueprint: it simply tells the barrier that it needs to move with these views.

Next, we need to position the barrier so that it’s at the bottom of the two views. Select the barrier in the component tree, and use the Attributes panel to change its barrierDirection attribute to “bottom.” This positions the barrier below the two edit texts so that the blueprint looks like this:

image

Constrain a button under the barrier

Now that the layout’s barrier is in place, let’s add the button, and constrain it to the barrier so that it moves down as the edit text views expand.

First drag a button from the palette to the blueprint, and place it somewhere underneath the barrier. Then center it horizontally by adding two horizontal constraints that connect the button’s sides to the edges of the blueprint like this:

image

Next, you need to attach the top of the button to the barrier. You can try doing this by drawing the constraint directly in the blueprint. If, like us, you find this a bit too fiddly, select the button, search for its layout_constraintTop_toBottomOf attribute in the Attributes panel, and change its value to the barrier’s ID (in our case, this is @id/barrier).

image

After you make this change, the blueprint should look something like this:

image

As barriers can be quite tricky to work with at first, we’ll show you our complete XML over the next couple pages, and then take the app for a test drive.

The full code for activity_main.xml

Here’s our complete code for activity_main.xml; if you want your blueprint to look like ours, replace the contents of this file so that it matches the code shown here:

image
image

Images Test Drive

When we run the app, a button is displayed beneath two edit text views. The button moves down when we type into each edit text, and the views expand.

As you can see, adding a barrier is a bit more complicated than drawing constraints and aligning views, but we think it’s worth the extra effort.

So what’s next?

image

Use a chain to control a linear group of views

You’ve now learned how to connect and align views, and use guidelines and constraints. But what if you want to create a row or column of views, and evenly space them out?

In this sort of situation, you can use a chain. This is a linear group of views that are linked together with bidirectional constraints. The chain controls each view’s position, so you can use it to evenly space the views out, or pack them in the center of the blueprint.

We’re going to create a horizontal chain

To see how this works, we’re going to create a chain that controls the position of three buttons. The buttons will be lined up in a horizontal row, and evenly spaced between each side of the blueprint like this:

image

When the app runs, the buttons will maintain their relative positions, regardless of screen size or orientation:

image

Let’s find out how to create a chain.

The chain will use three buttons

Before we create the chain, first remove all of the constraints that have been added to the blueprint so far. The quickest way of doing this is with the Clear All Constraints button in the design editor’s toolbar, so click on this button now.

image

You also need to get rid of any guidelines, barriers, and edit text views. Do this by selecting each one, and deleting it.

Then add two more buttons to the blueprint so there are three in total, and use the “Orientation for Preview” button in the design editor’s toolbar to change the blueprint’s orientation to landscape. This will make it easier to see the chain.

image

When you’ve added the buttons, the blueprint should look something like this:

image

Align the views we’re going to chain

Chains work best when views are aligned. First, add a constraint that connects the first button to the top of the blueprint, and set its margin to 64. Then select all three buttons, and use the Align button in the design editor’s toolbar to align their top edges. The blueprint should look like this.

image

Now that the buttons are nicely aligned, let’s go ahead and create the chain.

Create the horizontal chain

To create the chain, select all three buttons, then right-click on one of them. From the menu that appears, choose the Chains option, followed by Create Horizontal Chain.

image

When the horizontal chain is created, it joins the buttons together, and fastens the first and last view to the blueprint’s vertical edges. The chain should look something like this:

image

By default, views in a chain are evenly spaced out between the blueprint’s edges. You can change this behavior by right-clicking on one of the chain’s views, selecting the Chains option from the menu that appears, and then choosing Horizontal Chain Style.

Possible chain style options include spread, spread inside, and packed. See if you can work out what these options do by having a go at the following exercise.

There are different styles of chain

As you have discovered, you can choose different chain styles to change how a chain arranges its views.

Spread spaces out views between the blueprint’s edges

The default style is spread. This is used to evenly distribute the views between the blueprint’s edges like this:

image

Spread inside moves the first and last view to the edges

The spread inside style is similar to spread, except that it moves the first and last view to the blueprint’s edges. It then evenly spaces out any remaining views like so:

image

Packed moves the views together

The packed style is used to pack views together. It then centers the entire group of views like this:

image

Now that you’ve seen what these options do, let’s take the app for a test drive.

Images Test Drive

When we use the spread style of chain and run the app, the buttons are evenly spread in the device screen. This is irrespective of screen orientation.

image

Always test layouts on a variety of device sizes and orientations to make sure they look and behave how you want.

image

She’s right.

Constraint layouts can include both horizontal and vertical chains, and a single view can belong to both types. You can use this to arrange views in a grid.

The blueprint above, for example, shows six buttons arranged in a grid. Each row is a horizontal chain, and the leftmost buttons form a vertical chain:

Another way in which you can create grids is to use a flow. Let’s find out what this is, and how to use it.

A flow is like a multi-line chain

A flow is like a chain that can span multiple rows. It’s invaluable when, say, you want to display lots of views in a row, but they might not fit on the screen for some screen sizes or orientations.

As an example, suppose you have a chain that displays six buttons in a horizontal row. When the orientation is landscape, they are displayed like this:

image

But when the orientation is changed to portrait, there’s not enough room to display all of the views:

image

If you replace the chain with a flow, any views that can’t fit on the first row will flow onto a second row like this:

image

Let’s see how flows work by building the above layout.

How to add a flow

First, remove any constraints by clicking on the Clear All Constraints button in the design editor’s toolbar. Then add extra buttons to the blueprint so there are six in total like this:

image

Next, select all of the buttons, click on the Guidelines button in the design editor’s toolbar, and choose the Flow option. This adds the flow component.

We now need to tweak the flow component’s settings to make it behave the way we want. To do this, select the flow in the component tree, then use the blueprint or constraint widget to add constraints to connect its sides and top to the edges of the blueprint. Change its layout_width attribute to “0dp” so that it matches its constraints. Finally, search for its flow_wrapMode attribute in the Attributes panel, and set this to “chain”.

When you’ve made all of these changes, the blueprint should look something like this when the orientation is landscape:

image

If you change the orientation to portrait, the blueprint should look like this instead:

image

Once you’ve created a flow, you can tweak the way in which it displays its views. Let’s see how.

You can control the flow’s appearance

The main way in which you can alter the flow’s appearance is with its flow_wrapMode attribute.

Use “chain” to create a multi-line chain

If you set the flow_wrapMode attribute to chain, the flow behaves like a flexible chain that lets its views flow onto extra rows.

With this option, you can make further changes to the flow’s appearance by changing the value of its flow_horizontalStyle attribute. Possible options for this attribute are spread, spread inside, and packed. These have exactly the same effect as when you used them with chains. The packed option, for example, packs the views together like this:

image

Use “aligned” to line up the views

If you set the flow_wrapMode attribute to aligned, the views flow onto extra rows, and they are lined up like this:

image

You can also set the flow_wrapMode attribute to none or leave it unset. This makes the flow behave like a normal chain so that its views don’t flow onto a second row.

The full code for activity_main.xml

Flows can sometimes be a little tricky to get right, so here’s our full code for activity_main.xml; if you want to, replace the code for this file so it matches the code shown here:

image
image

Let’s take the app for a test drive.

Images Test Drive

When we use the chain style of flow and run the app, the buttons are evenly spread in the device screen when the orientation is landscape.

When we change the orientation to portrait, any buttons that don’t fit onto the first row flow onto the second.

image

Congratulations! You’ve now learned how to design super-flexible screens using constraint layouts. As well as looking and behaving the way you want, they don’t use nested layouts, so they’re extremely efficient.

Before you move on to the next chapter, why not put your new skills into practice, and try experimenting with some of the techniques you’ve learned?

Your Android Toolbox

image

You’ve got Chapter 4 under your belt and now you’ve added constraint layouts to your toolbox.

image

Get Head First Android Development, 3rd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.