Achieving Negative Margin in ConstraintLayout
Mar 8, 2021
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:
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.
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