Since its inception, one of the key elements of the Android platform has been the tools provided in its resource framework to select appropriate assets tailored to the user's device type. Over time, this system has been added to here and there to include differentiators for different screen sizes, resolution densities, presence of different hardware items like keyboards, and so on. Undoubtedly, the most common use of this system is to create differentiation for different screen configurations (both size and resolution density). As the Android device landscape has grown, developers like myself have found themselves struggling to keep up with the additions Google is making to the SDK to allow applications to properly adapt.
Today, basically three main device classes exist that Android applications need to support:
- Handsets (phones)
- Small tablets (7" category)
- Large tablets (10" and above)
Within these categories you will find minor differences in resolution, aspect, ratio, and physical size; but for the design of an application's user interface, these three should remain the focus. In most cases, if your application is going to support both tablets and handsets, those user experiences should be quite different, and this often means that the layouts used for each Activity of Fragment will also be very different. On top of this, the difference in screen real estate between a small and large tablet may also require a slightly different layout between them.
Universal Applications and Size Qualifiers
Google still recommends that you should present your application as a single entity to the user that runs optimized for each device type when the user runs it, rather than creating separate applications for the handset and tablet. So to accomplish this, and to keep our code from being full of branches for different devices, we want to make use of resource qualifiers to create separate layout assets that the device chooses at runtime to best suit its configuration.
Google's first attempt at creating qualifiers to separate layouts by device was to
create a handful of size "buckets": small
, normal
, large
, xlarge
.
Initially, this worked out well as handset devices were in the small/normal category,
the first 7" tablets on the market were large
, and the first 10" tablets were xlarge
...
everything had a place.
However, more and more devices began appearing in the market with new screen sizes
and the lines began to blur about where a device might present itself. A new solution
to the problem was presented in Android 3.2, in the form of new qualifiers to pick
resources based on a devices current width/height or "smallest" width; the latter
being a new designation for the devices size by representing it in terms of the width
(in density-independent pixels, or dp) of the shortest side of the screen.
Doing this qualification in terms of the "dp" of the screen is more conducive to
layout design, as we have an inherent minimum constraint that the layout can expect
from the device. Using the new paradigm, it has become common to define small tablets
as having a smallest width of 600dp
, and large tablets at 720dp
, with handsets
being everything that falls below that line. For applications targeting Android 3.2
or higher as a minimum this new system also works well because everything still has a place.
There is one additional difficulty created by today's marketplace. Developers wishing to create an application that runs universal on handsets and tablets are compelled to support a minimum Android version no higher than 2.2, or at best 2.3. In addition, tablet devices in the 7" and 10" form factor really started to come about with the release of Android 3.0 and many remain either at that version or 3.1...still below the version limit for the new qualifiers.
So what do we do? If we want to support handsets at all, and preferably older tablets, we are forced to use the old size qualifiers. But as new handset devices begin to commonly reach 5" screens, we start to see handsets encroaching on the "large" qualification we are trying to reserve for tablets, so we need to leverage the new qualifiers on devices that can understand them. We could just create duplicates of each resource in a directory with the old size qualifier and place it an another directory with a corresponding smallest width qualifier. It is common also that dimensions or other resource items will need to be specified differently for tablet and handset. This may lead us to a directory structure like the following:
-
We place any layouts that are common to all devices and layouts that should be displayed only on handset devices in the default directory.
res/layout/ main.xml res/values/ dimens.xml strings.xml
-
If our application needs some additional layouts and resources to create tablet UI separate from handset, we add the
*-large
and*-sw600dp
directories and place those files there (the same layout files in each place). The reason we need to have them defined in both is so the devices running pre-3.2 and 3.2+ can both find the correct resource.res/layout-large/ main.xml res/values-large/ dimens.xml res/layout-sw600dp/ main.xml res/values-sw600dp/ dimens.xml
-
If there are any further layouts or resource that need to be specified differently for large tablets versus small tablets, we can add the
*-xlarge
and*-sw720dp
directories as well.res/layout-sw720dp/ main.xml res/values-sw720dp/ dimens.xml res/layout-xlarge/ main.xml res/values-xlarge/ dimens.xml
However, you can probably already see that this creates a maintenance issue for the application as almost every resource asset has to be maintained twice. There is also a secondary problem in that all the files have the same name, even though they represent different layout content, and in some IDE tools this can become confusing as to which file you are working with.
Reducing Duplication
In order to reduce the number of resource copies, we can employ a technique called aliasing.
This involves placing the resource items themselves into a common directory
(meaning they have different names, usually describing their function), and then
placing links that represent each resource with its common name into qualified
directories. This reduces the number of directories we need since the layout aliases
can fit into the values-*
directories with other qualified resources.
So, for example, we now have the following:
res/layout
main.xml
main_smalltablet.xml
main_largetablet.xml
res/values/
dimens.xml
strings.xml
res/values-large/
layout.xml
dimens.xml
res/values-sw600dp/
layout.xml
dimens.xml
res/values-sw720dp/
layout.xml
dimens.xml
res/values-xlarge/
layout.xml
dimens.xml
The layout resources for handset (and any future small-screen device we didn't anticipate)
is named properly directly in the default directory. It is always good practice to
keep as much in your default locations as possible to avoid a ResourceNotFoundException
on a device some day in the future. The layout.xml
files in the qualified directories
contain the aliases, and look like the following:
res/layout-large/layout.xml and res/layout-sw600dp/layout.xml
<resources>
<item name="main" type="layout">@layout/main_smalltablet</item>
</resources>
res/layout-xlarge/layout.xml and res/layout-sw720dp/layout.xml
<resources>
<item name="main" type="layout">@layout/main_largetablet</item>
</resources>
These files normalize the other two layouts to have the same R.layout.main
resource identifier on larger screen devices. You can see that we still need to
copy the alias file between the size qualified and smallest-width qualified directories,
but this duplication is much easier to manage; we still only have one layout file
to maintain.
This approach, and the one previous, both still suffer from one increasing problem:
the *-large
directories are considered a valid resource qualifier on 5"+ handset devices.
The smallest width qualifier may have higher precedence than the size qualifier, but
any matching qualifier will win over a default selection...which is what we want
all handsets to use.
A Proposed Solution
Taking all of this into account, the following is the approach I have chosen to take moving forward...
One Final Assumption
I made note of the fact that Android tablet devices smaller than 10" were not prevalent in the market in until after Android 3.2; the original Galaxy Tab 7 is the only major device in this category running Android 2.2 and its sales were less than spectacular. So to combat the issue of devices encroaching on "large", we can remove this qualified directory with a minimal impact. Any 7" device running the application on pre-3.2 software will simply load the handset UI (less than ideal, but also the lesser evil). For larger tablets, however, the device population running Android 3.0 and 3.1 is significant enough that using the older qualifier makes good sense.
The Compromise
Therefore, on most applications, where layout resources for all tablets are the same and no special distinctions need to be made between large and small, I use the following:
res/layout/
**ALL layout resources, named appropriately**
res/values/
**Default resource values**
res/values-sw600dp/
**Tablet specific aliases and resource values**
In this paradigm we need not duplicate any resources or even aliases to resources,
and the casualties in terms of devices not optimized is minimal. In cases where
special adaptions need to be made for larger tablets only, I add back in the xlarge
and sw720dp
buckets:
res/layout/
**ALL layout resources, named appropriately**
res/values/
**Default resource values**
res/values-sw600dp/
**Tablet specific aliases and resource values**
res/values-sw720dp/
**Large tablet specific alises and resource values**
res/values-xlarge/
**Copy of large tablet aliases and resource values**
This reduces the copied resources down to a single set, and the layout copies are only aliases. Until such time when we can remove support for device versions that do no recognize the new qualifier system, this approach is a good compromise that covers most devices in use and is still easy to maintain.