27 July 2011

New Tools For Managing Screen Sizes

[This post is by Dianne Hackborn and a supporting cast of thousands; Dianne’s fingerprints can be found all over the Android Application Framework — Tim Bray]


Android 3.2 includes new tools for supporting devices with a wide range of screen sizes. One important result is better support for a new size of screen; what is typically called a “7-inch” tablet. This release also offers several new APIs to simplify developers’ work in adjusting to different screen sizes.

This a long post. We start by discussing the why and how of Android “dp” arithmetic, and the finer points of the screen-size buckets. If you know all that stuff, you can skip down to “Introducing Numeric Selectors” to read about what’s new. We also provide our recommendations for how you can do layout selection in apps targeted at Android 3.2 and higher in a way that should allow you to support the maximum number of device geometries with the minimum amount of effort.

Of course, the official write-up on Supporting Multiple Screens is also required reading for people working in this space.

Understanding Screen Densities and the “dp”

Resolution is the actual number of pixels available in the display, density is how many pixels appear within a constant area of the display, and size is the amount of physical space available for displaying your interface. These are interrelated: increase the resolution and density together, and size stays about the same. This is why the 320x480 screen on a G1 and 480x800 screen on a Droid are both the same screen size: the 480x800 screen has more pixels, but it is also higher density.

To remove the size/density calculations from the picture, the Android framework works wherever possible in terms of "dp" units, which are corrected for density. In medium-density ("mdpi") screens, which correspond to the original Android phones, physical pixels are identical to dp's; the devices’ dimensions are 320x480 in either scale. A more recent phone might have physical-pixel dimensions of 480x800 but be a high-density device. The conversion factor from hdpi to mdpi in this case is 1.5, so for a developer's purposes, the device is 320x533 in dp's.

Screen-size Buckets

Android has included support for three screen-size “buckets” since 1.6, based on these “dp” units: “normal” is currently the most popular device format (originally 320x480, more recently higher-density 480x800); “small” is for smaller screens, and “large” is for “substantially larger” screens. Devices that fall in the “large” bucket include the Dell Streak and original 7” Samsung Galaxy Tab. Android 2.3 introduced a new bucket size “xlarge”, in preparation for the approximately-10” tablets (such as the Motorola Xoom) that Android 3.0 was designed to support.

The definitions are:

  • xlarge screens are at least 960dp x 720dp.

  • large screens are at least 640dp x 480dp.

  • normal screens are at least 470dp x 320dp.

  • small screens are at least 426dp x 320dp. (Android does not currently support screens smaller than this.)

Here are some more examples of how this works with real screens:

  • A QVGA screen is 320x240 ldpi. Converting to mdpi (a 4/3 scaling factor) gives us 426dp x 320dp; this matches the minimum size above for the small screen bucket.

  • The Xoom is a typical 10” tablet with a 1280x800 mdpi screen. This places it into the xlarge screen bucket.

  • The Dell Streak is a 800x480 mdpi screen. This places it into the bottom of the large size bucket.

  • A typical 7” tablet has a 1024x600 mdpi screen. This also counts as a large screen.

  • The original Samsung Galaxy Tab is an interesting case. Physically it is a 1024x600 7” screen and thus classified as “large”. However the device configures its screen as hdpi, which means after applying the appropriate ⅔ scaling factor the actual space on the screen is 682dp x 400dp. This actually moves it out of the “large” bucket and into a “normal” screen size. The Tab actually reports that it is “large”; this was a mistake in the framework’s computation of the size for that device that we made. Today no devices should ship like this.

Issues With Buckets

Based on developers’ experience so far, we’re not convinced that this limited set of screen-size buckets gives developers everything they need in adapting to the increasing variety of Android-device shapes and sizes. The primary problem is that the borders between the buckets may not always correspond to either devices available to consumers or to the particular needs of apps.

The “normal” and “xlarge” screen sizes should be fairly straightforward as a target: “normal” screens generally require single panes of information that the user moves between, while “xlarge” screens can comfortably hold multi-pane UIs (even in portrait orientation, with some tightening of the space).

The “small” screen size is really an artifact of the original Android devices having 320x480 screens. 240x320 screens have a shorter aspect ratio, and applications that don’t take this into account can break on them. These days it is good practice to test user interfaces on a small screen to ensure there are no serious problems.

The “large” screen size has been challenging for developers — you will notice that it encompases everything from the Dell Streak to the original Galaxy Tab to 7" tablets in general. Different applications may also reasonably want to take different approaches to these two devices; it is also quite reasonable to want to have different behavior for landscape vs. portrait large devices because landscape has plenty of space for a multi-pane UI, while portrait may not.

Introducing Numeric Selectors

Android 3.2 introduces a new approach to screen sizes, with the goal of making developers' lives easier. We have defined a set of numbers describing device screen sizes, which you can use to select resources or otherwise adjust your UI. We believe that using these will not only reduce developers’ workloads, but future-proof their apps significantly.

The numbers describing the screen size are all in “dp” units (remember that your layout dimensions should also be in dp units so that the system can adjust for screen density). They are:

  • width dp: the current width available for application layout in “dp” units; changes when the screen switches orientation between landscape and portrait.

  • height dp: the current height available for application layout in “dp” units; also changes when the screen switches orientation.

  • smallest width dp: the smallest width available for application layout in “dp” units; this is the smallest width dp that you will ever encounter in any rotation of the display.

Of these, smallest width dp is the most important. It replaces the old screen-size buckets with a continuous range of numbers giving the effective size. This number is based on width because that is fairly universally the driving factor in designing a layout. A UI will often scroll vertically, but have fairly hard constraints on the minimum space it needs horizontally; the available width is also the key factor in determining whether to use a phone-style one-pane layout or tablet-style multi-pane layout.

Typical numbers for screen width dp are:

  • 320: a phone screen (240x320 ldpi, 320x480 mdpi, 480x800 hdpi, etc).

  • 480: a tweener tablet like the Streak (480x800 mdpi).

  • 600: a 7” tablet (600x1024).

  • 720: a 10” tablet (720x1280, 800x1280, etc).

Using the New Selectors

When you are designing your UI, the main thing you probably care about is where you switch between a phone-style UI and a tablet-style multi-pane UI. The exact point of this switch will depend on your particular design — maybe you need a full 720dp width for your tablet layout, maybe 600dp is enough, or 480dp, or even some other number between those. Either pick a width and design to it; or after doing your design, find the smallest width it supports.

Now you can select your layout resources for phones vs. tablets using the number you want. For example, if 600dp is the smallest width for your tablet UI, you can do this:

res/layout/main_activity.xml           # For phones
res/layout-sw600dp/main_activity.xml   # For tablets

For the rare case where you want to further customize your UI, for example for 7” vs. 10” tablets, you can define additional smallest widths:

res/layout/main_activity.xml           # For phones
res/layout-sw600dp/main_activity.xml   # For 7” tablets
res/layout-sw720dp/main_activity.xml   # For 10” tablets

Android will pick the resource that is closest to the device’s “smallest width,” without being larger; so for a hypothetical 700dp x 1200dp tablet, it would pick layout-sw600dp.

If you want to get fancier, you can make a layout that can change when the user switches orientation to the one that best fits in the current available width. This can be of particular use for 7” tablets, where a multi-pane layout is a very tight fit in portrait::

res/layout/main_activity.xml          # Single-pane
res/layout-w600dp/main_activity.xml   # Multi-pane when enough width

Or the previous three-layout example could use this to switch to the full UI whenever there is enough width:

res/layout/main_activity.xml                 # For phones
res/layout-sw600dp/main_activity.xml         # Tablets
res/layout-sw600dp-w720dp/main_activity.xml  # Large width

In the setup above, we will always use the phone layout for devices whose smallest width is less than 600dp; for devices whose smallest width is at least 600dp, we will switch between the tablet and large width layouts depending on the current available width.

You can also mix in other resource qualifiers:

res/layout/main_activity.xml                 # For phones
res/layout-sw600dp/main_activity.xml         # Tablets
res/layout-sw600dp-port/main_activity.xml    # Tablets when portrait

Selector Precedence

While it is safest to specify multiple configurations like this to avoid potential ambiguity, you can also take advantage of some subtleties of resource matching. For example, the order that resource qualifiers must be specified in the directory name (documented in Providing Resources) is also the order of their “importance.” Earlier ones are more important than later ones. You can take advantage of this to, for example, easily have a landscape orientation specialization for your default layout:

res/layout/main_activity.xml                 # For phones
res/layout-land/main_activity.xml            # For phones when landscape
res/layout-sw600dp/main_activity.xml         # Tablets

In this case when running on a tablet that is using landscape orientation, the last layout will be used because the “swNNNdp” qualifier is a better match than “port”.

Combinations and Versions

One final thing we need to address is specifying layouts that work on both Android 3.2 and up as well as previous versions of the platform.

Previous versions of the platform will ignore any resources using the new resource qualifiers. This, then, is one approach that will work:

res/layout/main_activity.xml           # For phones
res/layout-xlarge/main_activity.xml    # For pre-3.2 tablets
res/layout-sw600dp/main_activity.xml   # For 3.2 and up tablets

This does require, however, that you have two copies of your tablet layout. One way to avoid this is by defining the tablet layout once as a distinct resource, and then making new versions of the original layout resource that point to it. So the layout resources we would have are:

res/layout/main_activity.xml           # For phones
res/layout/main_activity_tablet.xml    # For tablets

To have the original layout point to the tablet version, you put <item> specifications in the appropriate values directories. That is these two files:

res/values-xlarge/layout.xml
res/values-sw600dp/layout.xml

Both would contain the following XML defining the desired resource:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item type="layout" name="main_activty">
            @layout/main_activity_tablet
    </item>
</resources>

Of course, you can always simply select the resource to use in code. That is, define two or more resources like “layout/main_activity” and “layout/main_activity_tablet,” and select the one to use in your code based on information in the Configuration object or elsewhere. For example:

public class MyActivity extends Activity {
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate();

        Configuration config = getResources().getConfiguration();
        if (config.smallestScreenWidthDp >= 600) {
            setContentView(R.layout.main_activity_tablet);
        } else {
            setContentView(R.layout.main_activity);
        }
    }
}

Conclusion

We strongly recommend that developers start using the new layout selectors for apps targeted at Android release 3.2 or higher, as we will be doing for Google apps. We think they will make your layout choices easier to express and manage.

Furthermore, we can see a remarkably wide variety of Android-device form factors coming down the pipe. This is a good thing, and will expand the market for your work. These new layout selectors are specifically designed to make it straightforward for you to make your apps run well in a future hardware ecosystem which is full of variety (and surprises).