51

I am having trouble using tags <include> and <merge> inside a ConstraintLayout.

I want to create a flat view hierarchy (hence Constraints) but still have elements that are reusable. So I use <include> in my layout and <merge> in the included layouts to avoid having nested layouts (especially avoiding nested ConstraintLayouts)

So I wrote this: Parent layout

<android.support.constraint.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include
        android:id="@+id/review_1"
        layout="@layout/view_movie_note"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/review_2"/>

    <include
        layout="@layout/view_movie_note"
        android:id="@+id/review_2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginLeft="7dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/review_1"
        app:layout_constraintRight_toRightOf="parent"
        />

</android.support.constraint.ConstraintLayout>

and this view_movie_note :

<merge>

    <TextView
        android:id="@+id/note_origin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="15dp"
        android:layout_marginStart="5dp"
        app:layout_constraintStart_toStartOf="@+id/cardView2"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginLeft="5dp" />


    <android.support.v7.widget.CardView
        android:id="@+id/five_star_view_container"
        android:layout_width="0dp"
        android:layout_height="52dp"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="10dp"
        android:elevation="3dp"
        app:cardUseCompatPadding="true"
        app:contentPaddingTop="22dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHeight_min="52dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/note_origin">

        <FiveStarsView
            android:id="@+id/five_star_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal" />

    </android.support.v7.widget.CardView>

    <android.support.v7.widget.CardView
        android:id="@+id/cardView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        app:cardBackgroundColor="@color/colorPrimary"
        app:contentPaddingLeft="15dp"
        app:contentPaddingRight="15dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/note_origin">

        <TextView
            android:id="@+id/grade"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp" />

    </android.support.v7.widget.CardView>


</merge>

I am expecting this

I am expecting this

Instead I got this

Instead I get this

Clearly the constraints that I put in the <include> tag are overriden by the constraints in the included layout.

Is this the expected behaviour ? If yes, how are we supposed to keep a flat layout using <include> and ConstraintLayout ?

4
  • It is expected behaviour. All the included views will be placed in parent view without any separate parent view group. You have card view with width as match_constraints and left and right constraint w.r.t parent and therefore, will occupy entire width
    – nomag
    Commented Sep 27, 2017 at 12:42
  • Please note, we see that issue in setting 0dp to content size. But in case of setting wrap content (for ex.), included constraints don't apply to merged Views with their settings. So how to solve this issue, without reseting all constraints in runtime.
    – GensaGames
    Commented Feb 8, 2018 at 16:07
  • 4
    I've added a bounty to your question and created an issue here issuetracker.google.com/issues/115695764 with a suggestion on how to handle merge for constraint layout, feel free to star it Commented Sep 14, 2018 at 23:14
  • 2
    Thanks @DanieleSegato it really got more attention this way. Unfortunately I was on holiday and your bounty expired... The answers only address the "why this is not working" part of the question but miss the real point which is : "How can we have reusable components and a flat layout ?" I guess we don't have a perfect solution yet and if I have to chose, I take reusable components over a perfectly flat layout any day. I starred your issue.
    – JDenais
    Commented Sep 22, 2018 at 13:53

6 Answers 6

52
+125

Short answer

The best move will be replacing <merge> block with a (nested) ConstraintLayout rather than using redundant layout structure.




ConstraintLayout is great but it doesn't work well with composition and separation of responsibilities of each piece

That is wrong. ConstraintLayout does work well with reusing layouts. Any layout in which all child views are laid out according to relationships between sibling views and the parent layout, behaves exactly like this. This is true even for RelativeLayout.


Then, where is the problem?

Let's take a closer look at what <merge> is.

The doc says

The <merge/> tag helps eliminate redundant view groups in your view hierarchy when including one layout within another.

It will have the same effect as replacing the <include> element with the contents of <merge> block. In other words, the views in the <merge/> block is directly placed to the parent layout without an intermediate view group. Therefore, the constraints of the <include> element is completely ignored.

In this particular example, the views in the including layout is added two times to the parent as the second one on top of another.


Conclusion

Layout resource files are intended to be used independently. To qualify the term reusable, it should not depend on it's parent (The view group in which it will be added in future). It would be looking okay if you had to include the layout only one time. But </merge> won't be a good idea in that case too because you can't place it in any different layout in a different position.

Obviously, flat layout hierarchies have better performance. However, sometimes we may have to sacrifice it.

1
  • 2
    Good answer which is perfectly tied up with the conclusion/discussion where the tradeoff is explained which gives the reader a sort of confidence in their choice of applying the tip in the post.
    – Ludvig W
    Commented Jun 20, 2019 at 9:15
7

Android documentation says

The <merge /> tag helps eliminate redundant view groups in your view hierarchy when including one layout within another

and has an example too

If your main layout is a vertical LinearLayout in which two consecutive views can be re-used in multiple layouts, then the re-usable layout in which you place the two views requires its own root view. However, using another LinearLayout as the root for the re-usable layout would result in a vertical LinearLayout inside a vertical LinearLayout. The nested LinearLayout serves no real purpose other than to slow down your UI performance.

Also see this answer, which will make you understand merge tag more.

Problem in your layout

For the child layout

You put constraints on child elements inside <merge tag. That's not okay. Because that constraints are destroyed at run time when both child layout are merged inside your parent layout. (You tell me if you can do this without include tag, will your constraints work?)

For parent layout

Same for <include tag, you are giving constraints/custom attributes to <include tag, that will be lost, because <merge tag is joined to the root view, so you can not apply custom attributes to the <include with <merge tag. That's why Bahman answer will work.

Attributes on <include tag works when you have root element inside child layout and no <merge tag.

Conclusion

As this is clear, you are not using <merge and <include, as it should be. You have understand what <include and <merge tag do. So use them appropriately.

If you ask solution

ConstraintLayout was introduced to solve complex layout. Not to increase complexity. So when you can do this easily with LinearLayout why to choose Constraints.

Parent Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <include
        android:id="@+id/review_1"
        layout="@layout/view_movie_note"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        />

    <include
        android:id="@+id/review_2"
        layout="@layout/view_movie_note"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="7dp"
        android:layout_weight="1"
        />

</LinearLayout>

view_movie_note.xml

<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
     .../>

    <android.support.v7.widget.CardView
    ...
    </android.support.v7.widget.CardView>

    <android.support.v7.widget.CardView
    ...
    </android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>

output

I hope I could make you understand well.

0
5

Wrap include tags with ConstraintLayout tags then move attributes of include tags to these new ConstraintLayout tags:

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <android.support.constraint.ConstraintLayout
            android:id="@+id/review_1"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toLeftOf="@+id/review_2">

                    <include  layout="@layout/view_movie_note"  />

   </android.support.constraint.ConstraintLayout>

    <android.support.constraint.ConstraintLayout
            android:id="@+id/review_2"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginLeft="7dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toRightOf="@+id/review_1"
            app:layout_constraintRight_toRightOf="parent">

                       <include layout="@layout/view_movie_note" />

    </android.support.constraint.ConstraintLayout>

  </android.support.constraint.ConstraintLayout>
12
  • the inner ConstraintLayouts should possibly be CardViews (unless aligning by constraints, within these child-nodes). Commented Sep 15, 2018 at 9:59
  • @MartinZeitler No the tags which is included with include have some constraint attributes which is invalid in cardView
    – Amani
    Commented Sep 15, 2018 at 10:00
  • @MartinZeitler we do not know maybe included tags is used in some other layout and asker wants to keep them as is
    – Amani
    Commented Sep 15, 2018 at 10:03
  • that's why I wrote "unless" - while the whole question is about "how to create an overly complex solution to a simple problem" (that's why my answer only features alternate approaches). Commented Sep 15, 2018 at 10:04
  • @MartinZeitler I agree it has simpler answer. nested layouts should be avoided
    – Amani
    Commented Sep 15, 2018 at 10:05
2

As a solution

Parent layoutenter image description here

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 android:layout_width="match_parent"
 android:layout_height="match_parent">


<android.support.v7.widget.LinearLayoutCompat
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:weightSum="2">

    <include
        android:id="@+id/review_1"
        layout="@layout/view_movie_note"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/review_2"
        app:layout_constraintTop_toTopOf="parent" />

    <include
        android:id="@+id/review_2"
        layout="@layout/view_movie_note"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="7dp"
        android:layout_weight="1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@+id/review_1"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

 </android.support.v7.widget.LinearLayoutCompat>


</android.support.constraint.ConstraintLayout>

view_movie_note 2]

<?xml version="1.0" encoding="utf-8"?>


<android.support.constraint.ConstraintLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">

<TextView
    android:id="@+id/note_origin"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="15dp"
    android:layout_marginLeft="5dp"
    android:layout_marginStart="5dp"
    app:layout_constraintStart_toStartOf="@+id/cardView2"
    app:layout_constraintTop_toTopOf="parent" />


<android.support.v7.widget.CardView
    android:id="@+id/five_star_view_container"
    android:layout_width="wrap_content"
    android:layout_height="52dp"
    android:layout_marginBottom="8dp"
    android:layout_marginTop="10dp"
    android:elevation="3dp"
    app:cardUseCompatPadding="true"
    app:contentPaddingTop="22dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHeight_min="52dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/note_origin">

    <!--<FiveStarsView-->
    <!--android:id="@+id/five_star_view"-->
    <!--android:layout_width="wrap_content"-->
    <!--android:layout_height="wrap_content"-->
    <!--android:layout_gravity="center_horizontal" />-->

    <RatingBar
        android:id="@+id/ratingBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</android.support.v7.widget.CardView>

<android.support.v7.widget.CardView
    android:id="@+id/cardView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="20dp"
    app:cardBackgroundColor="@color/colorPrimary"
    app:contentPaddingLeft="15dp"
    app:contentPaddingRight="15dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@+id/note_origin">

    <TextView
        android:id="@+id/grade"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp" />

  </android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>
2

merge is a tag and not a ViewGroup, so all the parameter passed to the include will be ignored... You can flat this ViewGroup only with a duplicated layout, if you need to manage it, you can create a Group... XML attributes from merge layout to RelativeLayout via inflate

0

Some issues with your question:

  1. As per android documentation link

You can also override all the layout parameters (any android:layout_* attributes) of the included layout's root view by specifying them in the <include/> tag So any constraint you put into include tag will be removed.

  1. Any android:id in include will NOT be overridden if merge tag is used in your included layout.

  2. Chaining and adding constraint works on views with different ids. So for including same view multiple times with equal weight will not work via include tag.

That being said, you can either copy paste the entire

Therefore, you can not use include in this fashion.

You are left with 3 options:

  1. Use some other ViewGroup (LinearLayout and then constraint layout for example)
  2. Copy paste the content of include layout with different ids of views
  3. Modify ConstraintLayout code to support spread-chains so that entire included layout is copied horizontally.

IMO, 1st option is best if you have a small number of these layouts, 2nd option is best if you have only one layout (asked in question) and 3rd option is best if you have large number of layouts.

Not the answer you're looking for? Browse other questions tagged or ask your own question.