• Meghan Gill

ConstraintLayout Tutorial for Android: Beginner Example


What are layouts?


A layout is a ViewGroup which is responsible for positioning its child Views. A layout is an invisible container that holds views (i.e. widgets) with which users can interact. Layouts come in many types which provide different layout structures, such as LinearLayout or ConstraintLayout .



Common layouts


When building your Android UI common layouts include FrameLayout, LinearLayout and RelativeLayout. Each layout has specific limitations and performance issues as the view hierarchy becomes more complex:


  • FrameLayout is best used when there is only one child view. You can only position child view(s) within the FrameLayout with the attribute android:layout_gravity

  • LinearLayout is either horizontal or vertical. It doesn't allow views to overlap each other. Usually, you will have to use multiple nested Linear Layouts.

  • RelativeLayout is costly because it always does two measure passes.

Nested RelativeLayouts and nested LinearLayouts with layout_weight increase layout cost exponentially. Flatter hierarchies give better performance. That's the advantage of ConstraintLayout.



ConstraintLayout


ConstraintLayout offers layout control and much stronger performance. You can constrain objects to the container, other views or guidelines. That means you can make complex and dynamic layouts in a flat view hierarchy.



What's My Plant? ConstraintLayout Tutorial



In this project we will explore some basic features of constraint layout, namely how to dynamically position UI elements onscreen in relation to other UI elements and "0dp" or match constraint.


Step 1: Start a new Android Studio project with an Empty Activity. Name the project What's My Plant.


The Save location should be a place that makes sense on your computer. If you click on the open folder you can choose a location.


Choose Java or Kotlin. Pick a minimum SDK of API 21: Android 5.0 (Lollipop). You do not need any legacy android support libraries.




Begin a New ConstraintLayout


Step 2: Open activity_main.xml and go to the Code view.


res > layouts > activity_main.xml > Code


<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

You can see here that the root element of the layout is androidx.constraintlayout.widget.ConstraintLayout You will also see that your TextView with the text "Hello World!" is already constrained to the container.


It's constrained to the bottom with app:layout_constraintBottom_toBottomOf="parent" and since it is also constrained to the other three sides of the container the TextView is centered.



Add an Image


Step 3: For this step you will need the plant image from the screenshot of the app. You can find it here. Be sure to scroll to the bottom of the page and select the green button "Download PNG."


*Attribution: plant clipart PNG Designed By 588ku from <a href="https://pngtree.com/">Pngtree.com</a> 

Once you've downloaded the image you can copy it with command-C on Mac or control-C on Windows. Then select your drawable folder and paste it using command-V on Mac or control-V on Windows. You will be asked to choose a destination directory. Here I've chosen drawable.



Make sure your image name is written in snake_case or you will get an error. I've named mine snake_plant.png



Step 4: In the Design view delete the "Hello World" TextView. You will be using the Palette to add UI elements, if it is hidden click the Palette tab next to the Component Tree to open it.


Drag the following UI elements into the view from the Palette:

  • One ImageView. Pick the snake plant image when prompted to select an image.

You may have to resize it. My ImageView dimensions are:

android:layout_width="100dp"
android:layout_height="100dp"

You can add the width and height in either the XML code or the visual editor.

  • Two Buttons

  • Five textViews


Step 5: Update the text of the UI elements using the screenshot of the final app layout preview at the beginning of this tutorial. Drag the UI elements around the screen so that they resemble the final layout preview at the beginning of the tutorial.


Select the What's My Plant TextView. Switch to the Code editor. To get the proper styling, add the textAppearance attribute to the TextView:


android:textAppearance="@style/TextAppearance.AppCompat.Headline"/>



Constraining UI Elements


You will also notice the UI elements are red, and there are red and yellow lines in the XML. Those are warnings. Our views are not constrained. They merely have designtime attributes, so at runtime the views will not keep their positions. The designtime attributes begin with the tools prefix. For instance tools:layout_editor_absoluteX.


If we run the app now, the views will all jump to the top left corner.



Step 6: Constrain the ImageView to the top left corner of the view. To do that in the visual editor click the image view and a box will highlight the perimeter of the view. There will be four empty circles. These circles are the view's constraint anchors.


When you select an anchor it will become highlighted with an extra blue circle. That's Android Studio letting you know that the anchor can be used to create a constraint connection.


Click on the circle on the left side of your ImageView and drag it to the left. A line with an arrow will appear. Drag that arrow left until you reach the start of the parent container. You have now constrained the ImageView on one side. Repeat this for the constraint anchor at the top of the ImageView.


Set the marginTop to 30dp either by editing the XML code in the code view or by editing it in Constraint Widget of the the Attributes inspector in the design view. Set the marginStart to 16dp.



Constraining Views to Each Other


Step 7: We want to ensure that the What's My Plant TextView is always aligned with the snake plant image. To do that we will constrain one view to another view. Specifically, we'll constrain the top anchor of the What's My Plant TextView to the top anchor of the ImageView, and the bottom anchor of the TextView to the bottom anchor of the ImageView. That aligns the two views vertically.


We will do this in the visual editor, but it can also be accomplished via code.



Now the views move together. If you click the ImageView and drag it, the What's My Plant TextView will move up and down with it; the views will keep their vertical alignment.


Code view:

Now that you’ve added some constraints, the attributes with the tools prefix have disappeared from the ImageView because Android Studio has enough constraints for runtime. Attributes for each of the constraints you’ve added now appear, such as app:layout_constraintTop_toTopOf="parent".


The TextView is still red. It does not have the minimum number of constraints to maintain its position during runtime. It is constrained vertically but not horizontally.


Step 8: Constrain the left side of the What's My Plant TextView to the right side of the ImageView.


You can do that using the circular constraint anchor on the left side of the TextView. Drag it to the right side of the ImageView. If Android Studio created a margin, delete it.


In code your new constraint reads: app:layout_constraintStart_toEndOf="@+id/imageView" 

Now your TextView has the necessary runtime constraints. The red errors are gone.


*You may still see yellow warnings on the ImageView and the TextView for missing contentDescription and hardcoded strings. You can choose to fix these or ignore them for this practice exercise.

Step 9: We want to ensure that the SIGN UP Button is always vertically aligned with the LOGIN Button and that it is always 30dp from it. To do this you need three constraints:


  • Anchor the top of the LOGIN Button to the top of the SIGN UP Button.

  • Anchor the bottom of the LOGIN Button to the bottom of the SIGN UP Button.

  • Anchor the left side of the LOGIN Button to the right side of the SIGN UP Button.

  • Give the LOGIN Button a marginStart of 30dp using either the Design editor's Constraint Widget or the Code editor android:layout_marginStart="30dp"






Align the Left Edge


Step 10: If you look at the left screenshot of the emulator you can see that the views are beginning at uneven points. We want to align the left side of all of the TextViews and the SIGN UP Button. Luckily Android Studio's visual editor has a control for that.


Hold shift as you click each TextView and the SIGN UP Button. Then click Align and choose Left Edges.




Now most of the views all have one horizontal constraint, specifically the attribute app:layout_constraintStart_toStartOf.


Did you notice a problem?


Left Edges aligns all selected views to the leftmost view. The view at the bottom becomes the anchor. Also, for Left Edges to work it has to erase the horizontal constraints of all the views except the anchor view.


How do we fix it?


Let's reverse the constraint dependency order by making the What's My Plant TextView the anchor view. To do that simply apply the Left Edges command again. Now the constraint arrows should be pointing upward.


You may have the manually reenter the horizontal constraint of the What's My Plant TextView. Make sure it includes: app:layout_constraintStart_toEndOf="@id/imageView"



Distribute Views Vertically with Default Margins


You can set a default margin in the Design editor toolbar. When you create a default margin it doesn't change the margins of views you've previously positioned. It will only use the default margin for UI elements you constrain moving onward.


If you look at the screenshot below the default margin icon is circled in bright green. I set it to 60dp which will give all the views I constrain now a margin of 60dp. For example, if I constrain the top of a UI element then it becomes a marginTop of 60dp in the XML.

Step 11: The fastest way to distribute the UI elements vertically is to manually create constraints. To do that we can set the default margin in the Design editor to 60dp and create a vertical constraint for each view that lacks one.


That means the top of the Terms of Service TextView will be constrained to the bottom of the button and so on.





Match Constraint in ConstraintLayout


ConstraintLayout uses 0dp or "match constraint" to make a view fill a specific space of the UI. 0dp means the view occupies the entire space of the constraint.


Using "0dp"

  • To use 0dp for the width a view must have start and end constraints.

  • To use 0dp for height the view must have top and bottom constraints.


My long TextViews need a start and end constraint and a width of 0dp so that they display properly across different screen sizes.


We will set the height to "wrap_content" so that the TextView's height increases as necessary to fit the text. This will allow us to choose where the TextViews start and stop without cutting off any text.


First use a vertical guideline as an end constraint. Then utilize 0dp to give the long TextViews a finite width.


Guidelines


Guidelines are virtual helpers exclusive to ConstraintLayout. They are a type of view to which we can constrain other views, but guidelines never show during runtime.


You can use vertical and horizontal guidelines to divide the parent ConstraintLayout and constrain views. That gives you an effect similar to using weighted LinearLayout while maintaining a flat view hierarchy. That's a powerful tool.


Step 11: Let's create a vertical guideline to use as an end constraint for our long, unwieldy TextViews.


The simplest way to get a guideline is the click the Guidelines icon in the toolbar.



You can also get a guideline from the Palette. In the Palette choose Helpers then Guideline(Vertical).


Design view > Palette > Helpers > Guideline(Vertical)


Drag a vertical guideline from the palette onto the view. You should now see it in the component tree. Right click guideline and click Constrain and click parent end. Now you view see the guideline in your Design view.


Component Tree > guideline > Constrain > parent end



There are three ways to position vertical or horizontal guidelines:

  • from the starting edge (or top edge) of the parent in dp

  • from the ending edge (or bottom edge) of the parent in dp

  • as a relative percentage of the size of the parent


To switch between these three options click the bubble at the top of the guideline.

  • When you see a percent sign, it means you are positioning with percent.

  • An arrow right is dp from ending edge.

  • An arrow left is dp from starting edge.


You can position the guideline by dragging it to the desired position or in the XML. Here I've positioned the vertical guideline at 85% of the parent.


We can now constrain the ends of our views to the guideline.





It is simple to work with guidelines in XML. Check out the code:




Utilizing Match Constraint


Step 9: We want to limit the width of our two long TextViews. We'll call them the Perfect Plant TextView and the Terms of Service TextView.


To do that constrain them to the guideline and give them a width of "0dp". Make sure the height is "wrap_content." That lets the height increase to fit all of the text.




Now our TextViews have a finite width.



Constraint Bias


Constraint biases are an important tool for dynamically positioning views so that they work well with different screen sizes.


We can give a view a vertical bias or a horizontal bias.


To create a constraint bias, a child View needs two edges to serve as beginning and end points. We decide which constraint to favor, or the bias. Imagine the constraints working like springs to keep the child View anchored within its parent.


Do you want the child View in the center?

That means each spring has equal strength so the bias will be 50% or .5.


Do you want the child View closer to the left?

The left spring must be shorter than 50%. If we choose a smaller horizontal bias such as 25% or .25, it means the child View will begin after 25% of the space between the two anchor points is passed.


Do you want the child View closer to the right?

The left spring must be longer than 50%. If we choose a larger horizontal bias such as 75% or .75, it means the child View will begin after 75% of the space between the two anchor points is passed.


Step 10: For design purposes, we don't want the What's My Plant TextView to be centered. We want it slightly offset from the end of the imageView.


We use a constraint bias to begin the What's My Plant TextView after 1/3 or 33% of the space between the two anchor points is passed.

To implement the horizontal bias we need two edges between which to position the What's My Plant TextView. We have a start constraint, now add an end constraint.


Constrain the right anchor to the guideline. In XML that's:


 app:layout_constraintEnd_toStartOf="@+id/guideline7" 

You may have noticed that all of the TextViews shifted right slightly. That's because the What's My Plant TextView is equally positioned between the ImageView and the guideline. That's a horizontal bias of 50% (.5).


We want What's My Plant TextView to begin after 1/3 of the distance between the ImageView and the guideline. We can use the horizontal bias slider in the Constraint Widget in the Attributes panel to do that.


First click on the What's My Plant TextView. Adjust the horizontal bias slider to 33%.





In the Code editor you can now see:


app:layout_constraintHorizontal_bias="0.33"


Want to learn more about ConstraintLayout?


Want to build on your knowledge of ConstraintLayout and challenge yourself to create something more dynamic? Stay tuned for the my next post.





Final source code for activity_main.xml:


<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="30dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/snake_plant" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="What's My Plant?"
    android:textAppearance="@style/TextAppearance.AppCompat.Headline"
        app:layout_constraintBottom_toBottomOf="@+id/imageView"
        app:layout_constraintEnd_toStartOf="@id/guideline7"
        app:layout_constraintHorizontal_bias="0.33"
        app:layout_constraintStart_toEndOf="@id/imageView"
        app:layout_constraintTop_toTopOf="@+id/imageView" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="60dp"
        android:text="Pick the perfect plant for your room and lifestyle!"
        app:layout_constraintStart_toStartOf="@+id/textView"
        app:layout_constraintEnd_toStartOf="@id/guideline7"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="60dp"
        android:text="Talk to a stylist."
        app:layout_constraintStart_toStartOf="@+id/textView2"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="60dp"
        android:text="Take a lifestyle assessment"
        app:layout_constraintStart_toStartOf="@+id/textView3"
        app:layout_constraintTop_toBottomOf="@+id/textView3" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="61dp"
        android:text="SIGN UP"
        app:layout_constraintStart_toStartOf="@+id/textView4"
        app:layout_constraintTop_toBottomOf="@+id/textView4" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="30dp"
        android:text="LOGIN"
        app:layout_constraintBottom_toBottomOf="@+id/button"
        app:layout_constraintStart_toEndOf="@+id/button"
        app:layout_constraintTop_toTopOf="@+id/button" />

    <TextView
        android:id="@+id/textView5"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="60dp"
        android:text="By signing up, I agree to My Plant's Terms of Service, Privacy policy, Refund Policy and Accidental Pet Death by Dieffenbachia amoena Policy."
        app:layout_constraintStart_toStartOf="@+id/button"
        app:layout_constraintEnd_toStartOf="@id/guideline7"
        app:layout_constraintTop_toBottomOf="@+id/button" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintGuide_percent="0.85" />

</androidx.constraintlayout.widget.ConstraintLayout>






733 views0 comments