Achieving Negative Margin in ConstraintLayout

Mar 8, 2021

IMG_0843.jpg

Historically, negative margins were not supported in ConstraintLayout. It is changing though—this feature has been added to ContraintLayout 2.1.0-alpha2 but not available in stable version yet at the time of this writing.

In the meantime, we can use a Space widget to accomplish the same task.

Space

The idea is to define a View of type Space with 0 height and/or width with a margin set to achieve the negative margin effect. For instance, see this UI:

space.png

Notice how the blue background below overlaps with the CardView above it. Note that this particular layout could have been implemented as a LinearLayout with negative margins but let’s learn how to do it with ConstraintLayout.

Below is the layout code—pay attention to the Space View, its margin and how the blue background below is anchored to that spacer.

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.main.MainFragment">

    <androidx.cardview.widget.CardView
        android:id="@+id/card"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="48dp"
        android:layout_marginEnd="48dp"
        app:cardCornerRadius="8dp"
        app:cardElevation="10dp"
        app:cardUseCompatPadding="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

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

            <TextView
                android:id="@+id/cardTitle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/card_title_text"
                android:textSize="32sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/cardDescription"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="@string/card_description_text"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/cardTitle" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.cardview.widget.CardView>
    
    <Space
        android:id="@+id/bgSpacer"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="60dp"
        app:layout_constraintBottom_toBottomOf="@id/card"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#EFF7F8"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/bgSpacer">

        <TextView
            android:id="@+id/otherText1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="60dp"
            android:layout_marginEnd="16dp"
            android:text="@string/other_text1"
            android:textSize="18sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/otherText2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:text="@string/other_text2"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/otherText1" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Bonus: Adding an Arc

A quick way to take this layout to the next level visually is to add an arc shape to the blue background. This can be done by using a shape or an image to render an arc anchored to the top of the Space View.

space-arc.png

Looks better, doesn’t it? And all we had to do to achieve this is this:

    <View
        android:layout_width="0dp"
        android:layout_height="40dp"
        android:background="@drawable/bg_arc"
        app:layout_constraintBottom_toTopOf="@id/bgSpacer"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

Here is a full revised layout:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.main.MainFragment">

    <androidx.cardview.widget.CardView
        android:id="@+id/card"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="48dp"
        android:layout_marginEnd="48dp"
        app:cardCornerRadius="8dp"
        app:cardElevation="10dp"
        app:cardUseCompatPadding="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

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

            <TextView
                android:id="@+id/cardTitle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/card_title_text"
                android:textSize="32sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/cardDescription"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="@string/card_description_text"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/cardTitle" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.cardview.widget.CardView>

    <View
        android:layout_width="0dp"
        android:layout_height="40dp"
        android:background="@drawable/bg_arc"
        app:layout_constraintBottom_toTopOf="@id/bgSpacer"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Space
        android:id="@+id/bgSpacer"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="60dp"
        app:layout_constraintBottom_toBottomOf="@id/card"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#EFF7F8"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/bgSpacer">

        <TextView
            android:id="@+id/otherText1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="60dp"
            android:layout_marginEnd="16dp"
            android:text="@string/other_text1"
            android:textSize="18sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/otherText2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:text="@string/other_text2"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/otherText1" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Hope this example was helpful. Full source code is available in this repo https://github.com/jshvarts/ConstraintLayoutOverlappingViewsDemo

 
Previous
Previous

Flow widget in ConstraintLayout

Next
Next

RecyclerView DiffUtil with Change Payload