6 minutes read

Many screens across the application can have common parts: error, empty, loading states, a toolbar, a navigation bar — you name it. If a common part consists of a single view, it can be turned into a one-liner with styles. But in this topic, we'll consider other useful techniques for reusing layouts.

<include>

This special tag is used to include one layout into another. It's as simple as it goes:

<!-- layout/toolbar.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar
    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"
    android:background="#E2E2E2"
    app:title="The coolest app ever" />
<!-- layout/main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="…is not developed yet."
        android:textSize="24sp" />

</LinearLayout>

According to the official documentation, you can specify the id and layout_ parameters directly inside the <include> tag in order to override them. Also, according to the source code, you can override visibility in the same way.

<include> is an intrinsic, not a View. Unlike "normal" views, it uses a plain layout attribute without the android: namespace.

<merge>

What if you need to include several views into a container? An XML document must have a single root tag. Of course, you can wrap the included views in an extra ViewGroup, but this could give an unintended result. Also, it's better to avoid extra nesting for performance reasons.

Instead of a container, you can use the <merge> tag:

<!-- layout/content.xml -->
<?xml version="1.0" encoding="utf-8"?>
<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.appcompat.widget.Toolbar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#E2E2E2"
        app:title="The coolest app ever" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="…is not developed yet."
        android:textSize="24sp" />

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

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

</LinearLayout>

So, Toolbar and TextView will become direct children of LinearLayout.

Since the <merge> tag doesn't know to which parent it is going to be added, the preview will become of no use at this point. To fix this, you can add some hints for Android Studio:

<merge
    xmlns:...
    tools:parentTag="android.widget.LinearLayout"
    tools:orientation="vertical">

Unlike "normal" views, merged views will not receive attributes of the <include> tag.

ViewStub

One last thing: there's a special invisible zero-size view that acts like a lazy version of <include>. Similarly, it allows you to specify a layout to inflate, and will transfer layout_ attributes and id to the newly created layout. On the other hand, it will not actually create the whole layout until requested (and it doesn't support <merge>).

<!-- layout/toolbar.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar
    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"
    android:background="#E2E2E2"
    app:title="The coolest app ever" />
<!-- layout/main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ViewStub
        android:id="@+id/toolbar"
        android:layout="@layout/toolbar" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:text="…is not developed yet."
        android:textSize="24sp" />

</LinearLayout>

To show the nested layout, you can either call the special ViewStub.inflate() method, or the common View.setVisibility() method. When called, ViewStub inflates the specified layout and replaces itself with it. Why are there two options to do the same thing? The first one is explicit, and the second one will work even without knowing that it's a ViewStub.

findViewById<ViewStub>(R.id.toolbar).inflate()
// or
findViewById<View>(R.id.toolbar).setVisibility(View.VISIBLE)

This pattern is useful to show layout parts that appear optionally, but not in 100% of layout usage scenarios.

Conclusion

Now you know how to extract parts of a layout in order to reuse them or make the layout more concise using the <include> and <merge> tags. Also, now you can instantiate some view hierarchies on demand using ViewStub.

16 learners liked this piece of theory. 2 didn't like it. What about you?
Report a typo