Android Tablet & Phone UIs in one APK [HOW TO]

(the code and application links are at the bottom of the article for those who are a bit impatient)

At DroidCon UK this year I presented a talk on why Android developers should be thinking about tablets and I touched upon creating UIs which can adapt to the device they’re on. Anyone who attended DroidCon UK has now had plenty of time to get a head start so I’m now going to show one method of getting an Android application to adapt itself to present an appropriate UI.

Please note that this demonstration is designed to work on devices running 1.6 and above. If you’re targeting more recent versions of Android you can make some improvements (e.g. using include in the layouts for common parts), but as some tablets are currently shipping with Android 1.6 I’ve tried to be as inclusive as possible.

So let’s get started;

Step #1 – The Manifest

You’ll need ensure the applications manifest states your application is capable of supporting large (and, in Gingerbread, extra large) screens. There are two ways of doing this, either via explicitly listing the screens you want to support in the supports screens element, or choosing an appropriate targetSDK value in uses-sdk element which implies your app supports the screen types you want.

I’ve used the second method and declared Gingerbread (API Level 9) as the target SDK (purely because it involves less typing), and as I’m trying to keep compatibility with Android 1.6 my uses-sdk line looks like this;

....
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="9"/>
....

Step #2 – Layouts

You’re going to need to decide which orientations and sizes you want to treat differently. I’ve chosen screens which are being used in landscape mode that Android reports as being large or extra large (which should cover most tablet screen sizes).

The reason behind my choice is that in portrait mode users appreciate seeing more items on a list, but in landscape mode many layouts end up just leaving more blank space on each line to fill the screen, which isn’t really useful to anyone, so I’m going to create some layouts to support an application which presents users with a list on one screen and the data on another on most devices, but on a landscape orientated large (or extra large) screen device will show a list & data view combination on a single screen.

When you’ve selected the orientations and sizes you want to create special layouts for you’ll need to create the appropriate layout folders (You can find details on the naming scheme in the Android documentation). I’m going for large and extra large screens in landscape mode so I end up with the following three resource directories;

layout (which contains the default layouts)
layout-large-land (which contains my large and extra large landscape layouts)
layout-xlarge-land (which contains a 2nd copy of my large and extra large landscape layouts)

The copy in layout-xlarge-land is needed because Android 2.3 (Gingerbread) will use the layout resources on an extra large screen unless it finds layouts specifically marked for extra large screens.

(Thanks to Dan Morrill for letting me know the xlarge copy isn’t necessary)

The layout and layout-large-land folders both contain two layouts with the same names; my_list_view.xml for an activity designed to show a list of options, and my_main_view.xml for an activity designed to show the selected option.

The differences between the layouts in the folders is that the my_list_view.xml in layout has a hidden element (using android:visible="gone") that isn’t in the version of my_list_view.xml in layout-large-land and the my_main_view.xml in layout-large-land has a ListView which is to be shown on large and extra large landscape devices which isn’t in the my_main_view.xml for the other screen sizes and orientations.

The single landscape tablet layout I’m aiming for looks like this;

and the two non-landscape/non-tablet layouts should look like this;

Step #3 – The code

There are only two, relatively simple, Activity classes needed to achieve the UI changes I want. The demo also includes a ListAdapter, but this has no special features for dealing with any screen size or orientation.

When the user starts the application the activity called MyListActivity is started. It’s primarily designed to display a list of options the user can select from and it sets its’ content view to R.layout.my_list_view. Due to the resolution independent features introduced in Android 1.6 the OS will pick either the default layout (with the hidden element), or the alternative one (without it) automatically and the activity then has an easy way of telling whether the activity is running on a large/extra large landscape screen by checking for the existence of the hidden element using findViewById.

If findViewById returns null the activity knows the hidden element isn’t in the selected layout and so it must be running on a large or extra large screen device in landscape mode. It can then start the main display activity, call finish() to indicate that all the work in the option displaying activity has been done, and exit.

If findViewById returns something other than null then the hidden element is present which means the activitiy is running on another size screen or in portrait mode. It can then populate the option list, wait for the user to make a selection, and then start the main display activity passing the users selection as an extra in the Intent.

The main display activity works in a similar way; It uses findViewById to see if the version of my_main_view.xml selected by Android has a ListView. If a ListView is present it knows it’s on a large/extra large landscape screen and so it populates the list and sets up an appropriate Listener to process selections from the ListView by the user.

If the ListView isn’t present then the application knows it’s on another screen size or orientation and so it looks for the details of what to display and displays it.

There is only one other thing that needs to be considered, and that’s an orientation change when the user is looking at the main display view. This can be handled (in a not-too pretty way), in two steps;

#1- Whenever the ListView activity starts the main display activity it must call finish() to remove itself from the activity stack.
#2- In the main display activity we need to override onKeyDown, check for the back key being pressed, and if it is and the ListView is not present on the currently displayed layout (i.e. we’re not in landscape mode on a large/extra large screen) we need to restart the activity which shows the user the options to choose from.

(If anyone has any suggestions for a cleaner solution I’d be happy to hear it and the comments section is open below)

Step #4 – Relax

Which is always the most important step 🙂

I’ve made an eclipse project of the code available here so you can look over the code, and a compiled version of the application is available here.

11 thoughts on “Android Tablet & Phone UIs in one APK [HOW TO]

Add yours

  1. Thanks for sharing this.
    I don’t quite understand why you overwrite onKeyDown. What if we don’t finish() in ListView on phones?

    1. The problem comes with an orientation change. If the user is on a L / XL screen in portrait mode, goes to the display activity, then rotates the device to landscape and presses back they’ll get the option list activity displayed which isn’t what we want.

      If the user starts the app on an L / XL screen device in landscape, the goes to portrait they’re left with no way of selecting an option from the list (pressing back leaves the application entirely).

  2. Hi! This statement is false: “The copy in layout-xlarge-land is needed because Android 2.3 (Gingerbread) will use the layout resources on an extra large screen unless it finds layouts specifically marked for extra large screens.”

    The algorithm picks the closest fit. An xlarge device will prefer xlarge over large over normal over default, and picks the first that is present. Similarly a large device will pick large over normal over default.

    Now, if you provide layout-large and layout-land, it will prefer layout-land over layout-large if the device is in landscape orientation, which may be what you were seeing. But if you provide layout-large-land and layout-land to an xlarge device, it will use layout-large-land.

    1. Thanks for the clarification.

      I think Mark Murphy and I interpreted http://developer.android.com/guide/topics/resources/providing-resources.html#BestMatch in the same way in that it says “Screen pixel density is the one qualifier that is not eliminated due to a contradiction” and so I expected screen size to be treated in the same way as, say, a language qualifier.

      I guess this is something I can submit a patch for when the docs hit the AOSP repo :).

      1. Yes, pixel density is the devilish exception.

        Also, if you want to test this, you can create an emulator AVD in the form of an xlarge screen. 1280×720 at 160 DPI will do the trick, and the framework should decide that that is an xlarge screen. Then you can test all the resourcey goodness.

Leave a Reply

Your email address will not be published. Required fields are marked *

Proudly powered by WordPress | Theme: Baskerville 2 by Anders Noren.

Up ↑