October 25, 2014

Handling Multiple Screen Sizes, Part Three

This is the first part of a several part series on handling multiple screen sizes in your Android projects. This material is adapted from a chapter in The Busy Coder’s Guide to Android Development, Version 3.0.

When designing your app, there will be times when you want to have different looks or behaviors based upon screen size or density. Android has ways for you to switch out resources or code blocks based on the environment in which your application runs. When properly used in combination with the techniques described in the preceding post, achieving screen size- and density-independence is eminently possible, at least for devices running Android 1.6 and newer.

<supports-screens>

The first step to proactively supporting screen sizes is to add the <supports-screens> element to your AndroidManifest.xml file. This specifies which screen sizes you explicitly support and which you do not. Those that you do not will be handled by the automatic “compatibility mode”.

Here is a manifest containing a <supports-screens> element:

Three of these attributes are almost self-explanatory: android:smallScreens, android:normalScreens, and android:largeScreens each take a boolean value indicating if your application explicitly supports those screens (true) or requires “compatibility mode” assistance (false).

The android:anyDensity attribute indicates whether you are taking density into account in your calculations (true) or not (false). If false, Android will pretend as though all of your dimensions (e.g., 4px) are for a normal-density (160dpi) screen. If your application is running on a screen with lower or higher density, Android will scale your dimensions accordingly. If you indicate that android:anyDensity = “true”, you are telling Android not to do that, putting the onus on you to use density-independent units, such as dip, mm, or in.

Resources and Resource Sets

The primary way to “toggle” different things based on screen size or density is to create resource sets. By creating resource sets that are specific to different device characteristics, you teach Android how to render each, with Android switching among those sets automatically.

Default Scaling

By default, Android will scale all drawable resources. Those that are intrinsically scalable, as described in the previous section, will scale nicely. Ordinary bitmaps will be scaled just using a normal scaling algorithm, which may or may not give you great results. It also may slow things down a bit. If you wish to avoid this, you will need to set up separate resource sets containing your non-scalable bitmaps.

Density-Based Sets

If you wish to have different layouts, dimensions, or the like based upon different screen densities, you can use the -ldpi, -mdpi, and -hdpi resource set labels. For example, res/values-hdpi/dimens.xml would contain dimensions used in high-density devices.

Size-Based Sets

Similarly, if you wish to have different resource sets based upon screen size, Android offers -small, -normal, and -large resource set labels. Creating res/layout-large-land/ would indicate layouts to use on large screens (e.g., WVGA) in landscape orientation.

Version-Based Sets

There may be times when earlier versions of Android get confused by newer resource set labels. To help with that, you can include a version label to your resource set, of the form -vN, where N is an API level. Hence, res/drawable-large-v4/ indicates these drawables should be used on large screens at API level 4 (Android 1.6) and newer.

Apparently, Android has had the ability to filter on version from early on, and so this technique will work going back to Android 1.5 (and, perhaps, earlier).

So, if you find that Android 1.5 emulators or devices are grabbing the wrong resource sets, consider adding -v4 to their resource set names to filter them out.

Finding Your Size

If you need to take different actions in your Java code based on screen size or density, you have a few options.

If there is something distinctive in your resource sets, you can “sniff” on that and branch accordingly in your code. For example, as will be seen in the code sample at the end of this chapter, you can have extra widgets in some layouts (e.g., res/layout-large/main.xml) — simply seeing if an extra widget exists will tell you if you are running a “large” screen or not.

You can also find out your screen size class via a Configuration object, typically obtained by an Activity via getResources().getConfiguration(). A Configuration object has a public field named screenLayout that is a bitmask indicating the type of screen the application is running on. You can test to see if your screen is small, normal, or large, or if it is “long” or not (where “long” indicates a 16:9 or similar aspect ratio, compared to 4:3). For example, here we test to see if we are running on a large screen:

There does not appear to be an easy way to find out your screen density in a similar fashion. If you absolutely need to know that, a “hack” would be to create res/values-ldpi/, res/values-mdpi/, and res/values-hdpi/ directories in your project, and add a strings.xml file to each. Put a string resource in strings.xml that is has a common name across all three resource sets and has a distinctive value (e.g., name it density, with values of ldpi, mdpi, and hdpi, respectively). Then, test the value of the string resource at runtime. This is inelegant but should work.

The next post in the series will look at ways to help you better use the emulator, and real devices, for testing these different screen sizes and densities.