Android has a pretty flexible framework for displaying data from a database,
array, or other list structure using classes called Adapters. There are plenty
of great tutorials in the Android docs and throughout the web on how to use
adapters to create custom behavior for display this data, so I'm going to focus
on its less-discussed couterpart; the AdapterView
.
Adapter Basics
Android uses an Adapter class to bind data from a data structure
(like a database Cursor
or an array) to a view that is specially designed to
dynamically display the data as it moves and changes...this is the AdapterView
.
There are many common subclasses of AdapterView
(ListView
, Spinner
, Gallery
, etc.),
but they share the common function without any special overriding that I will
discuss in detail today.
The Empty View
All AdapterView subclasses have a property that maintains a reference to a View
object known as the empty view. The point of this view is to be displayed when
the Adapter bound to our data tells the AdapterView
that the data structure has
no data. It does this either by just returning zero from getCount()
, or through
a more complex implementation of isEmpty()
. Conceptually, it is then the role
of the AdapterView
to replace the view reserved for displaying content with this
View
, if it is not null.
Setting the Empty View for an AdapterView
You may call at any time AdapterView.setEmptyView()
on an AdapterView
instance
to attach a view you would like to use as the empty view. You may also call
AdapterView.getEmptyView()
at any time to verify if an empty view has been set.
That's it...sort of.
There's a trick...
As of this writing, one of the things the Android documentation doesn't tell you
is that the AdapterView
simply manipulates the visibility
properties of your
content view and empty view based on the information it receives from the Adapter.
After determining which view to display, it simply calls setVisibility(View.VISIBLE)
on that view, and calls setVisibility(View.GONE)
on the other. This is efficient
and works just fine, but it requires that you create both views and insert them
into a visible layout yourself. AdapterView
will not do this legwork for you.
You cannot assume that supplying an empty view to AdapterView
means that it
will insert that view in its place in the layout at runtime...
Example, please!
Let's examine the basic ListView
example to get a grip on things. When you
place a ListView
in your layout per the SDK tutorials, it will look something
like this:
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@+id/list1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
/>
<TextView
android:id="@+id/empty1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Nothing in the List!"
/>
</LinearLayout>
and the setup code in your activity would be like this:
ListView list;
TextView empty;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
empty = (TextView)findViewById(R.id.empty1);
list = (ListView)findViewById(R.id.list1);
list.setEmptyView(empty);
//...code to create and set an adapter of your choice...
}
The key here is this works because both the ListView and its "empty view" are
defined as children of the displayed root layout, so they are both laid out when
the Activity is displayed. Because of this, the AdapterView
method of simply
manipulating visibility works perfectly. Let's modify the example now so that
the layout XML looks like this:
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@+id/list1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
/>
</LinearLayout>
empty.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView
android:id="@+id/empty1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Nothing in the List!"
/>
...with the same setup code in our Activity. Now, the layout process is not
handling the setup and inflation of our "empty view". When our list is empty,
AdapterView
will manipulate the visibility of the two view objects...so the
content view will become invisible. However, with the empty view not attached to
our layout, it will not show up anywhere on the screen (even though it has been
set to View.VISIBLE
)!
In order for this second example to work properly, you would have to modify the Java code as follows:
ListView list;
TextView empty;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
empty = (TextView)findViewById(R.id.empty1);
list = (ListView)findViewById(R.id.list1);
//New code to add the empty view to the visible layout
LinearLayout rootLayout = (LinearLayout)findViewById(R.id.layout1);
rootLayout.addView(empty, 0);
list.setEmptyView(empty);
//...code to create and set an adapter of your choice...
}
Take-home reminder: AdapterView does nothing more for you than manage visibility between a content view and an empty view.
You are responsible to making sure those views are laid out in the appropriate
locations prior to utilizing this functionality. You could also subclass the
AdapterView
you are using to handle it, but many AdapterView
classes are not
as straightforward as you might think. I would highly recommend reading the docs
and the Android source thoroughly before doing so.
Looking Forward
Knowing this about AdapterView is actually a bit exciting because it means we
have yet even more flexibility at our fingertips. This means that we are not
forced to have the AdapterView
content and empty views occupy the same space.
We could define our content at the top of the screen (like an image gallery),
and display the empty view somewhere else, or even on top of the entire screen
(like a popup saying "nothing to see here, move along") restricting access to
other UI elements in the view.