Building a precise UI with ConstraintLayout
Open the ConstraintLayout
that was auto-generated when we created the project. It is probably already in a tab at the top of the editor. If not, it will be in the res
/layout
folder. Its name is activity_main.xml
.
Inspect the XML in the Text tab and note that it is empty, apart from a TextView
that says Hello World
. Switch back to the Design tab, left-click the TextView
to select it, and tap the Delete key to get rid of it.
Now we can build ourselves a simple, yet intricate, UI. ConstraintLayout
is very useful when you want to position parts of your UI very precisely and/or relative to the other parts.
Adding a CalenderView
To get started, look in the Widgets category of the palette and find the CalenderView
. Drag and drop the CalenderView
near the top and horizontally central. As you drag the CalenderView
around, notice that it jumps/snaps to certain locations.
Also notice the subtle visual cues that show when the view is aligned. I have highlighted the horizontally central visual cue in the following screenshot:
Let go when it is horizontally central, as it is in the screenshot. Now, we will resize it.
Resizing a view in a ConstraintLayout
Left-click and hold one of the corner squares that are revealed when you let go of the CalenderView
, and drag inwards to decrease the size of the CalenderView
:
Reduce the size by about half and leave the CalenderView
near the top, horizontally centered. You might need to reposition it a little after you have resized it, a bit like the following diagram:
You do not need to place the CalenderView
in exactly the same place as me. The purpose of the exercise is to get familiar with the visual cues that inform you where you have placed it, not to create a carbon copy of my layout.
Using the Component Tree window
Now look at the Component Tree window – the one to the left of the visual designer and below the palette. The component tree is a way of visualizing the layout of the XML, but without all the details.
In the following screenshot, we can see that the CalenderView
is indented to the right of the ConstraintLayout
, and is therefore a child. In the next UI we build, we will see that we sometimes need to take advantage of the Component Tree to build the UI.
For now, I just want you to observe that there is a warning sign by our CalenderView
. I have highlighted it in the following screenshot:
The error says This view is not constrained. It only has designtime positions, so it will jump to (0,0) at runtime unless you add the constraints. Remember when we first added buttons to the screen in Chapter 2, Kotlin, XML, and the UI Designer, that they simply disappeared off to the top-left corner?
Tip
Run the app now and click on the Load ConstraintLayout button if you want to be reminded of this problem.
Now, we could fix this by clicking the Infer constraints button that we used in Chapter 2, Kotlin, XML, and the UI Designer. Here it is again as a reminder:
But learning to add the constraints manually is worthwhile because it offers us more options and flexibility. And, as your layouts become more complex, there is always an item or two that doesn't behave as you want it to, and fixing it manually is nearly always necessary.
Adding constraints manually
Make sure that the CalenderView
is selected and observe the four small circles at the top, bottom, left, and right:
These are the constraint handles. We can click and drag them to anchor them with other parts of the UI or the sides of the screen. By anchoring the CalenderView
with the four edges of the screen, we can lock it into position when the app is run.
One at a time, click and drag the top handle to the top of the design, the right to the right of the design, the bottom to the bottom of the design, and the left to the left of the design.
Observe that the CalenderView
is now constrained in the center. Left-click and drag the CalenderView
back to the upper part of the screen somewhere, as in the following diagram. Use the visual cues (also shown in the following screenshot) to make sure the CalenderView
is horizontally centered:
At this stage, you could run the app and the CalenderView
would be positioned as shown in the preceding screenshot.
Let's add a couple more items to the UI and see how to constrain them.
Adding and constraining more UI elements
Drag an ImageView
from the Widgets category of the palette and position it below and to the left of the CalenderView
. When you place the ImageView
, a pop-up window will prompt you to choose an image. Select Project | ic_launcher, and then click OK.
Constrain the left-hand side of the ImageView
and the bottom of the ImageView
to the left and bottom of the UI, respectively. Here is the position you should be in now:
The ImageView
is constrained in the bottom-left corner. Now, grab the top constraint handle on the ImageView
and drag it to the bottom constraint handle of the CalenderView
. This is now the current situation:
The ImageView
is only constrained horizontally on one side, so is pinned/constrained to the left. It is also constrained vertically and equally between the CalenderView
and the bottom of the UI.
Next, add a TextView
to the right of the ImageView
. Constrain the right of the TextView
to the right of the UI and constrain the left of the TextView
to the right of the ImageView
. Constrain the top of the TextView
to the top of the ImageView
and constrain the bottom of the TextView
to the bottom of the UI. Now you will be left with something resembling the following diagram:
Notice that all the warnings in the Component Tree window about unconstrained items are gone.
Note
There are warnings about hardcoded strings because we are adding text directly to the layout instead of the strings.xml
file and a warning about missing the contentDescription attribute. The contentDescription attribute should be used to add a textual description so that visually impaired users can get a spoken description of images in the app. For the sake of making rapid progress with the ConstraintLayout
, we will ignore these two warnings. We will look at adding string resources correctly in Chapter 18, Localization, and you can read about accessibility features in Android Studio on the Android developer's website, at https://developer.android.com/studio/intro/accessibility.
You can move the three UI elements around and line them up neatly, just how you want them. Notice that when you move the ImageView
, the TextView
moves with it because the TextView
is constrained to the ImageView
. But also notice that you can move the TextView
independently, and wherever you drop it, this represents its new constrained position relative to the ImageView
. Whatever an item is constrained to, its position will always be relative to that item. And, as we have seen, the horizontal and vertical constraints are distinct from each other. I positioned mine as shown in the following diagram:
Tip
ConstraintLayout
is the newest layout type, and, while it is more complex than the other layouts, it is the most powerful, as well as the one that runs the best on our user's device. It is worth spending more time looking at some more tutorials about ConstraintLayout
. Especially look on YouTube, as video is a great medium to learn about tweaking ConstraintLayout
. We will return to ConstraintLayout
throughout the book, and you do not need to know any more than we have covered already to be able to move on.
Making the text clickable
We are nearly done with our ConstraintLayout
. We just want to wire up a link back to the main menu screen. This is a good opportunity to demonstrate that TextView
(and most other UI items) are also clickable. In fact, clickable text is probably more common in modern Android apps than conventional-looking buttons.
Change the text
attribute of the TextView
to Back to the menu
. Now, find the onClick
attribute and enter loadMenuLayout
.
Now, add the following function to the MainActivity.kt
file just after the loadTableLayout
function, as highlighted here:
fun loadTableLayout(v: View) { //setContentView(R.layout.my_table_layout) } fun loadMenuLayout(v: View) { setContentView(R.layout.main_menu) }
Now, whenever the user clicks the Back to the menu
text, the loadMenuLayout
function will be called and the setContentView function
will load the layout in main_menu.xml
.
You can run the app, and click back and forth between the main menu (LinearLayout
) and the CalenderView
widget (ConstraintLayout
).
Let's build the final layout for this chapter.