Pixel-perfect Android web UIs
STOP! As of February 2013, this article is out of date.
target-densitydpi is being deprecated and you shouldn’t use it. For more, see Pete LePage’s blog post on the topic.
Check out Targeting the iPhone 4 Retina Display with CSS3 Media Queries, an article discussing this same topic as it relates to the iPhone 4’s Retina Display.
If you’ve written Android SDK apps recently, you’re probably aware of the framework’s support for multiple screen densities using resource directory qualifiers. That is, you can create icons or button 9-patches at different pixel resolutions, stick them in the corresponding
res/drawable-ldpi directories, and the framework will, at runtime, choose which of the three graphic assets to display on a given device. The end result is a UI element that’s roughly the same physical size on every device, rendered in all its pixel-perfect glory. No blurry edges, no resampling artifacts… just the pixels, exactly as they were crafted by the designer.
But what about mobile web apps? Or
WebViews in Android SDK apps? How do we get them to be pixel-perfect on all devices?
Fortunately, the aforementioned resource selection behavior can be achieved in the Android browser using CSS3 media queries and the
viewport meta tag. In this post, we’ll take a closer look at what this entails.
A simple example
The rest of the post will follow a simple example user interface: a button in the middle of a blank page. In the spirit of instant gratification, here’s a link to the demo and its source code…
…and some screenshots of the demo page running in the Android browser, on medium and high density screens:
The middle screenshot shows what the page would look like without the optimizations described in this article.
NOTE: the high dithering in the image is just to keep file sizes down; the original screenshots were far less dithered.
CSS3 media queries and
target-densitydpi to the rescue
So let’s dive into the gory details. The most interesting part is the “ of
<link rel="stylesheet" href="css/all.css"> <link rel="stylesheet" href="css/mdpi.css" media="only screen and (-webkit-max-device-pixel-ratio:1.0) and (max-device-width:480px)"> <link rel="stylesheet" href="css/hdpi.css" media="only screen and (-webkit-min-device-pixel-ratio:1.5)"> <meta name="viewport" content="user-scalable=no, width=device-width, target-densitydpi=device-dpi">
Let’s go through this line-by-line:
We include all.css as our baseline stylesheet. Nothing special here, moving on…
We then include mdpi.css, which defines button dimensions and associates them with medium density border-images for various button states. For example, on medium density screens, we want buttons to be 48 pixels tall.
Using media queries, we limit this stylesheet to browsers that match the following constraints:
- Only desktop web browsers and full-HTML mobile browsers (using the ‘screen’ media type)
- Screens that have a maximum of a 1.0 CSS px/device px ratio (see this post on the Surfin’ Safari blog for discussion of CSS pixels vs. device pixels)
- Devices that are at most 480 pixels ‘wide’—pretty much meaning mobile devices only. This is kind of a hack.
We also include hdpi.css, which has the same structure as
mdpi.cssbut references high density button images and dimensions. For example, on high density screens, we want buttons to be 72 pixels tall (48 × 1.5).
We again use media queries to limit this stylesheet to full-HTML browsers and screens that have a minimum CSS/device pixel ratio of 1.5. The 1.5 scaling factor for dimensions and images is because on Android, the framework treats medium density screens as 160 ppi and high density screens as 240 ppi (160 × 1.5).
Lastly, we use “ to configure the viewport for our app within the browser. More specifically we tell the browser that:
- Users shouldn’t be allowed zoom the app—just as we probably don’t want users to zoom a native Android app UI
- The number of horizontal pixels in our UI is exactly the width of the device screen (otherwise there would be scaling and thus loss of pixel-precision)
- Most importantly, using
target-densitydpi=device-dpi, we tell the browser that the site is prepared to manually adapt its UI to any pixel ratio, and that no density-related scaling should occur. This is newly supported in Android 2.0, and is discussed in further detail in the WebView documentation and in this blog post by Dan Fabulich.
And that’s it! The rest of the process is making sure your CSS dimensions are correct in
hdpi.css, and potentially
ldpi.css if you choose to support it. It’s obviously also important to provide properly sized image assets. Here’s an example of the ‘normal’ button state image from the demo:
On Android alone, high density screens now account for over 50% of all devices, as of August 2nd, 2010. The iOS world is now in a similar boat with the iPhone 4, which sports a 326 ppi display. This means that if you’re making mobile web apps, a very large percentage of your users are now running high density screens. If your site or app isn’t properly optimized, you may not be delivering the most beautiful experience to your users.
However, if you start implementing some of the techniques described in the post and linked articles above, you can help make a prettier, more pixel-perfect mobile web.
Thanks for reading!