More Complex Forms
Let’s look at a bit more elaborate example, found in the ConstraintForm
modules of the Java and Kotlin projects.
What We Want
We want to have a form that collects the first and last name of a person, with a Button
to submit the form. We are not reacting to that button click — the purpose of this sample is to see how the form is constructed.
This seems straightforward enough. For example, we can have a ConstraintLayout
that collects the first name and last name in two rows, with the Button
below the last name row.
However, if we limit ourselves to the sort of ConstraintLayout
anchoring rules that we have seen above, we would wind up with a layout that looks like this:
This works, but it would be nicer if we had columns for the labels and the fields:
Fortunately, ConstraintLayout
offers some features for helping us get this sort of look.
How We Get There
Our layout is more complex than the earlier example, owing in part to having five children of the ConstraintLayout
: two TextView
widgets, two EditText
widgets, and a Button
:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/container_padding">
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_barrier"
android:layout_marginEnd="@dimen/margin_barrier"
app:barrierDirection="end"
app:constraint_referenced_ids="labelFirst,labelLast" />
<TextView
android:id="@+id/labelFirst"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@id/firstName"
android:text="@string/label_first"
app:layout_constraintEnd_toStartOf="@id/barrier"
app:layout_constraintBaseline_toBaselineOf="@id/firstName"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/firstName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/barrier"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/labelLast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@id/lastName"
android:text="@string/label_last"
app:layout_constraintEnd_toStartOf="@id/barrier"
app:layout_constraintBaseline_toBaselineOf="@id/lastName"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/lastName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:layout_marginTop="@dimen/margin_row"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/barrier"
app:layout_constraintTop_toBottomOf="@id/firstName" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save_caption"
android:layout_marginTop="@dimen/margin_row"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/lastName" />
</androidx.constraintlayout.widget.ConstraintLayout>
If you look at that XML, there are six child elements of the ConstraintLayout
— we will explore that sixth one shortly.
Naming the Widgets
Each of the widgets gets a fairly simple android:id
:
-
firstName
andlastName
for the twoEditText
widgets -
firstLabel
andlastLabel
for the twoTextView
widgets -
button
for theButton
Since we are not doing anything with these widgets from our Java/Kotlin code, the widget IDs are purely for internal use within this layout resource.
Barrier
: You Shall Not Pass
The sixth child of the ConstraintLayout
is a Barrier
. Barrier
is one of a couple of options with ConstraintLayout
for creating a virtual anchor point: a place for you to reference in layout anchoring rules that is not itself a widget in the layout.
Specifically, this Barrier
has:
app:barrierDirection="end"
app:constraint_referenced_ids="labelFirst,labelLast"
Here, the “barrier direction” indicates where the barrier is established relative to the widgets referenced in the app:constraint_referenced_ids
attribute. In our case, the end
for app:barrierDirection
means “put this Barrier
at the end position of the child that is farthest from the start position”. Both of the TextView
widgets are set up to start from the start
edge of the ConstraintLayout
and take up as much room as their content requires (android:layout_width="wrap_content"
). As a result, the Barrier
is placed at the end of whichever of those two TextView
widgets is longest. In the sample app, that is the second label, which uses Last Name (Surname):
as the caption (courtesy of @string/label_last
). So, the Barrier
is placed where the labelLast
TextView
ends.
We then set up the firstName
and lastName
widgets to have their start
position be where the Barrier
is, via app:layout_constraintStart_toEndOf="@id/barrier"
. This is why both fields have the same starting position, and that position is determined by the longest of the labels.
As a result, our four main widgets — the two labels and the two fields — are organized into two columns, where the width of the first column is determined by the width of the labels inside of it.
Your Position Shows Some Bias
Our two labels do not have the same width. The labelLast
text was set up to be excessively long to illustrate this.
By default, if we just anchored the labels to the start
edge of the ConstraintLayout
, their text would wind up on that edge. That is a bit awkward, though, in that our labelFirst
text would wind up relatively far away from its associated field.
To address this, our layout has each label do two things:
- Constrain both the
start
andend
edges, where theend
is anchored to theBarrier
viaapp:layout_constraintEnd_toStartOf="@id/barrier"
- Shove the text to the
end
side viaapp:layout_constraintHorizontal_bias="1.0"
Bias, in terms of ConstraintLayout
, means “if there is extra room, slide the widget in this direction along the axis”. The default bias is 0.5, meaning that the widget is centered in the available space. For the horizontal axis, 0.0 means “slide the widget all the way towards the start
side” and 1.0 means “slide the widget all the way towards the end
side”.
By having the label end at the Barrier
and having its associated field start at the Barrier
, we ensure that the label and the field are close together.
Declaring the Rows
In terms of the rows, the fields drive the vertical positioning:
- The top of
firstName
is anchored to the top of theConstraintLayout
viaapp:layout_constraintTop_toTopOf="parent"
- The top of
lastName
is anchored to the bottom offirstName
viaapp:layout_constraintTop_toBottomOf="@id/firstName"
- The top of
button
is anchored to the bottom oflastName
viaapp:layout_constraintTop_toBottomOf="@id/lastName"
- The labels have their baselines aligned with their associated fields
In addition:
- Each label has
android:labelFor
set, pointing to its associated field, for the benefit of screen readers and other assistive technologies - The
Button
hasapp:layout_constraintEnd_toEndOf="parent"
and nostart
-side anchor, so it will be flush on the end side of theConstraintLayout
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.