问题
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
Instead I got 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 ?
回答1:
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.
回答2:
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 anotherLinearLayout
as the root for the re-usable layout would result in a verticalLinearLayout
inside a verticalLinearLayout
. The nestedLinearLayout
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>
I hope I could make you understand well.
回答3:
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>
回答4:
As a solution
Parent layout
<?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
<?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>
回答5:
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
回答6:
file view_movie_note.xml
should have a single xml node at it's root - else there won't be two layout nodes which could be aligned with constraints (the <merge>
tag might be useless). having two identical layouts with identical resId
adds to the problem - because constraints are always being defined relative to a resId
, which is not unique here.
alternative approaches would be, a) to use a LinearLayout in order to distribute two items horizontally - or b) in case there should even be some more child nodes (which I'd assume), better use CardView
s in a GridLayout
with two columns, within a GridView / RecyclerView.
a single CardView
as the root node of such a child-layout would be the least to align properly.
according to the expected result, a ConstraintLayout
is not required to align nodes just alike that - with the GridLayoutManager
one can even adjust the column count according to the display size. A StaggeredGridLayoutManager
would also be an option, when the height of the elements varies.
in principle, it is always easier to work with the framework, than to work against the framework.
回答7:
Some issues with your question:
- 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.
Any
android:id
in include will NOT be overridden if merge tag is used in your included layout.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:
- Use some other ViewGroup (LinearLayout and then constraint layout for example)
- Copy paste the content of
include
layout with different ids of views - 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.
回答8:
You can use FrameLayout to include common layouts. Try out this:
<?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">
<FrameLayout
android:id="@+id/review_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/review_2"
app:layout_constraintTop_toTopOf="parent">
<include
layout="@layout/view_movie_note"
android:layout_width="0dp"
android:layout_height="0dp" />
</FrameLayout>
<FrameLayout
android:id="@+id/review_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/review_1"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<include
layout="@layout/view_movie_note"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="7dp" />
</FrameLayout>
</android.support.constraint.ConstraintLayout>
Hope, it will help you.
来源:https://stackoverflow.com/questions/46447621/using-include-with-merge-in-constraintlayout