Constraint Layout == Relative Layout on roids?
Introduction
At Google IO 2016, Android developers learned that there will be a new way of building UI for their apps thanks to the new Constraint Layout and improved Layout Editor from Android Studio. But what is all about? Let’s take a deeper look!
Pre-requirements
Currently, the new Layout Editor is available only in the Android Studio 2.2 Preview. Constraint Layout is an unbundled library compatible down to API 9, which means that you can add it separately as a dependency in your gradle file:
1 | compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha4' |
It is still in alpha version so we should be aware that there might be some bugs, even though it seems to be quite stable. It’s recommended to use it with the new Layout Editor, then you can take a full advantage of all new features. But you can use the Constraint Layout also in previous versions of Android Studio, by writing everything by yourself in xml code.
What is Constraint Layout?
Basically, it’s a new ViewGroup. We can say that it is an improved Relative Layout or a superset of Relative Layout as we heard at Google IO. The whole idea is based on constraints. When we add a view to Constraint Layout, we connect it either with the parent view or other views on the same level of the view hierarchy. The position of that view is calculated depending on those constraints.
Design like architect
Let’s check then how to use Constraint Layout. I will demonstrate it in the new Layout Editor and its Blueprint mode, which is clearly showing the structure of the layout that we are creating. Developers can design almost like the architects. If you don’t like the Blueprint mode you can also use Design mode, which is similar to the old Layout Editor, or you can see both previews at the same time, one next to other. Starting from the basics, let’s check first how to create constraints just by dragging.
There are 2 more ways to add constraints. We can either turn on the Autoconnect mode, so constraints will be added when we drag the view inside the layout, or we can press the Infer Constraints button to add the constraints automatically after the view is positioned in the layout.
If we set constraints for both edges: top and bottom or left and right, we can also set it’s vertical/horizontal bias. It allows us to position the view in the certain percent of the parent view.
Here’s the xml code generated for Text View. I chose the attributes specific for Constraint Layout only:
1 2 3 4 5 6 7 8 | ... app:layout_constraintBottom_toBottomOf="@+id/constraintLayout" app:layout_constraintLeft_toLeftOf="@+id/constraintLayout" app:layout_constraintRight_toRightOf="@+id/constraintLayout" app:layout_constraintTop_toTopOf="@+id/constraintLayout" app:layout_constraintHorizontal_bias="0.25" app:layout_constraintVertical_bias="0.33" ... |
We can see that there are 4 attributes that define Text View to be constrained to parent view with all its edges. Also, there are 2 attributes for bias.
It is possible to set view’s attributes in the new Layout Inspector too. We can set their id, margins, bias, width and height. Underneath (not visible in the screenshot) there’s also a bunch of view’s specific attributes. See below how to set width faster than typing in the field, just by pressing on specific icons.
Please note that if you set width or height of the child of Constraint Layout to 0dp, it means that it will fill available space. There’s no match_parent value for children of Constraint Layout.
We can also set text baseline between Text Views. It means that texts inside views will be aligned with the same line. If you work on xml code you can just use layout_constraintBaseline_toBaselineOf attribute and as a value, we just need to set id of other Text View that we want to set the baseline too.
Guidelines
Along with Constraint Layout, we can use Guidelines. I find them very useful as they allow us to do some work more easily than it was before. For example, let’s imagine a situation when you want to set the view’s width to be 30% of parent’s width and height to fill the parent’s height. The only thing you need to do is to add vertical Guideline in 30% of parent’s width and set the width and height of the added view to be 0dp. Below you can see the generated xml code (I used the Layout Editor to achieve that):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/constraintLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.constraint.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_Percent="30" tools:layout_editor_absoluteX="119dp" tools:layout_editor_absoluteY="0dp" /> <TextView android:id="@+id/textView" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="@+id/constraintLayout" app:layout_constraintLeft_toLeftOf="@+id/constraintLayout" app:layout_constraintRight_toLeftOf="@+id/guideline" app:layout_constraintTop_toTopOf="@+id/constraintLayout" tools:layout_constraintLeft_creator="1" tools:layout_constraintTop_creator="1" /> </android.support.constraint.ConstraintLayout> |
As you can see there are few new things. First of all, Guideline view itself. What’s important: it has android:orientation=”vertical” and app:layout_constraintGuide_Percent=”30″ attributes. Orientation is quite clear, it determines if the Guideline should be orientated vertically or horizontally. The second attribute determines the position of Guideline. In this case, it is gonna be in the 30% of parent view’s width. You can also set the position of Guideline with layout_constraintGuide_end or layout_constraintGuide_begin attributes. In those cases, you just set up values in up and it works as an offset from one of the sides of the parent view. I will skip attributes for tools. Let’s take a look at Text View, there are 4 attributes that determine constraints for each side of the view:
1 2 3 4 5 6 | ... app:layout_constraintBottom_toBottomOf="@+id/constraintLayout" app:layout_constraintLeft_toLeftOf="@+id/constraintLayout" app:layout_constraintRight_toLeftOf="@+id/guideline" app:layout_constraintTop_toTopOf="@+id/constraintLayout" ... |
The last thing we need to do is to set up:
1 2 3 4 | ... android:layout_width="0dp" android:layout_height="0dp" ... |
It means that view will get the whole available space.
Sample Charts App
Let’s demonstrate usage of Constraint Layout in a sample app. In the example, I focused on how to use Guidelines to create charts application. It has 2 screens, first to fill up content and second to display charts. You can see how it works below:
I won’t focus on how to create the first screen, but I will explain in detail how I’ve created the second screen with chart bars. It is built with Recycler View with chart bars as a single item. Below you can see the xml code of a single item:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/constraint_layout" android:layout_width="@dimen/chart_bar_item_width" android:layout_height="match_parent" android:orientation="vertical"> <include layout="@layout/merge_chart_background"/> <View android:id="@+id/view_chart_bar" style="@style/ChartBar" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintLeft_toLeftOf="@+id/constraint_layout" android:layout_marginLeft="@dimen/margin_medium" android:layout_marginStart="@dimen/margin_medium" app:layout_constraintTop_toTopOf="@+id/guideline_top" app:layout_constraintRight_toRightOf="@+id/constraint_layout" android:layout_marginRight="@dimen/margin_medium" android:layout_marginEnd="@dimen/margin_medium" app:layout_constraintBottom_toBottomOf="@+id/constraint_layout" /> <android.support.constraint.Guideline android:id="@+id/guideline_top" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" tools:layout_editor_absoluteX="0dp" tools:layout_editor_absoluteY="71dp" /> <TextView style="@style/TextLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/text_label" android:lines="1" android:ellipsize="end" app:layout_constraintLeft_toLeftOf="@+id/view_chart_bar" app:layout_constraintRight_toRightOf="@+id/view_chart_bar" app:layout_constraintBottom_toTopOf="@+id/guideline_top" android:layout_marginBottom="@dimen/margin_small" /> </android.support.constraint.ConstraintLayout> |
Starting from the top, layout contains background (it is a set of lines for certain percents), View as a bar itself, Guideline and Text View as labels. Maybe you have noticed that Guideline doesn’t have attribute app:layout_constraintGuide_Percent. It is set dynamically in Java code, by getting Guideline’s layout params and setting ConstraintLayout.LayoutParams.guidePercent field:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class ChartsViewHolder extends RecyclerView.ViewHolder { //... @BindView(R.id.guideline_top) Guideline mGuidelineTop; //... public void bind(final Item item) { //... ConstraintLayout.LayoutParams layoutParamsGuideline = (ConstraintLayout.LayoutParams) mGuidelineTop.getLayoutParams(); layoutParamsGuideline.guidePercent = item.getPercent(); mGuidelineTop.setLayoutParams(layoutParamsGuideline); } } |
Thanks to that I can easily set the bar’s height. Its top is constrained to the Guideline, bottom to parent view and it has the height set to 0dp so it will take the available space. If you are interested in the rest of the code, you can check it on our GitHub.
Problems with Constraint Layout
I faced some issues while working with the Constraint Layout. It’s important to remember that this library is still in alpha version and new Layout Editor is available only in Android Studio Preview. Hopefully, all the problems will be fixed before the stable version’s release!
I had few problems with Layout Editor itself. Sometimes after making the changes in the xml and going back to Blueprint mode, some values were not set correctly. Additionally, I found it hard sometimes to make the Guideline as selected in the Layout Editor. Also, I had often an issue that I set the width or height of the view to 1dp in Layout Inspector and after selecting some other components the value changed automatically to 5dp.
Another point that comes to my mind, rather the improvement than an issue, is that the layout_constraintGuide_Percent could be a float value instead of an integer. Why that? Let me explain that on the item_chart_bar.xml layout example. For now, we can calculate the percentage of guideline as an exact equivalent of the value that user puts in it because the chart bar takes whole available space of the parent view. But imagine the situation, when you would like to add for example a Text View below the bar with a label describing specific value. Then we would need to calculate the percentage of the guideline differently, and since the bar wouldn’t take the whole height of the parent view, there would be some values that give us the same percentage for Guideline. As a result, the user would have the impression that (for example) values 98 and values 99 have the same height on the screen. It could be resolved by nesting layouts and putting the bar view separately, but having percentage attribute as a float value would be nicer to have.
UPDATE (9 August 2016): This improvement was added in alpha 6 release. If you are setting the percent in xml file, there is another attribute now: layout_constraintGuide_percent and you can use it with float values. For example:
1 | app:layout_constraintGuide_percent="0.192" |
The previous attribute: layout_constraintGuide_Percent still exists and it takes the integer value. But if you would like to set the percent programmatically with ConstraintLayout.LayoutParams.guidePercent, be aware that it is a float value now.
When I was playing around with the new Constraint Layout, I also faced the problem with adding Guidelines dynamically in the code. Turns out you cannot set the orientation of Guideline in ConstraintLayout.LayoutParams. After decompiling the Constraint Layout class you can see that there is an orientation field, but it has package access instead of the public. I hope to see it fixed soon!
UPDATE (9 August 2016): This bug was actually fixed in the recent alpha 6 release. It is possible now to dynamically add Guidelines, since we can set the orientation programmatically.
Summary
To sum up, here are some key features that Constraint Layout provides us with:
- possibility to position views anywhere using percentages (bias),
- guidelines, which can help to position the view or set the view’s width/height to be some percentage of parent’s width/height,
- we should be able to reduce nesting layouts when using Constraint Layout.
Even though it is still in alpha version, I strongly recommend everyone to take a look and check the possibilities that it offers, so when the stable version is released you can start using it right away.
Complete project can be found in ConstraintLayoutCharts repository.
Ready to take your business to the next level with a digital product?
We'll be with you every step of the way, from idea to launch and beyond!