July 31, 2014

Handling Multiple Screen Sizes, Part Two

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.

The simplest approach to handling multiple screen sizes in Android is to design your user interfaces such that they automatically scale for the screen size, without any size-specific code or resources. In other words, “it just works”.

This implies, though, that everything you use in your user interface can be gracefully scaled by Android and that everything will fit, even on a QVGA screen.

Here are some tips for achieving this “all in one” solution:

Don’t Think About Positions, Think About Rules

Some developers, perhaps those coming from the “drag-and-drop” school of UI development, think first and foremost about the positions of widgets. They think that they want certain widgets to be certain fixed sizes at certain fixed locations. They get frustrated with Android layout manager (containers) and may gravitate to the deprecated AbsoluteLayout as a way to design UIs they way they used to.

That rarely works well even on desktops, as can be seen by applications that do not handle window resizing very well. Similarly, it will not work on mobile devices, particularly Android, with its range of screen sizes and resolutions.

Instead of thinking about positions, think about rules. You need to teach Android the “business rules” about where widgets should be sized and placed, with Android then interpreting those rules based upon what the device’s screen actually supports in terms of resolution.

The simplest rules are the fill_parent and wrap_content values for android:layout_width and android:layout_height. Those do not specify specific sizes, but rather adapt to the space available.

The richest environment for easily specifying rules is to use RelativeLayout. While complicated on the surface, RelativeLayout does an excellent job of letting you control your layout while still adapting it to other screen sizes. For example, you can:

  • Explicitly anchor widgets to the bottom or right side of the screen, rather than hoping they will wind up there courtesy of some other layout
  • Control the distances between widgets that are “connected” (e.g., a label for a field should be to the left of the field) without having to rely on padding or margins

The greatest control for specifying rules is to create your own layout class. For example, suppose you are creating a series of applications that implement card games. You may want to have a layout class that knows about playing cards: how they overlap, which are face up versus face down, how big to be to handle varying number of cards, etc. While you could achieve the desired look with, say, a RelativeLayout, you may be better served implementing a PlayingCardLayout or a HandOfCardsLayout or something that is more explicitly tailored for your application. Unfortunately, creating custom layout classes is under-documented at this point in time.

Consider Physical Dimensions

Android offers a wide range of available units of measure for dimensions. The most popular has been the pixel (px), because it is easy to “wrap your head around” the concept. After all, all Android devices will have screens with such-and-so number of pixels in each direction.

However, pixels start to become troublesome as screen density changes. As the number of pixels in a given screen size increases, the pixels effectively shrink. A 32px icon on a traditional Android device might be finger-friendly, but on a high-density device (say, WVGA in a mobile phone form factor), 32px may be a bit small for use with a finger.

If you have something intrinsically scalable (e.g., a Button) where you had been specifying a size in pixels, you might consider switching to using millimeters (mm) or inches (in) as the unit of measure. 10mm is 10mm regardless of the screen resolution or the screen size. This way, you can ensure that your widget is sized to be finger-friendly, regardless of the number of pixels that might take.

Avoid “Real” Pixels

In some circumstance using millimeters for dimensions will not make sense. Then, you may wish to consider using other units of measure while still avoiding “real” pixels.

Android offers dimensions measured in density-independent pixels (dip). These map 1:1 to pixels for a 160dpi screen (e.g., a classic HVGA Android device) and scale from there. For example, on a 240dpi device (e.g., a phone-sized WVGA device), the ratio is 2:3, so 50dip = at 160dpi = 75px at 240dpi. The advantage to the user of going with dip is that the actual size of the dimension stays the same, so visibly there is no difference between 50dip at 160dpi and 50dip at 240dpi.

Android also offers dimensions measured in scaled pixels (sp). Scaled pixels, in theory, are scaled based on the user’s choice of font size (FONT_SCALE value in System.Settings).

Choose Scalable Drawables

Classic bitmaps — PNG, JPG, GIF — are not intrinsically scalable. If you are not running in “compatibility mode”, Android will not even try to scale them for you based on screen resolution and size. Whatever size of bitmap you supply is the size it will be, even if that makes the image too large or too small on some screens.

One way to address this is to try to avoid static bitmaps, using nine-patch bitmaps and XML-defined drawables (e.g., GradientDrawable) as alternatives. A nine-patch bitmap is a PNG file specially encoded to have rules indicating how that image can be stretched to take up more space. XML-defined drawables use a quasi-SVG XML language to define shapes, their strokes and fills, and so on.

The next post in the series will look at techniques for when the above techniques fail and you need to provide specific support for specific screen sizes and densities.