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
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
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)
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
layout has a hidden element (using
android:visible="gone") that isn’t in the version of
layout-large-land and the
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 returns null the activity knows the hidden element isn’t in the selected layout and so it must be running on a
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.
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
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.
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
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)
Step #4 - Relax
Which is always the most important step :)