This post is the first one in our tech stuff series, appearing at irregular intervals, i.e. when something interesting shows up…
This time it’s about Android XML layout re-use tricks and all too possible pitfalls (i.e. the interesting stuff). And of course the solution.
The boring stuff
As you may know, you can create re-usable UI layouts on Android by putting them into an extra XML file and then <include/> them. This way, it’s possible to re-use complete layouts, as opposed to just re-use single widgets. This definitely comes in handy when you’re developing for different screen sizes.
Thus, for a portrait layout the layout XML of our open-source VNC viewer MultiVNC looks like
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:padding="10dip" >
<include
android:id="@+id/discovered_servers"
layout="@layout/discovered_servers_element" />
<include
android:id="@+id/bookmarks"
layout="@layout/bookmarks_element" />
<include
android:id="@+id/new_conn"
layout="@layout/new_conn_element" />
</LinearLayout>
</ScrollView>
which will put all three included layouts one under the other:
For a landscape tablet layout you would put the following XML in res/layout-xlarge-land:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<ScrollView
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:layout_marginRight="50dp"
android:scrollbars="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:orientation="vertical"
android:padding="10dip" >
<include
android:id="@+id/discovered_servers"
layout="@layout/discovered_servers_element" />
<include
android:id="@+id/bookmarks"
layout="@layout/bookmarks_element" />
</LinearLayout>
</ScrollView>
<ScrollView
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1"
android:scrollbars="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:padding="10dip" >
<include
android:id="@+id/new_conn"
layout="@layout/new_conn_element" />
</LinearLayout>
</ScrollView>
</LinearLayout>
which will put the discovered servers and bookmarks layouts in a left pane like this:
The pitfall
Nice and all, but what happens if you decide to <include/> the same UI XML snippet several times in the same layout? Suppose we’d want to have a second bookmark list (e.g. for really really favourite bookmarks or the like…). Then our portrait XML would look like
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical"
android:padding="10dip" >
<include
android:id="@+id/discovered_servers"
layout="@layout/discovered_servers_element" />
<include
android:id="@+id/bookmarks"
layout="@layout/bookmarks_element" />
<include android:id="@+id/bookmarks_favourite"
layout="@layout/bookmarks_element" />
<include
android:id="@+id/new_conn"
layout="@layout/new_conn_element" />
</LinearLayout>
</ScrollView>
Now we have included res/layout/bookmarks_element.xml twice. For reference, it looks like the XML below. Note that there’s an ID defined.
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dip"
android:paddingTop="10dip"
android:text="@string/bookmarks"
android:textAppearance="?android:attr/textAppearanceLarge" />
<LinearLayout
android:id="@+id/bookmarks_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dip" />
</merge>
That has some interesting consequences: What happens if we call
LinearLayout fav_bookmarks = findViewById(R.id.bookmarks_list); // WRONG!
Which one will we get? Remember that bookmarks_element.xml is now included twice!
The answer is: If we do it this way, we will always get the first included bookmarks LinearLayout and never ever the second one (the ‘favourite bookmarks). Why? Because R.id.bookmarks_list is now ambiguous (it’s given to two LinearLayouts in the resulting XML) and will always resolve to the first match…
The solution
The solution? Resolve the ambiguity! This is done by first resolving the ID of the <include /> and then using that handle to get the wanted ID:
View bookmarks_container_2 = findViewById(R.id.bookmarks_favourite);
bookmarks_container_2.findViewById(R.id.bookmarks_list);
This code gets thethat contains the second bookmarks list and using that handle, R.id.bookmarks_list now resolves to the right widget!



July 22, 2012 02:36
Thanks. Useful information.
August 27, 2012 06:10
Great article.
Give a simple solution to annoying problem.
January 16, 2013 17:00
Hi,
It’s a great a brief article to a important problem, it looks like easy to solve, but… i’m getting a problem recovery the “include” element, always, when i try to get the findViewById(R.id.bookmarks_favourite); i get null, i’m using fragments, but i supposes that it’s not the problem.
Any idea?
Code:
fragment.xml
merge.xml
….whatever without layouts, only a textview for example
Code in fragment to get the include, in the method onActivityCreated (after onCreateView in lifecycle fragment)
View include = this.getTabActivity().findViewById(R.id.news);
View include2 = this.getTabActivity().findViewById(R.id.news2);
Both are null… but if i look for the text view i can get the first one as you has commented.
Thanks very much and if someone get the solution email me to manu_gv84@hotmail.com
Pingback: Getting more XML-layouts in Listview : Android Community - For Application Development