Android ConstraintLayout Tips

This is not a tutorial on how to use Android ConstraintLayout, it’s more a collection of tips on how to do things using ConstraintLayout. These are things that I’ve learnt over the last few years that I find useful, so I’m putting them here as a reminder to myself and anyone else who cares to read them.


1. Square Images

This is something I only found fairly recently. I love the power of ConstraintLayout and how it keeps improving, and this is one of the things that makes me realise how good it is.

I remember a few years ago when I wanted a square image I would create a very simple subclass of ImageView and override its onMeasure method like this:

@Override   
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {     
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = getMeasuredWidth();
    setMeasuredDimension(width, width);
}

But now we can do it natively using the layout_constraintDimensionRatio attribute of ConstraintLayout:

<ImageView
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:adjustViewBounds="true"
    android:scaleType="centerCrop"
    app:layout_constraintDimensionRatio="H,1:1"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

Obviously you can use any ratio you want depending on your requirement, and you can constrain the ratio vertically instead of horizontally as I have here.


2. Overlapping Views

If you need to overlap two views so that the second one sits on top of the first one by a fixed amount, this is a neat way of doing it. I learnt this one a while ago, it applies to other layout views as well, not just ConstraintLayout.

I’ll start with something simple that I had to do for a real app for a client a few weeks ago. We had a full width square product image at the top of the screen, with a circular thumbnail of a second image aligned to the bottom of the first image and overlapping by half the second image’s radius (In fact there were several of these thumbnails, but I’ll keep it simple here). We also needed a narrow white border around the circular image so that the overlapped part stood out from the background image. Here’s how it looked:

And here’s the relevant part of the layout:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/imgProduct"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:adjustViewBounds="true"
        android:scaleType="centerCrop"
        app:layout_constraintDimensionRatio="H,1:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/imgThumbnail"
        android:layout_width="40dp"
        android:layout_height="40dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imgProduct"
        app:layout_constraintBottom_toBottomOf="@+id/imgProduct"
        android:background="@drawable/thumbnail_border"/>

As you can see all you need to do is align the top and bottom of the circular image with the bottom of the main image and this puts the centre of the circle on the bottom of the square image.

To make the thumbnail a circle I use the excellent Android image loading library Coil with a circular transformation:

imgThumbnail.load(thumbnailUrl) {
    transformations(CircleCropTransformation())
}

The 1dp white border around the thumbnail comes from an xml drawable set as the background:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@android:color/white" />
    <padding android:bottom="1dp" android:left="1dp" android:right="1dp" android:top="1dp" />
</shape>

Now to something a bit more involved. The designer wanted a title and subtitle on a screen, but instead of the subtitle sitting directly below the title, which would create quite a big line spacing, he wanted the subtitle to overlap the title by 8dp. This created the look of the subtitle aligning almost to the bottom of the title text.

In the past I’ve seen people recommending you achieve this just by specifying a negative top margin for the subtitle TextView by using

android:layout_marginTop="-8dp"

This will probably work on some devices on some versions of Android, but it’s not a safe way of doing it, it definitely won’t work all the time.

The correct way to do this is to use a zero height Space widget between the two TextViews, aligned to the bottom of the first TextView, with a bottom margin of 8dp. This makes the Space widget overlap the first TextView by 8dp, you can then align the top of the second TextView to the bottom of the Space widget:

<TextView
    android:id="@+id/title1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    android:textSize="28sp"
    android:text="MAIN TITLE"/>

<Space
    android:id="@+id/vert_space"
    android:layout_width="wrap_content"
    android:layout_height="0dp"
    android:layout_marginBottom="8dp"
    app:layout_constraintBottom_toBottomOf="@+id/title1"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent" />

<TextView
    android:id="@+id/title2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toBottomOf="@+id/vert_space"
    app:layout_constraintEnd_toEndOf="@id/title1"
    android:textSize="14sp"
    android:text="subtitle"/>

And here’s how it looks (I’ve removed all the styling for simplicity):


3. Groups of Views

Another simple one but super useful. If you have a number of views in a layout that you want to do the same thing to in code such as show/hide them all.

In the bad old days before ConstraintLayout we would have created a nested LinearLayout or RelativeLayout to wrap all the views and then reference that in code to perform whatever operation we wanted.

Now with ConstraintLayout we can use a ConstraintLayout Group which allows us to do that. Create a Group in the layout xml and use its constraint_referenced_ids attribute to tell it which other views in the layout it will act upon.

<androidx.constraintlayout.widget.Group
    android:id="@+id/grp_product_type"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:constraint_referenced_ids="img_product_type, img_product_main"/>

<ImageView
    android:id="@+id/img_product_type"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintStart_toStartOf="parent"/>

<ImageView
    android:id="@+id/img_product_main"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toTopOf="@+id/img_product_type"
    app:layout_constraintBottom_toBottomOf="@+id/img_product_type"
    app:layout_constraintStart_toStartOf="@+id/img_product_type"
    app:layout_constraintEnd_toEndOf="@+id/img_product_type"/>

Then in code if you want to hide all the views referenced by the group, just write:

binding.grpProductType.isVisible = false

Note I’m using view binding here which is the preferred way of referencing views in code.


Leave a Reply

Your email address will not be published. Required fields are marked *


The reCAPTCHA verification period has expired. Please reload the page.