Loading... Search articles

Search for articles

Sorry, but we couldn't find any matches...

But perhaps we can interest you in one of our more popular articles?
Designing complex UI using Android ConstraintLayout

Designing complex UI using Android ConstraintLayout

Aug 29, 2020

Use M1 Mac mini VMs by default with Codemagic🚀 Build faster

Written by Souvik Biswas

Creating a complex user interface design on Android with a nested view hierarchy used to be common. But with the introduction of ConstraintLayout, this has changed a lot. ConstraintLayout reduces a lot of the challenges of building complex layouts, as you can now design them using the GUI of Layout Editor by just dragging UI elements into a visual design editor instead of writing layout in an XML file by hand.

You can take full advantage of this Layout Editor while using ConstraintLayout (part of Android Jetpack libraries). It uses a flat view hierarchy and also provides some performance benefits.

If you want more information regarding the performance benefits of ConstraintLayout, check out this Android developer’s blog post.

If you need more information on getting started and developing an application with Jetpack Compose, check out this article by a Senior Mobile Engineer Salih.

So, let’s get started, but first, let us know what’s your relationship with CI/CD tools?

Overview of constraints

You must set at least two constraints, one horizontal and one vertical constraint, to position a view element properly on the screen.

If you do not have Autoconnection turned on, any view that you drop in the Layout Editor stays where you leave it without any constraints. However, when you run an app with a view element without any constraints, it is by default rendered to the position [0,0] (the top-left corner of the device screen).

Now, let’s look into the different types of constraints that you can set on a view element to position and adjust UI components.

Types of constraints

Android Studio allows you to use the following types of constraints:

  1. Fixed constraint: A constraint that is specified using a hard-coded number.

  2. Adaptable constraint: A constraint that defines a relationship in relative and weighted terms.

  3. Absolute positioning: A numerical positioning, such as the position in x, y coordinates.

  4. Relative positioning: Views are positioned by specifying relationships with other views.

Some of the useful features that you can use with ConstraintLayout are listed below:

  • Chains
  • Bias
  • Ratios
  • Guideline
  • Barrier
  • Baseline
  • Group
  • Circular positioning
  • Flow
  • ConstraintSet

Let’s talk about these in more detail.

Chains

A chain is a group of views that are linked to each other with bi-directional position constraints. The views within a chain can be distributed either vertically or horizontally.

Some of the ways in which chains can be styled are shown below:

  • Spread: Elements are evenly distributed along the mentioned axis. This the default style for chains.

  • Spread inside: Elements are spread to use the available space with the head and tail attached to the parent.

  • Weighted: Elements are resized to use all the available space according to specified weights with the head and tail glued to the parent.

    This can be achieved when the chain is set to either spread or spread inside. You can fill the remaining space by setting one or more views to match constraint (0dp).

  • Packed: Elements are packed to use minimum space.

  • Packed with bias: Elements are packed to use minimum space and are moved on their axis depending on bias.

The XML code with the default chain style looks like below:

<Button
    android:id="@+id/button1"
    android:layout_width="56dp"
    android:layout_height="56dp"
    app:layout_constraintBottom_toBottomOf="@+id/button2"
    app:layout_constraintEnd_toStartOf="@+id/button2"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@+id/button2" />

<Button
    android:id="@+id/button2"
    android:layout_width="56dp"
    android:layout_height="56dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toStartOf="@+id/button3"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toEndOf="@+id/button1"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.514" />

<Button
    android:id="@+id/button3"
    android:layout_width="56dp"
    android:layout_height="56dp"
    app:layout_constraintBottom_toBottomOf="@+id/button2"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toEndOf="@+id/button2"
    app:layout_constraintTop_toTopOf="@+id/button2" />

Bias

When a view is constrained to both sides on either axis, it is centered on that axis, and the bias value is set to 50%. You can adjust this bias by using the slider present in the Attributes panel under the Layout tab.

The XML code looks like below:

<Button
    android:id="@+id/button"
    android:layout_width="56dp"
    android:layout_height="56dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.2"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

Ratio

In ConstraintLayout, you can set the size of a view by defining an aspect ratio. To use ratio, set at least one of the view dimensions (i.e., either height or width, or both) to match constraint (0dp).

Then you will notice a small triangle in the top-left corner.

Click on it to enable ratio for that selected view. Now, you can define any aspect ratio in the format width:height. (By default, it is set to 1:1.)

If you have the height fixed to a certain dp value or wrap_content and width to match constraint, then the ratio will define the width with respect to the height and vice versa.

If you have both the width and height set to match constraint, then the ratio will define both of them with respect to the parent. In this case, you get some more options. For example, if you press the triangle a second time, it will set the ratio with respect to the height (of the parent). Pressing the triangle a third time will set the ratio with respect to the width (of the parent).

Applying ratio with only width set to match constraint (0dp)

The XML code looks like below:

<Button
    android:id="@+id/button"
    android:layout_width="0dp"
    android:layout_height="56dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintDimensionRatio="h,1:1"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

Guideline

A Guideline is a virtual helper object that allows you to create horizontal and vertical guidelines, which are positioned relative to the ConstraintLayout container. You can then constrain other view elements relative to your specified guideline.

A guideline can be created by right-clicking on the Design editor. Then under Helpers, select either Add Vertical Guideline or Add Horizontal Guideline. You can also do this by clicking the Guidelines icon in the toolbar.

After adding the guideline, you can drag the dotted line by holding the circle to reposition it (by default, it is measured in dp from the left edge of the container).

You can click on the circle to toggle the edge from which the dp value is set, or you can set it as a percentage.

The XML code looks like below:

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline5"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_begin="68dp" />

Barrier

A Barrier is similar to a guideline, but unlike a guideline, it doesn’t need to have a fixed position. It can move based on the positions of views it contains, and you can constrain other view elements to it.

Barriers are particularly useful when you’re laying out internationalized strings or displaying user-generated content with an unpredictable size that can change during runtime.

You can create a barrier by right-clicking on the Design editor, then under Helpers, selecting either Add Vertical Barrier or Add Horizontal Barrier. You can also click the Guidelines icon in the toolbar and select the option accordingly.

The XML code looks like below:

<androidx.constraintlayout.widget.Barrier
    android:id="@+id/barrier"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:barrierDirection="end"
    app:constraint_referenced_ids="button7,button6" />

Baseline

You can align the baseline of multiple text views, regardless of their font sizes. This constraint makes it much easier to align text views.

If you are not sure what baseline is exactly, Material Design Typography states it as follows:

In order to create a baseline constraint, right-click on a text view and select Show Baseline. Then, you can just click on the text baseline of that view and drag it to the baseline of another to align them.

The XML code looks like below:

<TextView
    android:id="@+id/large_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Large text"
    android:textSize="22sp"
    app:layout_constraintBaseline_toBaselineOf="@+id/small_text"
    app:layout_constraintEnd_toStartOf="@+id/small_text"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintHorizontal_chainStyle="packed"
    app:layout_constraintStart_toStartOf="parent" />

<TextView
    android:id="@+id/small_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Small text"
    android:textSize="12sp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toEndOf="@+id/large_text"
    app:layout_constraintTop_toTopOf="parent" />

Group

You can use a Group to logically group together certain view elements. A group contains references to the view ids. This is pretty useful if you want to change the visibility of multiple view elements, as you just set the visibility of the whole group rather than setting it for each element.

You can create a group by selecting the view elements that you want to include in the group. Then right-click, and under Helpers, select Add Group.

The XML code looks like below:

<androidx.constraintlayout.widget.Group
    android:id="@+id/group3"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:constraint_referenced_ids="button3,button2,button1" />

Circular positioning

Circular positioning allows you to constrain a widget center relative to another widget center at an angle and a distance. This allows you to position a view element in a circular formation around the central view.

An example of a Floating Action Button with multiple options:

The XML code for this is as follows:

<com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/edit_fab"
    style="@style/Widget.Design.FloatingActionButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginEnd="16dp"
    android:layout_marginBottom="16dp"
    app:backgroundTint="@color/colorPrimaryDark"
    app:fabSize="normal"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:srcCompat="@drawable/ic_baseline_edit_24" />

<com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/aspect_ratio_fab"
    style="@style/Widget.Design.FloatingActionButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    app:backgroundTint="@color/colorPrimary"
    app:fabSize="normal"
    app:layout_constraintCircle="@+id/edit_fab"
    app:layout_constraintCircleAngle="0"
    app:layout_constraintCircleRadius="90dp"
    app:srcCompat="@drawable/ic_baseline_aspect_ratio_24" />

<com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/crop_fab"
    style="@style/Widget.Design.FloatingActionButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    app:backgroundTint="@color/colorPrimary"
    app:fabSize="normal"
    app:layout_constraintCircle="@+id/edit_fab"
    app:layout_constraintCircleAngle="315"
    app:layout_constraintCircleRadius="90dp"
    app:srcCompat="@drawable/ic_baseline_crop_24" />

<com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/rotate_90_fab"
    style="@style/Widget.Design.FloatingActionButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginEnd="16dp"
    app:backgroundTint="@color/colorPrimary"
    app:fabSize="normal"
    app:layout_constraintCircle="@+id/edit_fab"
    app:layout_constraintCircleAngle="270"
    app:layout_constraintCircleRadius="90dp"
    app:srcCompat="@drawable/ic_baseline_rotate_90_degrees_ccw_24" />

Flow

Flow is a part of ConstraintLayout 2.0. (It is currently in release candidate; the stable version will be announced soon.) It allows you to lay out a long chain of view elements and wrap them into multiple rows or columns. You can specify the flow_wrapMode property to define the wrap style.

The following are the wrap modes available for Flow:

  • Wrap none: This simply creates a chain out of the referenced elements. It doesn’t try to fit them within the flow dimensions.

    The XML code is as follows:

    <androidx.constraintlayout.helper.widget.Flow
      android:layout_width="200dp"
      android:layout_height="wrap_content"
      app:constraint_referenced_ids="button1, button2, button3, button4, button5, button6, button7, button8, button9, button10"
      app:flow_wrapMode="none"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />
    
  • Wrap chain: This creates multiple rows or columns if the referenced views don’t fit within the specified dimension.

    The XML code is as follows:

    <androidx.constraintlayout.helper.widget.Flow
      android:layout_width="200dp"
      android:layout_height="wrap_content"
      app:constraint_referenced_ids="button1, button2, button3, button4, button5, button6, button7, button8, button9, button10"
      app:flow_wrapMode="chain"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />
    
  • Wrap aligned: This is similar to the wrap chain style, but it will align the view elements along the row or column.

    The XML code is as follows:

    <androidx.constraintlayout.helper.widget.Flow
      android:layout_width="200dp"
      android:layout_height="wrap_content"
      app:constraint_referenced_ids="button1, button2, button3, button4, button5, button6, button7, button8, button9, button10"
      app:flow_wrapMode="aligned"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />
    

ConstraintSet

ConstraintSet allows you to create and save constraints defined using a ConstraintLayout. It also allows you to apply them to an existing ConstraintLayout. This can be used for creating animations very easily by defining a starting layout and final layout, and the rest is handled automatically by this class.

The following is a simple animation created using ConstraintSet:

First of all, you have to define the two ConstraintLayouts, i.e., the starting and the final layouts.

Starting layout XML code:

<!--activity_main.xml-->

<layout 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"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/root"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab1"
            style="@style/Widget.Design.FloatingActionButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            app:backgroundTint="@color/colorPrimary"
            app:fabSize="normal"
            app:srcCompat="@drawable/ic_baseline_adb_24" />

        <!-- Other FAB buttons can be defined similarly... -->

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/filter_fab"
            style="@style/Widget.Design.FloatingActionButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            app:backgroundTint="@color/colorPrimaryDark"
            app:fabSize="normal"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:srcCompat="@drawable/ic_baseline_filter_list_24" />

        <androidx.constraintlayout.helper.widget.Flow
            android:id="@+id/flow"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            app:constraint_referenced_ids="fab1, fab2, fab3, fab4, fab5, fab6, fab7, fab8, fab9"
            app:flow_maxElementsWrap="3"
            app:flow_wrapMode="aligned"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Final layout XML code (with changed dimension of the Flow layout):

<!--final_view.xml-->

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab1"
        style="@style/Widget.Design.FloatingActionButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        app:backgroundTint="@color/colorPrimary"
        app:fabSize="normal"
        app:srcCompat="@drawable/ic_baseline_adb_24" />

    <!-- Other FAB buttons can be defined similarly... -->

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/filter_fab"
        style="@style/Widget.Design.FloatingActionButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="16dp"
        app:backgroundTint="@color/colorAccent"
        app:fabSize="normal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:srcCompat="@drawable/ic_baseline_filter_list_24" />

    <androidx.constraintlayout.helper.widget.Flow
        android:id="@+id/flow"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:constraint_referenced_ids="fab1, fab2, fab3, fab4, fab5, fab6, fab7, fab8, fab9"
        app:flow_horizontalStyle="spread_inside"
        app:flow_maxElementsWrap="3"
        app:flow_wrapMode="aligned"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

The Kotlin code for the animation is given below:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.constraintlayout.widget.ConstraintSet
import androidx.transition.TransitionManager
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private lateinit var filterFAB: View

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        addConstraintSetAnimation()
    }

    private fun addConstraintSetAnimation() {
        filterFAB = filter_fab

        var filteredList = false
        val constraint1 = ConstraintSet()
        constraint1.clone(root)

        val constraint2 = ConstraintSet()
        constraint2.clone(this, R.layout.final_view)

        filterFAB.setOnClickListener {
            TransitionManager.beginDelayedTransition(root)
            val constraint = if (filteredList) constraint1 else constraint2
            constraint.applyTo(root)
            filteredList = !filteredList
        }
    }
}

Now, let’s apply the concepts that you have learned above to build a sample app using ConstraintLayout with some simple animations.

Building an app using ConstraintLayout

We will be creating the following user interface design of a movie ticket booking app:

The app will contain three layout designs, which we will be creating using ConstraintLayout. Then we will use ConstraintSet to perform the animations while transitioning between these layouts.

Getting started

  • Create a new project using Android Studio.

    New Project

  • Select Empty Activity and click Next.

  • Enter the name of the project and location. Select the language as Kotlin, and set the minimum SDK version to API 19. Click Finish.

This will create a new Android project for you. Wait for the build indexing to complete.

Main layout

The main layout will consist of a menu button (top-left corner), a cover image of the movie, and its description.

Start by placing the menu button by dragging an ImageButton from the Palette onto the Design Editor, and constrain it to the top-left corner of the layout with a margin of 16dp from the edges.

<ImageButton
    android:id="@+id/menu_button"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:layout_marginStart="16dp"
    android:layout_marginTop="16dp"
    android:background="@null"
    android:contentDescription="@string/menu_button"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:srcCompat="@drawable/ic_scatter" />

You can get the assets used in this project from my GitHub repository. (The link is at the end of this article.)

Now, add two guidelines, which will help in placing the cover image and other view elements on the screen. Place one at 15% and another at 85% from the left edge.

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/left_guideline"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    app:layout_constraintGuide_percent="0.15" />

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

Add a TextView to show the movie status. Constrain it to the bottom of the menu button and to the left guideline.

<TextView
    android:id="@+id/status"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginTop="8dp"
    android:fontFamily="@font/poppins_semibold"
    android:src="@drawable/transition"
    android:text="@string/movie_status"
    android:textAppearance="@style/TextAppearance.AppCompat.Large"
    app:layout_constraintStart_toStartOf="@+id/left_guideline"
    app:layout_constraintTop_toBottomOf="@+id/menu_button" />

Add an ImageView with the cover image. Constrain it to the guidelines that we added earlier, and set the ratio to 2:3.

<ImageView
    android:id="@+id/cover"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:layout_marginStart="16dp"
    android:layout_marginTop="36dp"
    android:layout_marginEnd="16dp"
    android:contentDescription="@string/cover_image"
    android:scaleType="centerCrop"
    app:layout_constraintDimensionRatio="2:3"
    app:layout_constraintEnd_toStartOf="@+id/right_guideline"
    app:layout_constraintStart_toStartOf="@+id/left_guideline"
    app:layout_constraintTop_toBottomOf="@+id/status"
    app:srcCompat="@drawable/red_sparrow" />

We will use a vertical chain with the style set to packed to constrain the description TextView.

<TextView
    android:id="@+id/movie_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginTop="24dp"
    android:fontFamily="@font/poppins_semibold"
    android:text="@string/movie_title"
    android:textAppearance="@style/TextAppearance.AppCompat.Large"
    app:layout_constraintBottom_toTopOf="@+id/desc"
    app:layout_constraintStart_toStartOf="@+id/left_guideline"
    app:layout_constraintTop_toBottomOf="@+id/cover"
    app:layout_constraintVertical_chainStyle="packed" />

<TextView
    android:id="@+id/desc"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginBottom="16dp"
    android:fontFamily="@font/poppins"
    android:text="@string/movie_desc"
    app:layout_constraintBottom_toTopOf="@+id/rating"
    app:layout_constraintStart_toStartOf="@+id/left_guideline"
    app:layout_constraintTop_toBottomOf="@+id/movie_title" />

<TextView
    android:id="@+id/rating"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="16dp"
    android:layout_marginBottom="16dp"
    android:fontFamily="@font/poppins_medium"
    android:text="@string/movie_rating"
    android:textAppearance="@style/TextAppearance.AppCompat.Body1"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="@+id/left_guideline"
    app:layout_constraintTop_toBottomOf="@+id/desc" />

The full XML code of the main layout is available here.

Now, we will add two more layouts, which we will be using for transitioning to and fro.

Cover layout

The UI design of the cover layout is as follows:

This layout will contain the same elements as defined in the main layout. So, just copy-paste the XML code from the activity_main.xml file to the cover_view.xml file, and then redefine some of the constraints to achieve the desired UI.

The full XML code of the cover layout is available here.

Description layout

The description layout will look like this:

This layout is a bit complex, but it can be easily achieved using ConstraintLayout.

First of all, copy-paste the same code from the activity_main.xml file to the description_view.xml file, and modify the constraints as necessary. As you can see, this layout has some extra elements that have to be added.

The date selector can be created by chaining (horizontally) together the views containing date and day.

To center a view element vertically with respect to another, you can constrain the left and right side of the view to another.

Centering

The date view can be created by adding a simple view element with the background color set to white. The bar on top is also created in a similar manner by adding a view with a black background color. Then constrain the start and end of the view with respect to the start and end of the day text view.

Date View

<View
    android:id="@+id/date_selector"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:layout_marginTop="36dp"
    android:background="@android:color/white"
    android:paddingLeft="@dimen/selector_side_padding"
    android:paddingEnd="@dimen/selector_side_padding"
    android:paddingRight="@dimen/selector_side_padding"
    app:layout_constraintBottom_toBottomOf="@+id/day_1"
    app:layout_constraintEnd_toEndOf="@+id/day_1"
    app:layout_constraintStart_toStartOf="@+id/day_1"
    app:layout_constraintTop_toBottomOf="@+id/movie_info" />

Before moving on to the layout animations, you will need to define the position of the new view elements added in this layout in the other two layouts as well. As they should not be visible in those layouts, constrain them such that they are outside of the root view area.

NOTE: If you do not define the appropriate position of the new elements in the main layout, then they will not get loaded in other layouts as well.

The full XML code of the description layout is present here.

Adding animations

You can animate the transition between the layouts using ConstraintSet.

The following is the code for transitioning from the main layout to the cover layout:

// Defining initial ConstraintSet
val initialConstraint = ConstraintSet()
// Here, "root" is the id of the main ConstraintLayout
initialConstraint.clone(root)

// Defining cover ConstraintSet
val coverConstraint = ConstraintSet()
coverConstraint.clone(this, R.layout.cover_view)

// Initiate the transition from the main layout
// to the cover layout as coverImage is clicked
coverImage.setOnClickListener {
    TransitionManager.beginDelayedTransition(root)
    coverConstraint.applyTo(root)
}

If you look carefully, you will notice that the text and the icon buttons also change color from black to white during this layout animation.

You can achieve the same result using this:

val anim = ValueAnimator()
anim.setIntValues(Color.BLACK, Color.WHITE)
anim.setEvaluator(ArgbEvaluator())
anim.addUpdateListener {
    menuButton.setColorFilter(it.animatedValue as Int)
    movieStatus.setTextColor(it.animatedValue as Int)
    movieTitle.setTextColor(it.animatedValue as Int)
    movieDescription.setTextColor(it.animatedValue as Int)
    movieRating.setTextColor(it.animatedValue as Int)
    descriptionButton.setColorFilter(it.animatedValue as Int)
}

anim.duration = 300
anim.start()

In order to animate the date selector, we will be modifying the constraints set on the date view element as a specific day/date is clicked.

  • Define a list of day TextView and a map of day to date TextView:

    val days: List<TextView> = listOf(day_1, day_2, day_3, day_4, day_5, day_6, day_7)
    
    val mapOfDays: Map<TextView, TextView> = mapOf(
        day_1 to date_1,
        day_2 to date_2,
        day_3 to date_3,
        day_4 to date_4,
        day_5 to date_5,
        day_6 to date_6,
        day_7 to date_7
    )
    
  • Set click listeners to these text views:

    for (day in days) {
        day.setOnClickListener { selectDate(it as TextView, descriptionConstraint) }
    }
    
    for (day in mapOfDays) {
        day.value.setOnClickListener { selectDate(day.key, descriptionConstraint) }
    }
    
  • Define the method selectDate, and re-connect the constraints to the appropriate TextView as they are clicked on.

    private fun selectDate(day: TextView, destinationConstraint: ConstraintSet) {
        // re-defining the constraints
        destinationConstraint.connect(
            date_selector.id,
            ConstraintSet.START,
            day.id,
            ConstraintSet.START
        )
        destinationConstraint.connect(
            date_selector.id,
            ConstraintSet.END,
            day.id,
            ConstraintSet.END
        )
    
        // for animating the effect
        TransitionManager.beginDelayedTransition(root)
    
        // applying the constraints
        destinationConstraint.applyTo(root)
    }
    

You have successfully completed building the sample ticket booking app.

Conclusion

ConstraintLayout makes it really simple for Android developers to build complex UI designs. ConstraintSet helps to animate between these layouts with minimal lines of code. There are a lot more functionalities to explore in ConstraintLayout, we’ve covered only the basics in this post. If you want to apply more complex animations, there is a subclass of ConstraintLayout called MotionLayout. This allows you to define a whole new level of animation for your Android apps.

Learn more


Souvik Biswas is a passionate Mobile App Developer (Android and Flutter). He has worked on a number of mobile apps throughout his journey. Loves open source contribution on GitHub. He is currently pursuing a B.Tech degree in Computer Science and Engineering from Indian Institute of Information Technology Kalyani. He also writes Flutter articles on Medium - Flutter Community.

Latest articles

Show more posts