ConstraintLayout in the LIMELIGHT

Rami Jemli
33 min readDec 19, 2018

Remember the times when we looked at design mock-ups, and we had to rack our brains about what combinations of view groups (RelativeLayout, LinearLayout, etc.) to use to get that high performing pixel perfect UX?!

At real life situations, it even got worse when we had to resort to nesting view groups just for the sake of design and deadlines.

Well, those times are over. Today, we have ConstraintLayout! 😎

Google introduced the library in its 2016 Google I/O conference. It allows us to express complex UI in a very explicit, productive and flexible manner. It’s definitely the holy grail for android developers.

The Android team did a great job channeling ConstraintLayout’s power through the layout editor’s visual tools. However, in this extensive article, we will stick to old school XML, and Java/kotlin code for educational purposes.

The purpose of this article is to make one complete source of information about ConstraintLayout. Even if you are familiar with the library, you will definitely still learn something new. So, this article will include 17 parts in which I will cover meticulously every feature, and possible tricks of the library in detail:

  1. What’s with all the hype?
  2. Gradle integration
  3. It’s all about Constraints
  4. Relative positioning
  5. Centering
  6. Dimension rules
  7. Margins
  8. Gone behavior
  9. Biasing constraints
  10. Dynamic constraints
  11. Chains
  12. Virtual helpers
  13. Aspect ratio
  14. Percentage based layout
  15. Circular positioning
  16. Keyframe animations
  17. Placeholders

It’s a long journey, but, totally worth it.

Brace yourselves. Let’s go!

1. What’s with all the hype?

ConstraintLayout was praised by Google and gained a lot of interest among developers. This is due to the fact that it makes it possible to structure large and complex layouts with a flat view hierarchy out of the box (no nested view groups). In some cases, it can even reduce layout overdraw.

Two layout files of the same UI design

In the example above, we notice already how flat and concise is the ConstraintLayout approach. As for the old approach(dark ages), it has a lot of cons:

  • Nested view groups: We have two levels of nested view groups, and as view groups require calculating all the positions and sizes of its child views, every level will have to go through the calculation process again which will make the layout take much more time to render. As a result, this will chip away at our frame time (16 ms per frame) and users will start to see lag. This is detrimental for layout performance.
  • Maintaining layout is hard: Whenever we want to change a single view in the layout, we will have to update other views’ positions and dimensions accordingly to maintain a valid layout. Furthermore, handling changes from Java is inconvenient.
  • Limited animation behavior: Dealing with animations and transitions is restrained and not easy as we have to deal with clipped areas imposed by the nested view groups.

No matter how we look at it, the old way has many handicaps. On the other hand, ConstraintLayout was built with performance as the main focus. It allows creating layouts in a much more straightforward way, and it’s deeply integrated with Android studio. With its many interesting features at our disposal, we get all the benefits of the existent view groups (RelativeLayout, LinearLayout, etc.) and more!

2. Gradle integration

To use ConstraintLayout in your project, proceed as follows:

We have to declare maven.google.com repository in our project level build.gradle file:

repositories {
google()
}

Dependency should be declared in our app module level build.gradle file:

dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
//OR use the AndroidX build artifact
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}

3. It’s all about CONSTRAINTS

Defining constraints is the building block of ConstraintLayout. A constraint describes the relationship between two widgets (or a widget and its parent) and controls how those widgets will be laid out within the layout.

View’s anchor points highlighted in white

In ConstraintLayout, each view has what is called anchor points.

  • Top, left/start, bottom, right/end (Not the size handles in corners)
  • Center point (centerX, centerY)
  • Baseline (for text-based widgets only)

These anchor points operate as a source or a target for constraints.

4. Relative positioning

Using relative positioning constraints, we can position a view relative to another one or the parent view group horizontally or vertically:

  • Horizontal Axis: left/start and right/end sides.
  • Vertical Axis: top, bottom sides and text baseline.

A relative positioning constraint in XML has to follow this format:

app:layout_constraint[SourceAnchorPoint]_to[TargetAnchorPoint]Of="[Target Id OR parent]"

Or, to make it simpler:

app:layout_constraint[Source Side]_to[Target Side]Of="[Target Id OR parent]"

The possible values in [Source Side] and [Target Side] are left/start, top, right/end, bottom, baseline.

In fact, the different combinations can help us have different positioning behaviors:

  • layout_constraintTop_toTopOf: View aligning top to top with another one or with the parent view group.
  • layout_constraintBottom_toBottomOf: View aligning bottom to bottom with another one or with the parent view group.
  • layout_constraintLeft_toLeftOf / layout_constraintStart_toStartOf: View aligning left to left with another one or with the parent view group.
  • layout_constraintRight_toRightOf / layout_constraintEnd_toEndOf: View aligning right to right with another one or with the parent view group.
  • layout_constraintTop_toBottomOf: View positioned below another one or the parent view group.
  • layout_constraintBottom_toTopOf: View positioned above another one or the parent view group.
  • layout_constraintLeft_toRightOf / layout_constraintStart_toEndOf: View positioned to the right of another one or the parent view group.
  • layout_constraintRight_toLeftOf / layout_constraintEnd_toStartOf: View positioned to the left of another one or the parent view group.
  • layout_constraintBaseline_toBaselineOf: Text-based view aligning baseline to baseline with another one.

At this point, here are all the possible relative positioning constraints illustrated one by one.

Source anchor point/Source side will be in RED. Target anchor point/Target side will be in BLUE. Please take your time. It’s all self explanatory.

Edge alignments between two views

Vertical edge alignments between two views

1. Black view aligning top to top with the white view.

<View android:id="@+id/black"
app:layout_constraintTop_toTopOf="@+id/white" ... />

2. Black view aligning bottom to bottom with the white view.

<View android:id="@+id/black"
app:layout_constraintBottom_toBottomOf="@+id/white" ... />
Horizontal edge alignments between two views

3. Black view aligning left to left with the white view.

<View android:id="@+id/black"
app:layout_constraintLeft_toLeftOf="@+id/white" ... />

4. Black view aligning right to right with the white view.

<View android:id="@+id/black"
app:layout_constraintRight_toRightOf="@+id/white" ... />

Like we used to in RelativeLayout, we can also position a view next or above or below another one.

Horizontal relative alignment between two views

5. Black view’s right edge positioned to the left of the white view’s left edge.

<View android:id="@+id/black"
app:layout_constraintRight_toLeftOf="@+id/white" ... />

6. Black view’s left edge positioned to the right of the white view’s right edge.

<View android:id="@+id/black"
app:layout_constraintLeft_toRightOf="@+id/white" ... />
Vertical relative alignment between two views

7. Black view positioned below the white view.

<View android:id="@+id/black"
app:layout_constraintTop_toBottomOf="@+id/white" ... />

8. Black view positioned above the white view.

<View android:id="@+id/black"
app:layout_constraintBottom_toTopOf="@+id/white" ... />
Black text view aligning baseline to baseline with the white text view

9. layout_constraintBaseline_toBaselineOf: Black text view aligning baseline to baseline with the white text view.

<TextView android:id="@+id/black"
app:layout_constraintBaseline_toBaselineOf="@+id/white" ... />

This constraint applies only to text-based widgets.

Edge alignments between a view and its parent

Vertical edge alignments between a view and its parent view group

1. Black view aligning top to top with the parent view group.

<View android:id="@+id/black"
app:layout_constraintTop_toTopOf="parent" ... />

2. Black view aligning bottom to bottom with the parent view group.

<View android:id="@+id/black"
app:layout_constraintBottom_toBottomOf="parent" ... />
Horizontal edge alignments between a view and its parent view group

3. Black view aligning left to left with the parent view group.

<View android:id="@+id/black"
app:layout_constraintLeft_toLeftOf="parent" ... />

4. Black view aligning right to right with the parent view group.

<View android:id="@+id/black"
app:layout_constraintRight_toRightOf="parent" ... />

Furthermore, we can position a view outside the parent view group. In some cases, this may turn out to be useful as setting up a view’s visibility to gone or invisible might not be the wanted behavior.

Black view positioned outside the parent view group vertically

5. Black view positioned above the parent view group.

<View android:id="@+id/black"
app:layout_constraintBottom_toTopOf="parent" ... />

6. Black view positioned below the parent view group.

<View android:id="@+id/black"
app:layout_constraintTop_toBottomOf="parent" ... />
Black view positioned outside the parent view group horizontally

7. Black view positioned outside the parent view group on the left.

<View android:id="@+id/black"
app:layout_constraintRight_toLeftOf="parent" ... />

8. Black view positioned outside the parent view group on the right.

<View android:id="@+id/black"
app:layout_constraintLeft_toRightOf="parent" ... />

5. Centering

When we define the pair opposite constraints horizontally or vertically, the constrained view will be centered on its constraints bounded space. Thus, we get the same centering behavior of RelativeLayout.

Centering between two views

Centering between two views

1. Black view horizontally centered with the white view.

<View android:id="@+id/black"
app:layout_constraintLeft_toLeftOf="@+id/white"
app:layout_constraintRight_toRightOf="@+id/white"
... />

2. Black view vertically centered with the white view.

<View android:id="@+id/black"
app:layout_constraintTop_toTopOf="@+id/white"
app:layout_constraintBottom_toBottomOf="@+id/white"
... />

Centering a view within parent

Black view centered within the parent view group

1. Black view horizontally centered within the parent view group.

<View android:id="@+id/black"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
... />

2. Black view vertically centered within the parent view group.

<View android:id="@+id/black"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
... />

Another possible trick that may come in handy, is that we can actually center a view relatively to another view’s edge. That’s possible if we use the same target anchor point/target side in the pair opposite constraints of a widget.

Edge centering between two views

Vertical edge centering between two views

1. Black view centered with the white view’s top edge.

<View android:id="@+id/black"
app:layout_constraintBottom_toTopOf="@+id/white"
app:layout_constraintTop_toTopOf="@+id/white"
... />

2. Black view centered with the white view’s bottom edge.

<View android:id="@+id/black"
app:layout_constraintBottom_toBottomOf="@+id/white"
app:layout_constraintTop_toBottomOf="@+id/white"
... />
Horizontal edge centering between two views

3. Black view centered with the white view’s left edge.

<View android:id="@+id/black"
app:layout_constraintLeft_toLeftOf="@+id/white"
app:layout_constraintRight_toLeftOf="@+id/white"
... />

4. Black view centered with the white view’s right edge.

<View android:id="@+id/black"
app:layout_constraintRight_toRightOf="@+id/white"
app:layout_constraintLeft_toRightOf="@+id/white"
... />

Edge centering between a view and its parent

Vertical edge centering between a view and its parent

1. Black view centered with the parent view group’s top edge.

<View android:id="@+id/black"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="parent"
... />

2. Black view centered with the parent view group’s bottom edge.

<View android:id="@+id/black"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
... />
Horizontal edge centering between a view and its parent

3. Black view centered with the parent view group’s left edge.

<View android:id="@+id/black"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="parent"
... />

4. Black view centered with the parent view group’s right edge.

<View android:id="@+id/black"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="parent"
... />

In all the previous examples, we used a one axis constraint (horizontal or vertical) to illustrate the attributes’ behavior separately.

When we do not set constraints on a widget, it will be positioned on the top left of the layout by default. However, we can also experience a weird behavior, or a view can even not render at all.

So to avoid ambiguous behavior, let’s make it a rule. Defining the views’ constraints on both axes (horizontal and vertical) is mandatory.

6. Dimension rules

In ConstraintLayout, there are three size definitions that can apply to the width or height of child views. Still, the dimension rules are slightly different than what we are used to:

Dimensions in ConstraintLayout

fixed: A fixed dimension expressed in dp unit.

wrap_content: The view spreads only as much as needed to fit its content.

0dp(match_constraint): The view expands to fill the space set by the defined constraints. For this to work, we have to set constraints on both opposite sides/source anchor points. (left and right horizontally, top and bottom vertically).

“0dp” with one constraint in an axis = “wrap content”

Dimension modifiers

When a view’s dimensions is set to match constraint(0dp), there are additional modifiers to help us tweak the sizing behavior according to our needs.

Enforcing constraints

“layout_constrainedWidth” attribute’s behavior

If for instance a horizontally constrained widget set to “wrap_content” requires more space to fit its content, its constraints will not limit the resulting dimension. As we have to be careful with smaller screens, we can use the layout_constrainedWidthand layout_constrainedHeightattributes. These accept “true” or “false”(default), and will force the widget’s content to respect the constraints bounded space.

The layout_constraintWidth_default and layout_constraintHeight_default attributes which used to achieve the same behavior are now deprecated.

Min and Max

  • layout_constraintWidth_min and layout_constraintHeight_min : These will set the minimum size for this dimension.
  • layout_constraintWidth_max and layout_constraintHeight_max : These will set the maximum size for this dimension.

The value used for min and max can be either a dimension in dp, or “wrap”, which will use the same value as what wrap_content would do.

Min and max related attributes are applicable only if height/width is set to match constraint (0dp).

7. Margins

In ConstraintLayout, margins can be applied only on a constrained side/anchor point of a widget. They will act as a space between the source side and the target side.

margins’ behavior example

The regular layout margin attributes can be used to this effect:

  • android:layout_marginStart
  • android:layout_marginEnd
  • android:layout_marginLeft
  • android:layout_marginTop
  • android:layout_marginRight
  • android:layout_marginBottom

Note that a margin can only be positive.

8. Gone behavior

When it comes to “GONE” widgets, the library takes margins’ behavior to the next level.

As usual, views marked as “GONE” are not going to be displayed and are not part of the layout itself. Still, ConstrainyLayout has a specific handling for such views.

  • Their dimensions will be considered as zero (they will be resolved to a point).
  • If they are constrained to other views, they will still be respected, but all its defined margins will become zero.
Gone margin behavior

This specific behavior (A and B) allows to build layouts where we can mark widgets as “GONE” without breaking the UI. However, we may find ourselves having a different margin value that does not go well with the design.

This is where the gone margin attributes come into play. These allow us to specify another margin that is applied only when a targeted view constraint is gone.

  • app:layout_goneMarginStart
  • app:layout_goneMarginEnd
  • app:layout_goneMarginLeft
  • app:layout_goneMarginTop
  • app:layout_goneMarginRight
  • app:layout_goneMarginBottom

Gone margins can be applied only on the constraint’s source view (source side/source anchor point). Not on the widgets we mark as “GONE”.

9. Biasing constraints

We know that widgets constrained on both sides of the same axis will be spaced evenly between the two constraints by default. Yet, ConstraintLayout steps up its flexibility game with a concept called bias.

A bias is a weighted proportion that unevenly distributes the spacing between the two pair constraints of the same axis. It’s a float value between 0 and 1 which can be applied using these attributes:

app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintVertical_bias="0.3"

Constraint bias works only when the two pair constraints of the same axis are defined.

Constraint bias defined horizontally
<Button
android:id="@+id/white"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintHorizontal_bias="0.25"
... />

When no bias constraint is applied, the bias is technically set to 0.5 by default. This is why we get a centered widget in the default behavior.

The concept is more akin to the weight system in LinearLayout. Still, this proves to be better as widgets do not have to fill the available space in order to make use of it.

At this point, we can already see how ConstraintLayout elegantly embraced every aspect of RelativeLayout and even made it more flexible in its own intuitive way.

10. Dynamic constraints

The library still has room for exploration, but up to this point, we have been dealing only with static XML code. It’s about time we tackle its dynamic aspect which is the Java/Kotlin approach.

We can update a view and its constraints like the old way using ConstraintLayout.LayoutParams. In any case, we all know that this approach is quite inconvenient.

Well, ConstraintLayout as a modern library comes with its own perception of the UI. Mainly, it perceives views and their relationships(constraints) as two different things. Put differently, constraints themselves are represented as a single concept. This is impressive as now we can manage the layout via a single object without the overhead of re-inflating everything.

So, the library comes with its own layout maestro. ConstraintSet! Primarily, it’s an extra class that allows handling widgets/constraints in a dynamic way.

The ConstraintSet class helps define and manipulate programmatically a set of constraints to be used within the layout. Its usage is very straightforward, and it’s just four easy steps:

1. Instantiation like any Java class

ConstraintSet c = new ConstraintSet();

2. Optional — When dealing with an existing ConstraintLayout, we have to take a snapshot of the layout using the clone method, meaning, we have to make our ConstraintSet object save the current state of every widget’s constraints and attributes within the layout

  • From an XML layout file
    clone(Context context, int layoutId)
  • From a ConstraintLayout instance
    clone(ConstraintLayout constraintLayout)
  • From a Constraints instance
    clone(Constraints constraints)
  • From another ConstraintSet instance
    clone(ConstraintSet constraintSet)

3. In this step, we just have to manipulate widgets and their constraints.

For instance, we can apply relative positioning constraints using the connect() method.

c.connect(R.id.source_view_ID, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP);

The best part is that we do not need a View instance to manipulate constraints. We simply use IDs. Hooray for clean code!

4. Finally, we have to apply the new constraints/state on the layout:

c.applyTo(constraintLayout);

That’s it! To sum it up:

  1. Instantiation.
  2. Optional — Cloning state.
  3. Updating constraints.
  4. Applying constraints to the layout.
1. ConstraintSet c = new ConstraintSet();2. c.clone(constraintLayout);
// OR c.clone(context, R.layout.layout_filename);
3. c.connect(R.id.source_view_ID, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP);4. c.applyTo(constraintLayout);

The third step is where the magic happens. It’s where we have to handle the new constraints for the layout. So, here’s the Java way of everything we have seen up to this point.

Relative positioning constraints

To apply this type of constraints, all we need is ConstraintSet’s connect() method.

connect(int sourceViewId, int sourceSide, int targetViewId, int targetSide)

Here are all the combinations we know:

Centering

Instead of applying relative positioning constraints one by one, ConstraintSet has methods to apply the centering behavior in one line.

Dimensions

ConstraintSet can also handle views’ dimensions.

Margins/Gone margins

setMargin(int viewId, int anchorPoint, int margin)

setGoneMargin(int viewId, int anchorPoint, int margin)

These two methods are applicable only on a constrained anchor point/side.

Constraint bias

setHorizontalBias(int viewId, float bias)

setVerticalBias(int viewId, float bias)

These two methods are functional only on a view set to match constraint with its opposite pair constraints defined.

Views manipulation

ConstraintSet was created to do all the layout’s heavy lifting. Meaning, it’s developed to be the only manipulation pipeline with the layout. Hence, it even supports handling the regular widget properties. Here is the full list:

As for other attributes like background and custom attributes, unfortunately the current 1.1 release cannot handle it. The good news is that the 2.0 release can do it with methods like addColorAttributes(String... attributeName) and addStringAttributes(String... attributeName), etc. Regardless, I am not going to cover these as the 2.0 release is still in its beta version for now.

Removing constraints

clear(int viewId, int anchorPoint): Clears the constraint and margin applied on a view’s anchor point.

clear(int viewId): Clears all the constraints and margins applied on a view.

ConstraintSet’s achilles’ heel

ConstraintSet’s approach is almost perfect. Yet, it has some noticeable shortcomings:

  • Behavior conflict: If we apply updates on a View’s instance directly, ConstraintSet won’t know about the changes because it happened out of its scope. We have to use the clone method again after every direct update, otherwise, the two ways won’t mix.
  • Limited manipulation: The current 1.1 release cannot update all views’ properties. So, using ConstraintSet as the only manipulation pipeline with the layout is limited for now.
  • Nested view groups: Even though nesting a view group is against what ConstraintLayout was created for, we may need it to get a special behavior like FlexLayout for example. The problem is that ConstraintSet can manipulate only its direct child views.

For now, we may think that ConstraintSet is just to update constraints/views. In fact, we discovered only the tip of the iceberg. We will see later what more can it do! Furthermore, every feature’s Java approach will be included from this point on.

11. Chains

When we connect(constraint) together a group of distinct widgets in a bidirectional way, we get a chain.

This feature allows us to control how the available space is shared/divided between a collection of views horizontally or vertically.

Figure 10: Horizontal chain’s “head” view

Actually, every chain has a “head” view. It’s the left-most view in a horizontal chain (figure 10) and the top-most view in a vertical chain. (Not the top one in XML)

A chain works properly only if each end of the chain is constrained to an object on the same axis.

We can define the chain’s behavior by applying layout_constraintHorizontal_chainStyle or layout_constraintVertical_chainStyle to the head view XML tag.

These attributes accepts three values: spread, spread_inside, packed.

Chains can be styled as follows:

Spread

Spread chain style applied to a horizontal chain

This will evenly distribute the views across the constrained chain space (after margins are accounted for).

Spread inside

Spread inside chain style applied to a horizontal chain

This will attach the first and last view to the constraints on each end of the chain, and the rest will be evenly distributed.

Packed

Packed chain style applied to a horizontal chain

This will pack the views together (after margins are accounted for).

Packed chain style with bias applied to a horizontal chain

As illustrated, we can also apply constraint bias on the “head” view.

Weighted

Evenly weighted horizontal chain

When the style is set to either spread or spread inside, we can fill the remaining space by setting one or more views to “match_constraint” (0dp). This way, the space is evenly distributed between each view by default.

Weighted chain style applied to a horizontal chain

Furthermore, we can assign a weight proportion to each view using layout_constraintHorizontal_weight and layout_constraintVertical_weight attributes. This is exactly like LinearLayout’s weight system.

A view can be a part of both a horizontal and a vertical chain at the same time.

How to create a chain

Horizontal spread chain

1. We have to create the bidirectional alignment constraints between the three views (X, Y, and Z):

<Button
android:id="@+id/button_x"
app:layout_constraintRight_toLeftOf="@+id/button_y" />
<Button
android:id="@+id/button_y"
app:layout_constraintLeft_toRightOf="@+id/button_x"
app:layout_constraintRight_toLeftOf="
@+id/button_z" />
<Button
android:id="@+id/button_z"
app:layout_constraintLeft_toRightOf="@+id/button_y" />

Now, we have a collection of views constrained together.

2. Each end of the chain (“X” and “Z”) have to be attached as well.

<Button
android:id="@+id/button_x"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/button_y" />
<Button
android:id="@+id/button_y"
app:layout_constraintLeft_toRightOf="@+id/button_x"
app:layout_constraintRight_toLeftOf="@+id/button_z" />
<Button
android:id="@+id/button_z"
app:layout_constraintLeft_toRightOf="@+id/button_y"
app:layout_constraintRight_toRightOf="parent" />

3. Lastly, we have to apply a chain style on the chain’s head view (“X” button) to get the desired behavior. However, keep in mind that when we do not apply the chain style attribute, it’s set to “spread” by default.

Final result

To sum it up:

  1. Apply views’ bidirectional alignment constraints horizontally or vertically.
  2. Constrain each end of the chain horizontally or vertically.
  3. Apply a chain style (and chain weights if needed).

Chains in Java

ConstraintSet has some chains-related methods to make their creation easier:

To create the above example, we can do it this way:

At this level, we can see how ConstraintLayout adopts LinearLayout’s behavior in a more elastic way. By now, it’s obvious how the creators developed the library with the flat view hierarchy concept in mind.

12. Virtual helpers

ConstraintLayout has some special widgets that don’t really take part in the layout, yet, they enhance other widgets’ behavior. In fact, these virtual helpers technically operate more like an injected behavior within the layout than an actual widget. All in all, we have Guidelines, barriers and groups.

Guidelines

Keylines

In Material design, it’s recommended to use keylines. They are vertical lines that show where elements are placed in a design. Primarily, they enable the precise placement of elements within the layout to get a pixel perfect UI.

Be that as it may, the library has a special widget that we can use as a keyline and more. A Guideline!

A Guideline is a widget( subclass of View) that serves the purpose of being a visual guide(an anchor point) to help constraint other views. In fact, it’s considered a helper object within ConstraintLayout which is why it has a few special properties:

  • It’s marked as View.GONE. So, it’s not displayed at run-time.
  • It always measures its size to a zero.
Ten examples of the Guideline widget

A guideline can be either horizontal or vertical. This is set using the attribute orientation="[horizontal or vertical]". In addition, it can be positioned within the layout in three different ways:

  • layout_constraintGuide_begin: A fixed distance from the left/start(vertical orientation) or the top(horizontal orientation) of a layout.
  • layout_constraintGuide_end: A fixed distance from the right/end(vertical orientation) or the bottom(horizontal orientation) of a layout.
  • layout_constraintGuide_percent: A percentage of the width or the height of a layout expressed as a float value from 0 to 1.

Guidelines are functional only within ConstraintLayout.

Vertical guideline used as a margin

Creating Guidelines in Java

Guidelines can also be manipulated via ConstraintSet:

To create the above example, we can proceed as follows:

In this example, we saw how we can use Guidelines as a margin. We can even use it to attain an adaptive layout(percentage based). We will see this later.

One for all! Groups

Added in the 1.1 release

Group is a class that was added in the 1.1 release of the library. It’s basically a widget with the size 0 that helps control only the visibility of a collection of views.

In Java/Kotlin code, we used to maintain widgets’ visibility one by one or using a list. It was all boilerplate code.

Using groups, we only need to reference the views’ ids in the constraint_referenced_ids attribute(comma separated), and the visibility of the group itself will be applied to the plugged views.

In this case, if we try this one line in a Fragment/Activity class:

group.setVisibility(View.GONE);

The views referenced in the Group XML tag will all receive View.GONE too.

Multiple groups can reference the same widgets. In that case, the XML declaration order will define the final visibility state (the group declared last will have the last word).

For the sake of keeping ConstraintSet as the only manipulation pipeline with the layout, we might want to use this instead:

ConstraintSet c = new ConstraintSet();
c.clone(constraintLayout);
c.setVisibility(R.id.group, View.GONE);c.applyTo(constraintLayout);

Groups are functional only within ConstraintLayout.

Barriers

Added in the 1.1 release

There are cases where the layout has to change according to a dynamic content, or, even to the localisation as some translations can be longer than others.

For instance, we might want to position a button on the right side of two text views. However, we don’t know which text view is going to have the biggest width on run-time.

This used to be a pain point as we had to deal with it programmatically, but, not anymore! In ConstraintLayout, we have Barriers!

A Barrier is a smart guideline. It’s similar to guidelines as it’s a subclass of View, and it sets its own visibility to “GONE”. Except it does not define its own position. Instead, it moves based on the positions and dimensions of other views. In a way, it allows to constraint a widget to a group of widgets rather than one specific widget.

To create a functional Barrier, we have to set two attributes as follows:

  • constraint_referenced_ids: To reference the views to be contained within the Barrier. This attribute accepts a comma separated list of ids as a value.
  • barrierDirection: To set which side/anchor point should the barrier keep track of for the group of views specified. This attribute accepts top, left/start, bottom, or, right/end as a value. Top and bottom will create a horizontal barrier. Left/start or right/end will create a vertical one.

For instance, if we reference three views and set the direction to “right”/”end”, the barrier will always be at the right side of the views as an invisible vertical line. Now, to make a view stay always positioned at the right side of the three other views, we just have to align it to the barrier.

This way, we successfully and indirectly constrained a view to a group of three other views.

What about “GONE” views?

If one of the referenced views is marked as “gone”, we can force the Barrier to ignore it by setting the modifier attribute barrierAllowsGoneWidgets to false (true by default). Otherwise, it’s going to take into account the resolved position of the gone widget.

Barriers in Java

Using ConstrainSet, we can create a barrier.

  • createBarrier(int id, int direction, int... referenced)

Barriers are functional only within ConstraintLayout.

13. Aspect ratio

For those unfamiliar with aspect ratio, it describes the proportional relationship between the width and height of an image, and it’s expressed in ratio form:

width:height (width always comes first)

For 3 images that all have an aspect ratio of 16:9, one image might be 16 inches wide and 9 inches high, another 16 centimeters wide and 9 centimeters high, and the third might be 8 yards wide and 4.5 yards high. Thus, aspect ratios concern the relationship of the width to the height, not an image’s actual size.

Navagio beach displayed in different common aspect ratios

Common aspect ratios: 3:2, 1:1(square), 4:3, 16:9.

In Android, a common task involves fixing a view to a particular aspect ratio. This is extremely common with images, whether it’s square (1:1), 4:3, 16;9, or something custom. With ConstraintLayout being an accomplished library as usual, we no longer have to delegate such task to custom views/view groups thanks to the layout_constraintDimensionRatio attribute.

To get the behavior right, we have to at least set one constrained dimension to match constraint(0dp), and set the attribute layout_constraintDimensionRatio to a given ratio.

The ratio can be expressed as:

  • a float value representing a ratio between width and height
  • a ratio in the form “width:height”

This feature is also functional when both dimensions of a view are set to match constraint (0dp). In particular, specifying the ratio preceded with either an “H” (height) or a “W” (width) indicates which of the dimensions should be subject to the constraints applied to it.

  • “H, width:height”: This means that the height is going to adapt according to the aspect ratio specified, and the view’s width is going to fully expand to match its constraints.
  • “W, width:height”: This means that the width is going to adapt according to the aspect ratio specified, and the view’s height is going to fully expand to match its constraints. (default behavior)
Views’ aspect ratios maintained within ConstraintLayout

All in all, using an aspect ratio requires three steps:

  1. Setting one or both of the views’ dimensions to match constraint(0dp).
  2. Defining the pair opposite constraints for the correspondent match constraint dimension.
  3. Applying an aspect ratio.

Aspect ratio in Java

ConstraintSet has one method to apply a ratio:

setDimensionRatio(int viewId, String ratio)

16. Percentage based layout

Another perk of using ConstraintLayout is that it allows the creation of a responsive design. Meaning we can have one layout for all screen sizes. In fact, the library’s remarkable flexibility and its feature-rich nature allows the creation of percentage based layouts in more than one way.

Percent dimensions

First things first, we can use percent dimensions. More specifically, we can define the view’s height or width as a percentage of the parent.

All we need is two required easy steps:

  • Set the dimension to match constraint (0dp) with its pair opposite constraints in place.
  • Use the attributes layout_constraintWidth_percent or layout_constraintHeight_percent with a value between 0 and 1.

Percentage based positioned guidelines

Another method involves using guidelines. We saw already how they support percentage based positioning using the attribute layout_constraintGuide_percent.

Basically, we can connect our “match constraint” views to one guideline or more within the layout to get a percentage based dimension, or we can use it as a percentage based margin.

Percentage based chains

What’s more is that we can also use tweak the weight aspect of chains to get a percentage based layout.

Weighted chain style applied to a horizontal chain

For instance, we can have three views with widths set to 20%, 40% and 40% of the parent respectively from left to right.

Aspect ratio as a modifier

All the previous methods are great, but combined with the aspect ratio feature we can break the axis barrier, and set a view’s height to a percentage of the parent’s width.

If we set a view’s width to 40% of the parent’s width, and set the aspect ratio to 1 using the attribute layout_constraintDimensionRatio, we get a view’s height set to 40% of the parent’s width.

The fact that many features can be combined and work flawlessly together prooves how ConstrainyLayout is a powerful library that can provide more than one solution for a problem.

17. Circular positioning

ConstraintLayout kicks it up a notch again with another interesting feature called Circular Positioning. As the name suggests, this allows us to constraint a view on a circle. More specifically, we can position a widget’s center anchor point relatively to another widget’s center anchor point, at an angle and a distance.

Circular positioning between two widgets

To use this feature, we have to set these 3 attributes on the constraint source view:

  • layout_constraintCircle : Target view id
  • layout_constraintCircleRadius : Distance to the target widget center
  • layout_constraintCircleAngle : Source view position angle (in degrees from 0 to 360)

In Java, we just have to use ConstraintSet’s constraintCircle method:

ConstraintSet c = new ConstraintSet();
c.clone(constraintLayout);
//constraintCircle(int sourceViewId, int targetViewId, int radius, float angle)
c.constraintCircle(sourceViewId, targetViewId, radius, angle);
c.applyTo(constraintLayout);

Using this feature, we will try to create the illustrated clock layout and animate it.

Clock UI

Walkthrough

layout state at start + blueprint + layout widgets’ breakdown

Layout widgets’ breakdown:

  1. The clock’s background as a View.
  2. The hour marks as TextView widgets.
  3. The blue circle View as the hour hand’s holder.
  4. The red circle View as the minute and second hands’ holder.
  5. The clock hands as Views.
  6. The ticker holder as a View.
  7. The ticker as a View which is supposed to rotate 360 degrees every second.
  8. Clock animation’s start/pause button.

Layout file:

In this layout, we evenly positioned the hour marks circularly around the clock background view’s center.

The three clock hands are constrained circularly around the clock’s center. Plus, they were positioned distant enough to make them look attached to their respective holders (the blue and red circles). This may seem a little tricky as we have to take into account that the layout_constraintCircleRadius attribute operates on the center anchor point.

Furthermore, the ticker and its holder are constrained to the center of the clock’s second hand.

Finally, we added a start button on bottom to start/pause/resume the animations.

In Java code, we initialized ConstraintSet and extracted the layout’s state using the “clone” method. As for the angle animations of the time hands, we used the “ValueAnimator” class to animate an int value from 0 to 359.

In the animator value update listener, we updated the new angle and the new rotation of the time hands using ConstraintSet’s constraintCircle and setRotation methods.

public void constrainCircle(int sourceViewId, int targetViewId, int radius, float angle);public void setRotation(int viewId, float rotation);

Actually, the rotation and the constraint’s angle have to be updated with the same value in the same animation update listener to make the time hands’ vertical axis animate like it’s rotating around the clock’s center.

When the rotation center point is a part of the rotating view, we can omit the circular constraint and animate only the rotation. If needed, the view’s pivot point can always be adjusted.

The “animate” method returns a ValueAnimator object with the needed behavior.

Furthermore, we have to make the animations respect the activity’s life cycle to avoid unnecessary computing and memory leaks.

Finally, we implemented the start button’s click listener to handle the animations’ start, pause and resume actions.

Final result

Animated clock UI

In the example, we can see how constraints are always respected. In fact, we didn’t animate the angle of the ticker and its holder. They are constrained to the second hand’s center which is why they are getting dragged along the animation.

This example is just to get more familiar with ConstraintLayout’s circular positioning. Obviously, it has overdraw issues. To implement such an animated clock, a custom view would be the ideal solution.

Keyframes animation

Flipbook created by Andymation

When many frames of successive content are displayed in a sequential manner with the right speed, it creates the illusion of motion. Hence, we get an animation. A key frame is one of the frames of an animation’s timeline that marks the beginning or end of a transition.

Be that as it may, we saw previously that ConstraintLayout has its own layout maestro; ConstraintSet!

Well, the class’s ability to take a layout’s snapshot via its clone() method allows us to use it as keyframes/states. Combined with the transition framework’s TransitionManager class, we can perform highly complex animations (scene transitions) to multiple views simultaneously within the layout.

Two easy steps are required to implement this:

  1. TransitionManager.beginDelayedTransition(ViewGroup rootLayout) has to be called in the event that should trigger the transition. This way, the TransitionManager will be on standby waiting for changes to be applied on the root layout
  2. Next, we update our ConstraintLayout with a new set of constraints.

When the system redraws the UI, the TransitionManager will automatically animate the changes between the original state and the new state.

Keep in mind that animations using ConstraintSet changes, will include only layout-related attributes, and not style-related attributes such as textSize, etc.

All in all, there are two methods to implement this:

  1. We can use separate layout files that both have the same widgets, but different layout/constraints.
  2. Update constraints dynamically using one ConstraintSet instance in Java code.

Separate layout files use case

First things first, we have to create two layouts/keyframes.

Layout at start
Layout expanded on backdrop image click

Finally, we have to trigger the transition on backdrop image click in Java code.

Basically, we used two ConstraintSet instances. Each one acted as a keyframe as it saved a different layout file. Finally, on click we applied new Constraints on our root layout right after calling the beginDelayedTransition() method.

Final result

Animating ConstraintLayout between two states/keyframes

In this example, the first thing we notice is how small and easy the code is for such an intricate animation.

Using the separate layout files approach may be helpful as we get to see the different layout states directly in the visual tool. However, if a project supports different orientations/screen sizes and we need animating between more than two states, we may end up with a big number of layout files, and that’s hard to maintain.

Single ConstraintSet instance use case

In the next example, we are going to create four circular buttons with an animated red selector that moves to the clicked button.

First, we have to create our layout file.

Layout at start

In this layout, we used backgrounds as separate views because we want the red selector to be positioned behind the icons.

Basically, we used one ConstraintSet instance. On click, we center align the red selector to the clicked button. Of course, the TransitionManager handles the rest.

Final result

Animated layout using the single ConstraintSet instance approach

Using the single ConstrainSet approach allows us to animate between a lot of states/keyframes with minimal code update effort if needed, and without the overhead of managing a lot of layout files.

An extra useful effort would be to use a temporary layout file to check in the visual editor tool if the needed constraints after an animation meet the requirements or not.

Placeholders

So far, the library proved itself to be an all-in-one package for layout creation. However, ConstraintLayout still has another intriguing feature that definitely can’t go unnoticed. Placeholders!

In a parking lot, if we consider Views to be vehicles, a placeholder would be a parking space. It’s a virtual helper that allocates a constrained space within the layout. In fact, we can apply constraints and position the placeholder like any other View.

When the id of another view is set on a placeholder using the attribute content or the method setContentId(), the placeholder becomes the referenced content view. Meaning, the content view will be re-positioned according to the placeholder’s constraints, and have its original location treated as GONE.

Keep in mind that a placeholder is substitutable. So, if we set another view on an occupied placeholder, the old content view would go back to the original location/constraints it had at layout start.

Using this feature, we will try to create the illustrated quiz UI and animate it.

Layout at start

When a user clicks on an answer, we have to make the tapped view take over the placeholder’s position.

Final result

Animated layout using the placeholder feature

On click, we set the tapped view’s id on the placeholder using the method setContentId(), and we get to animate the layout change using the TransitionManager class.

Heads up

In this part, we will walk through some ambiguities and use cases in which we have to resort to tricky solutions.

Editor based attributes

Using the layout editor, we may find some tools attributes in XML.

tools:layout_editor_absoluteX
tools:layout_editor_absoluteY
tools:layout_constraint[Anchor point]_creator

These attributes are added to keep the visual editor working properly and prevent it from interfering with the constraints we apply manually.

In fact, the tools namespace serves the purpose of enabling design-time features (layout rendering, etc.), or compile-time behaviors (resources shrinking mode, etc.). When we build our app, the build tools will remove any tools attributes so there is no effect on APK size or run-time behavior.

All in all, we have to take a hint when the layout on device is not the same one in the visual editor because tools attributes have absolutely no effect in run-time.

Negative margins

Negative values as a margin are not allowed. Be that as it may, the library’s flexibility allows a workaround using the Space widget (a lightweight view).

Negative margin applied using the Space widget

Using relative positioning constraints, we can position a space widget outside the parent view group to act as a negative margin (width/height = margin). Next, we have to align the needed view’s edge with the space widget’s respective edge outside. Hence, we get our view constrained with a pseudo negative margin applied. Of course, this works between two views, or a view and its parent view group.

Text based widgets’ “wrap_content” behavior

There is a common ui problem where the dynamic content of a “wrap_content” text based widget may overlap other widgets in run-time.

The solution can be implemented in three easy steps:

  1. Apply the opposite constraints: in our case, the text view has to be edge aligned with parent on the left, and positioned to the left of the cake image view.
  2. Applying bias: As our text view is set to “”wrap_content”, it’s centered in its constraints bounded space by default. We have to set the horizontal bias to zero, to make it aligned to the left.
  3. Enforcing width contraints: We have to make the width respect its constraints bounded space by setting the layout_constrainedWidth attribute to “true”.

Final result

Another use case involves moving an image view according to a text based widget’s wrapped content. The problem is that like the previous example, the text based widget should not overlap the image view when there isn’t enough space.

The solution is to use a packed chain with a bias.

All child views’ widths have to match the widest child view’s width

In this case, we want all the child views’s widths to match the widest child view’s width among them. The problem is that the widest one is not always the same as it changes in run-time, and, the views are expanding from both sides(right and left).

With the current 1.1 release, it’s not possible to get 3 widgets to always match their widths to the widest one among them without nesting a view group. However, there is a workaround to this.

Actually, to get the same visual result, we have to treat backgrounds as separate views, meaning, we have to get the same Button widget appearance using two widgets; a View as a background and a TextView.

Using two barriers, we can keep track of the widest TextView by constraining each barrier at each end of the three text views; one on the right and one on the left.

Finally, we constraint the backgrounds to the barriers as “match constraint” views to fill the space in between. This way, the background views will expand according to the widest text view. Hence, we get the needed visual result.

Certainly, this is not the cleanest solution to get such a behavior. Still, we will get one with the 2.0 release of ConstraintLayout as it allows developers to implement their own custom virtual helpers.

Relative percentage based dimensions

Up to this point, we know that a percentage based layout can be achieved in more than one way, and that percentages are always defined according to the parent view group only. However, we may have a situation where we need a percentage based dimension relatively to another widget who doesn’t use percentages. For example, a widget’s width has to be at 70% of another widget’s width.

For this use case, we can use our own pseudo virtual helper; the Space widget. In fact, ConstraintLayout allows a view marked as “gone” to keep its constraints respected. So, if we set a Space widget to “wrap_content” and mark it as “gone”, it can still act as an anchor point within the layout.

If we center the space widget between the target view’s edges and apply a bias,we would get a percentage based guideline that’s operational only within the target view’s boundaries (the white button). This way, we just have to edge align our source view (the red view) with one of the target view’s edges and the pseudo guideline on the opposite side. The space view’s bias will set the width percentage we need.

The downside of such a solution is that the source view has to be within the target view’s boundaries.

Final thought

There is no doubt that ConstraintLayout is a complex piece of code that got us all the upsides of the existing view groups and more. Its feature-rich nature and powerful flexibility prove that it’s an essential all in one package for modern android development.

Definitely, the 2.0 release that’s still in its alpha days will take android’s UX to the next level. We have MotionLayout, virtual view groups, decorators, etc. with performance as the main focus.

In the next article, we will see how ConstraintLayout contributes to the app’s overall performance in order to get more insight on how and when to use it.

That’s it!

Congrats for reading this far! Hopefully, by now we have a solid understanding of the library. In the mean time, I’d love to hear your feedback, so please don’t forget to clap 👏, leave a comment below, follow me on Twitter, and share with others!

Happy coding!

--

--