I have had a couple of my layouts blow up since changing over to ConstraintLayout version 1.1.0-beta4. Before I make any changes, I want to get a better understanding of how margins work in ConstraintLayout
chains. In the following, I compare a layout in ConstraintLayout
version 1.0.2 to version 1.1.0-beta4, but I believe that the issue first arose in 1.1.0-beta2.
My goal is to have some text views stretch across the screen with gaps between the 1st and 2nd text views and the 2nd and 3rd text views. The background should show in these margins. To do this, I create a horizontal chain and specify an end margin from the left text view to the center text view and an end margin from the center text view to the right text view. The horizontal chain style is spread_inside
.
Example 1 - Using ConstraintLayout version 1.0.2
This is how things look in version 1.0.2 and is what I expect.
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@android:color/holo_blue_light">
<TextView
android:id="@+id/tvLeft"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginEnd="8dp"
android:background="@android:color/white"
android:gravity="center"
android:text="Text1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tvCenter"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tvCenter"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginEnd="8dp"
android:background="@android:color/white"
android:gravity="center"
android:text="Text2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tvRight"
app:layout_constraintStart_toEndOf="@+id/tvLeft"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tvRight"
android:layout_width="0dp"
android:layout_height="35dp"
android:background="@android:color/white"
android:gravity="center"
android:text="Text3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/tvCenter"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
</android.support.constraint.ConstraintLayout>
Example 2 - Using ConstraintLayout version 1.1.0-beta4
This same layout looks like the following in version 1.1.0-beta4 of ConstraintLayout
. Notice that the margins have disappeared. I expect that this should look the same as example 1, but it doesn't.
Example 3 - Using ConstraintLayout version 1.1.0-beta4 with start margin
If I take this same layout and simply add a start margin of 8dp
to the right text view (tvRight
), my margins reappear not only between the center and right text views but also between the left and center textviews although I have not changed the margins there.
This is more than just the previously set margins suddenly being honored. If I set the start margin on the rightmost text view to '48dp', what appears to be a 48dp
margin also appears between the left and center text views.
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@android:color/holo_blue_light">
<!-- TextViews tvLeft & tvRight not shown but are the same as above.-->
<TextView
android:id="@+id/tvRight"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginStart="48dp"
android:background="@android:color/white"
android:gravity="center"
android:text="Text3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/tvCenter"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
</android.support.constraint.ConstraintLayout>
So, my question is, "Why am I seeing these results?" How are margins handled in ConstraintLayout
chains, especially spread_inside
chains? Has there been a change in the way chain margins are handled, or am I missing something? I am looking for an explanation or a reference to some documentation that explains all this.
Creating a chain is really easy, we can click and drag to select views or press ctrl and select views from Component Tree. And then right click on selected views in Editor Or Component Tree to apply constraints. You can see that in action in following screen records.
Bias, in terms of ConstraintLayout , means "if there is extra room, slide the widget in this direction along the axis". The default bias is 0.5, meaning that the widget is centered in the available space.
Chains In ConstraintLayout: Chains allow us to control the space between elements and chains all the selected elements to another. To create a chain, select the elements that you want to form part of the chain, and then right click – “Chain” – “Create Horizontal or Vertical Chain”.
This blog post aims to cover some of the new features, namely Guidelines, Barriers, Chains and Groups. Some of these features require using Android Studio 3.0 Beta 5 and the beta version of ConstraintLayout.
What are chains? Chains are a specific kind of constraint which allow us to share space between the views within the chain and control how the available space is divided between them. The closest analogue with traditional Android layouts is weights in LinearLayout, but chains do far more than that, as we shall see.
In order to use the beta version of ConstraintLayout, make sure you have at least the following dependency in your app level build.gradle file: implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta1' Guidelines are small visual helpers to design layouts with.
I can find no documentation that gives an authoritative answer to this exact question. However, there is a little bit of discussion about margins in the API documentation for ConstraintLayout
:
If side margins are set, they will be applied to the corresponding constraints (if they exist)
In the specific instance of a chain, you have two-way constraints between each view. That is, not only is View A's end constrained to View B's start, but View B's start is also constrained to View A's end.
In your posted layout, View A has an end constraint and an end margin, but View B has a start constraint with no start margin. As far as I can tell, this means you have conflicting rules in your layout (View A wants to be 8dp away from View B, but View B wants to be 0dp from View A). Perhaps different versions of the ConstraintLayout library have different strategies for (a) determining whether this even counts as a conflict and (b) resolving the conflict if so.
Via experimentation, here is how I've found margins to work in chains on different ConstraintLayout library versions:
Side margins on each view in the chain don't depend on or affect other views in the chain. This has (at least) two visible effects on behavior. First, adding margin to one view will push the other view away by that amount, regardless of that view's margins. Second, adding margin to one view will not affect margins of views farther down the chain (e.g. putting 8dp end margin on your first view does not by itself also cause 8dp worth of space to appear between your second and third views).
Side margins on each view in the chain both depend on and affect other views in the chain. Again, this has two visible effects on behavior. First, adding margin to one view will not push the other view away unless it also has a margin of that same amount*. Second, adding margin between the first and second view of the chain will also affect the spacing between the second and third view of the chain**.
*: It seems that 1.1.0-beta4 allows just a start margin to push the views apart, while just an end margin will have no effect. Regardless, I recommend matching the margins.
**: I suspect this is because the chain is trying to allocate "space" evenly. The margins between views A and B create a gap, and since the chain wants to enforce a consistent spacing it adds a similar gap between views B and C.
Examples:
Stripped way down, here's a layout just like your original, with the margins changed slightly. I've left every other attribute unchanged.
<android.support.constraint.ConstraintLayout>
<TextView
android:layout_marginEnd="8dp"/>
<TextView
android:layout_marginStart="8dp"/>
<TextView/>
</android.support.constraint.ConstraintLayout>
v1.0.2:
v1.1.0-beta4:
This should illustrate the two differences between the library versions. Again, I've been completely unable to find official documentation that explains all this, but it appears to be true just based on experimentation.
Expanding on Ben P.'s answer, I have determined the following regarding margins in ConstraintLayout
chains. This information applies to ConstraintLayout
version 1.1.0-beta4.
Within a chain, all start margins (android:layout_marginStart
) are honored. This means that the spacing between the views will not be less than the specified start margin. However, the spacing may be greater as explained below.
End margins (android:layout_marginEnd
) have no relevance and seem to be ignored. This does not apply to the end margins of the view at the end of the chain but only to the interior margins where views are cross-linked to create the chain.
When a chain is centered within its constraints, the chain is centered between the start margin of the chain's head and the end margin of the chain's tail.
In the examples below, views "A","D" ang "G" are constrained to the parent start. Views "C", "F" and "I" are constrained to the parent end.
packed
If the chain style is "packed," all views are placed end-to-end separated by the specified start margins. The spacing between the views can vary according to how the start margins are defined. In the following image, the width of the views are match_constraints
and the margins are set as indicated.
If the widths of the views are set to something other than match_constraints
, the views are still packed with the specified margins but the chain is centered between the start margin of the chain's head and the end margin of the chain's tail.
I came to this interpretation instead of considering the end margin to be attached to the end view because the Android Studio designer has this same interpretation:
spread
In the "spread" chain style, all views are distributed between the start and end constraints such that the space before and after each view is the same and equal to the greatest start margin defined. If the width of each view is match_constraints
, then all the views will have the same width by default.
spread_inside
The spread_inside
style of chains will take the first view of the chain and anchor it to its start constraint while honoring its start margin. The end view will be anchored to its end constraint while honoring its end margin. Interior views will be distributed with equal spacing between the views like spread
chains.
Below is the same layout with various margins set. Views "F" and "I" have a start margin of 8dp
set but the gap has expanded to 16dp
. View "G", "H" and "I" are all of equal width although they don't appear to be.
The XML for this layout is presented at the end of this post.
Of interest but of no real importance: The different chain types are indistinguishable if the views have a width of match_constraints
and all margins are zero.
The information above also applies to vertical chains. Substitute android:layout_marginTop
for android:layout_marginStart
and android:layout_marginBottom
for android:layout_marginEnd
.
Layout
<android.support.constraint.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_light">
<TextView
android:id="@+id/heading1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="packed, match_constraints"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/textA"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginTop="8dp"
android:background="@android:color/white"
android:gravity="center"
android:text="A"
android:textColor="@android:color/black"
app:layout_constraintEnd_toStartOf="@+id/textB"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/heading1"
tools:ignore="HardcodedText" />
<View
android:layout_width="8dp"
android:layout_height="35dp"
android:background="#ff00cc"
app:layout_constraintEnd_toStartOf="@id/textB"
app:layout_constraintTop_toTopOf="@id/textB" />
<TextView
android:id="@+id/textB"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginStart="8dp"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="B"
android:textColor="@android:color/white"
app:layout_constraintEnd_toStartOf="@+id/textC"
app:layout_constraintStart_toEndOf="@+id/textA"
app:layout_constraintTop_toTopOf="@+id/textA"
tools:ignore="HardcodedText" />
<View
android:id="@+id/view16dpOnC"
android:layout_width="16dp"
android:layout_height="35dp"
android:background="#fffb00"
app:layout_constraintEnd_toStartOf="@id/textC"
app:layout_constraintTop_toTopOf="@+id/textC" />
<TextView
android:id="@+id/textC"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginStart="16dp"
android:background="@android:color/white"
android:gravity="center"
android:text="C"
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textB"
app:layout_constraintTop_toTopOf="@+id/textA"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/heading2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="spread, match_constraints"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/textA"
tools:ignore="HardcodedText" />
<View
android:layout_width="16dp"
android:layout_height="35dp"
android:background="#00ff19"
app:layout_constraintEnd_toStartOf="@id/textD"
app:layout_constraintTop_toTopOf="@id/textD" />
<TextView
android:id="@+id/textD"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginTop="8dp"
android:background="@android:color/white"
android:gravity="center"
android:text="D"
android:textColor="@android:color/black"
app:layout_constraintEnd_toStartOf="@+id/textE"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/heading2"
tools:ignore="HardcodedText" />
<View
android:layout_width="16dp"
android:layout_height="35dp"
android:background="#fffb00"
app:layout_constraintEnd_toStartOf="@id/textE"
app:layout_constraintTop_toTopOf="@id/textE" />
<TextView
android:id="@+id/textE"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginStart="16dp"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="E"
android:textColor="@android:color/white"
app:layout_constraintEnd_toStartOf="@+id/textF"
app:layout_constraintStart_toEndOf="@+id/textD"
app:layout_constraintTop_toTopOf="@+id/textD"
tools:ignore="HardcodedText" />
<View
android:layout_width="8dp"
android:layout_height="35dp"
android:background="#003cff"
app:layout_constraintStart_toEndOf="@id/textE"
app:layout_constraintTop_toTopOf="@+id/textE" />
<View
android:layout_width="8dp"
android:layout_height="35dp"
android:background="#ff00cc"
app:layout_constraintEnd_toStartOf="@id/textF"
app:layout_constraintTop_toTopOf="@id/textF" />
<TextView
android:id="@+id/textF"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginStart="8dp"
android:background="@android:color/white"
android:gravity="center"
android:text="F"
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textE"
app:layout_constraintTop_toTopOf="@+id/textD"
tools:ignore="HardcodedText" />
<View
android:layout_width="16dp"
android:layout_height="35dp"
android:background="#00ff19"
app:layout_constraintStart_toEndOf="@id/textF"
app:layout_constraintTop_toTopOf="@id/textF" />
<TextView
android:id="@+id/heading3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="spread_inside, match_constraints"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/textD"
tools:ignore="HardcodedText" />
<View
android:layout_width="8dp"
android:layout_height="35dp"
android:background="#003cff"
app:layout_constraintEnd_toStartOf="@id/textG"
app:layout_constraintTop_toTopOf="@+id/textG" />
<TextView
android:id="@+id/textG"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="@android:color/white"
android:gravity="center"
android:text="G"
android:textColor="@android:color/black"
app:layout_constraintEnd_toStartOf="@+id/textH"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/heading3"
tools:ignore="HardcodedText" />
<View
android:layout_width="16dp"
android:layout_height="35dp"
android:background="#fffb00"
app:layout_constraintEnd_toStartOf="@id/textH"
app:layout_constraintTop_toTopOf="@id/textH" />
<TextView
android:id="@+id/textH"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginStart="16dp"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="H"
android:textColor="@android:color/white"
app:layout_constraintEnd_toStartOf="@+id/textI"
app:layout_constraintStart_toEndOf="@+id/textG"
app:layout_constraintTop_toTopOf="@+id/textG"
tools:ignore="HardcodedText" />
<View
android:layout_width="8dp"
android:layout_height="35dp"
android:background="#003cff"
app:layout_constraintStart_toEndOf="@id/textH"
app:layout_constraintTop_toTopOf="@id/textH" />
<View
android:layout_width="8dp"
android:layout_height="35dp"
android:background="#ff00cc"
app:layout_constraintEnd_toStartOf="@id/textI"
app:layout_constraintTop_toTopOf="@id/textI" />
<TextView
android:id="@+id/textI"
android:layout_width="0dp"
android:layout_height="35dp"
android:layout_marginStart="8dp"
android:background="@android:color/white"
android:gravity="center"
android:text="I"
android:textColor="@android:color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textH"
app:layout_constraintTop_toTopOf="@+id/textG"
tools:ignore="HardcodedText" />
<View
android:layout_width="8dp"
android:layout_height="35dp"
android:background="#ff00cc"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@id/textC"
app:layout_constraintTop_toTopOf="@id/textC" />
<View
android:id="@+id/view8dp"
android:layout_width="8dp"
android:layout_height="35dp"
android:layout_marginStart="24dp"
android:background="#ff00cc"
app:layout_constraintBottom_toTopOf="@id/view8dpGap"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textG"
app:layout_constraintVertical_bias="0.100000024"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/text8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="8dp start margin"
app:layout_constraintBottom_toBottomOf="@+id/view8dp"
app:layout_constraintStart_toEndOf="@id/view8dp"
app:layout_constraintTop_toTopOf="@+id/view8dp"
tools:ignore="HardcodedText" />
<View
android:id="@+id/view8dpGap"
android:layout_width="8dp"
android:layout_height="35dp"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:background="#003cff"
app:layout_constraintBottom_toTopOf="@+id/view16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view8dp" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="8dp gap not defined by start margin"
app:layout_constraintBottom_toBottomOf="@+id/view8dpGap"
app:layout_constraintStart_toEndOf="@+id/view8dpGap"
app:layout_constraintTop_toTopOf="@+id/view8dpGap"
tools:ignore="HardcodedText" />
<View
android:id="@+id/view16dp"
android:layout_width="17dp"
android:layout_height="35dp"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:background="#fffb00"
app:layout_constraintBottom_toTopOf="@+id/view16dpGap"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view8dpGap" />
<TextView
android:id="@+id/text16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="16dp start margin"
app:layout_constraintBottom_toBottomOf="@+id/view16dp"
app:layout_constraintStart_toEndOf="@+id/view16dp"
app:layout_constraintTop_toTopOf="@+id/view16dp"
tools:ignore="HardcodedText" />
<View
android:id="@+id/view16dpGap"
android:layout_width="17dp"
android:layout_height="35dp"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:background="#00ff19"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view16dp" />
<TextView
android:id="@+id/text16dpGap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="16dp gap not defined by start margin"
app:layout_constraintBottom_toBottomOf="@+id/view16dpGap"
app:layout_constraintStart_toEndOf="@+id/view16dpGap"
app:layout_constraintTop_toTopOf="@+id/view16dpGap"
tools:ignore="HardcodedText" />
</android.support.constraint.ConstraintLayout>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With