Posts Tagged ‘layout’

Adapting to Empty Views

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 it’s 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(View v) on an AdapterView object 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 it’s 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:

File: 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 it’s “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, AdapterView’s method of simply manipulating visibility works perfectly.  Let’s modify the example now so that the layout XML looks like this:

File: 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>
File: 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.

Android Layout Relationships

Developing UI layouts for Android is an experience unlike any other.  I have developed for both desktop and mobile on a variety of platforms, and the XML structure that Android uses is quite unique.  This can bring with it some issues and challenges as it runs contrary to the way many people think about designing their UI.  Today, I’m going to make some comments on questions that I see constantly regarding the XML keywords most commonly misused in creating layouts.

I’m going to focus on developing using XML, but the same rules apply when creating LayoutParams in code.

Me Or My Parent?

When laying out elements (or widgets) on the screen, it usually doesn’t take long for people to realize that there are two of many of the basic parameters associated with size and location.  Options like width, height, and gravity have parallel values named layout_width, layout_height, and layout_gravity; so what gives?  Parameters with the prefix layout_ are actually instructions that are to be passed to the PARENT of the object that they are placed on, while all other parameters are passed to the object itself.

This can be a source of confusion even when you want to do something as simple as center some text, so let’s look at an example.  Imagine we have a simple layout like the one listed below:

<?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">
  <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="My Text Content"
  />
</LinearLayout>

We placed the constraint layout_gravity=”center_horizontal” so that the text would be centered on the screen, but if this layout gets rendered, the results are not as expected!  Why?  Well, in the example the TextView told the following to its parent LinearLayout:

  • Make me as wide as you are (layout_width=”fill_parent”)
  • Make me just tall enough to fit the text inside (layout_height=”wrap_content”)
  • Lay me out centered horizontally (layout_gravity=”center_horizontal”)

Points #1 and #3 are actually in direct opposition to each other.  The TextView has asked to be centered within the LinearLayout, but also be just as wide (so there is really no room to center it).  What we really wanted to do was to either:

  1. Tell the TextView ITSELF to center its own contents horizontally within the view bounds
  2. Tell the TextView to be JUST as wide as its text, and them center the whole view horizontally in the parent.

Example Fix #1

<?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">
  <TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:text="My Text Content"
  />
</LinearLayout>

Example Fix #2

<?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">
  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:text="My Text Content"
  />
</LinearLayout>

Bottom Line: Remember that any parameter that starts with layout_ is actually an instruction for the parent view, and not the view where it is located!

Android Dialog, or is it?

We’re all familiar with the concept of providing a temporary modal view to the user in an application for the purposes of notification or basic data entry. In the Android framework (as in many computer systems) a class is provided to the developer to create and maintain these views called a Dialog. This class is then further extended for you to make some convenient versions that allow entry of date/time information and make basic list selections (AlertDialog, TimePickerDialog, etc.). These classes work really well, and I would encourage you to leverage them whenever possible.

But what if you want to use the Dialog interface, but you have some custom data entry needs? Android makes it pretty simple to extend the base Dialog class and create custom behavior with a custom layout, and many people would consider this as the next logical option if they can’t solve their problem with one of the canned Dialog solutions. I’m here to tell you today that there is a better way.

The Problem

There are some subtle, but fairly major issues with the custom Dialog classes and state persistence. By design, the Dialog does not have the same functionality available to it as the Activity for life-cycle management. In particular, this includes managing device orientation changes, which (by default) completely breaks down the Activity and rebuilds it for a new configuration in Android. This is exacerbated by the realization that, in many Android devices, rotation is required EXTREMELY often after an entry dialog is shown because the user will want access to the keyboard to enter the data! If you’re dialog doesn’t rotate, your app is broken to the user.

So what’s the issue?  Do some experimentation with this and monitor LogCat some day.  As soon as you do an orientation change with an active dialog, you will get an android.View.WindowLeaked exception from the WindowManager.  This occurs (usually) because of reference conflict between the new activity that gets created, and a reference existing to the previous version of the dialog when you try to show it again.  This is actually pretty difficult to manage, even with managed dialogs using the Activity (although, here’s a hint: dismissing a dialog and destroying it are not the same thing). The canned dialogs available in the framework do not have this problem, which is why I mentioned using them whenever it fits.

The other “problem” with this implementation is that the Dialog class doesn’t get a passed Bundle or other framework inside of which to persist data (like anything the user STARTED to do before they rotated) like the Activity does.  This means a lot of passing data back to the calling Activity so it can manage the persisted data.

…This is all doable, but inefficient.

The Solution

Short Answer: The Dialog Theme for the Activity class.  Personally, the only thing that I REALLY like about the dialog class itself is the presentation method.  I like how it partially covers and dims the current activity which, conceptually, links it to the Activity in the user’s mind.  But everything else on the back end is MUCH better handled with an Activity:

  • startActivityForResult() and finish() are simple replacements for showDialog() and dismiss()
  • Data can be passed to and from the modal session using Intents (a great habit to get into anyway)
  • All the state persistence exists in the framework to handle rotation, like onSaveInstanceState() and the passed Bundle

Plus, creating an Activity class isn’t any more work than a custom Dialog.  Both are going to need the Constructor ( or onCreate() ) to call up super and then set the view to a custom XML layout file to set themselves up.

So the only missing piece is the Theme.  The one extra thing that you must do when you create an Activity instead of a Dialog is you must declare it in your Manifest so the system knows it exists when it comes to things like Intent resolution.  Coincidentally, the Manifest is also where you would set the dialog theme.  Take a look at the example below:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...>
    <application>
        <activity android:name=".HomeActivity">...</activity>
        <activity android:name=".NiceDialog"
                     android:theme="@android:style/Theme.Dialog">...</activity>
        ...
    </application>
    ...
</manifest>

Here, the Activity NiceDialog has been skinned to look just like a dialog would using the theme attribute. It now will wrap the content tightly and only partially cover the view of the calling Activity. Now the user is none the wiser, and you get all the benefits of Activity life-cycle management from your custom Dialog.

Android Layouts Supporting Orientation

It’s been my experience that most developers don’t want to deal with screen rotation in their applications. They will either specifically code the app not to respond to rotation events (which can look weird when you have to slide the screen and use the keyboard), or they will forget that there are instances where the screen orientation rotates for you by default (and their portrait-centric layout goes nuts or gets cut off). This is one of those subtle details that can drag down the user experience in a big way if the developer is not careful.

If your platform of choice is Android, my only question if you’re afraid of screen rotation would be…why?  Android makes dealing with different orientations in your views very simple.  If you’ve opted to define your layouts with XML (which you probably should anyway), then all that is required is a second XML file defining the second orientation (probably landscape), and some resource syntax to describe to Android how and when to choose the proper layout.  Defining layouts in XML is so simple, adding another doesn’t requre significant effort.  The general steps are as follows:

  1. Create two folders in the resource bundle (“res” folder in the project): “layout” and “layout-land”
    • “layout” will hold all the XML layouts for portrait mode
    • “layout-land” will hold all the XML layouts for landscape mode
  2. Create an XML layout in both folders with the SAME name (i.e. main.xml)
    • There will now be a “res/layout/<name>.xml” and a “res/layout-land/<name>.xml”
  3. Build each layout to match the proper screen orientation
  4. POOF! You’re done.  Android will handle the rest at runtime.

So what’s happening?  Well, “land” is an alternate resource that Android recognizes for screen orientation.  Creating the resource directory “layout-land” tells Android specifics about how the contents of the directory are to be used…specifically layouts for landscape.

Let’s look at a quick example:

Let’s say I have a layout with four image buttons.  In portrait mode, I want those buttons to block in a 2×2 arrangement.  However, in landscape mode, that block runs off the screen, so I want them all in a single row.  I will define two layouts:

res/layout/main.xml

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
    	<TableRow>
			<ImageButton
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:src="@drawable/image1"/>
			<ImageButton
				android:layout_width="wrap_content"
				android:layout_height="wrap_content"
				android:src="@drawable/image2"/>
	</TableRow>
	<TableRow>
		<ImageButton
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:src="@drawable/image3"/>
		<ImageButton
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:src="@drawable/image4"/>
	</TableRow>
</TableLayout> 

res/layout-land/main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="horizontal"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<ImageButton
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:src="@drawable/image1"/>
	<ImageButton
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:src="@drawable/image2"/>
	<ImageButton
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:src="@drawable/image3"/>
	<ImageButton
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:src="@drawable/image4"/>
</LinearLayout>   

Whenever the main.xml view is inflated, it will display either the TableLayout version or the LinearLayout version, depending on the screen orientation.