Understanding Density-Independent Pixels

Reading time: about 3 minutes


Understanding Density-Independent Pixels

I switched to Android development from web front end, and one thing that was a bit hard to understand was how the density of Android screens affects the use of units. For web, I mainly worked with pixels, ems (the computed value of the element's font size), and rems (the computed value of the root element's font size).

So, I wanted to write a blog post about this theme because I bet I'm not the only one. Let's first look at the density-independent pixels, what they are, and why they're used. After that, let's discuss the conversion between them and pixels. I've purposely omitted scale-independent pixels from this blog post to concentrate on density-independent pixels.

What Are Density-Independent Pixels?

Android devices have different sizes of screens. They also have different pixel sizes for a screen - so a certain amount of pixels in, e.g., width, looks different on a screen with fewer pixels compared to one with a lot more pixels. This is because one screen might fit only 320 pixels in the same physical space while the other fits 640 pixels.

Density-independent pixels are here to fix the issues these differences cause. Android documentation defines density-independent pixels in the following way:

To preserve the visible size of your UI on screens with different densities, design your UI using density-independent pixels (dp) as your unit of measurement. One dp is a virtual pixel unit that's roughly equal to one pixel on a medium-density screen (160 dpi, or the "baseline" density). Android translates this value to the appropriate number of real pixels for each other density.

Source: Support different pixel densities - Android Developers

Let's next look at the conversions between real pixels and density-independent pixels.

Conversions Between Pixels and Density-Independent Pixels

There are multiple cases when we need the conversion from device-independent pixels to pixels - and vice versa. Let's look at how we can convert them - first in the scope of Composable components and then in other cases.

Composable Components

With Jetpack Compose, LocalDensity helps with conversions between pixels and density-independent pixels. LocalDensity is one of the built-in composition locals for Compose, and it provides a Density that can be used for conversions between pixels and density-independent pixels, as well as scale-independent pixels.

Let's look at how we can use it in a Composable component. First, conversion from pixels to device-independent pixels:

fun PxToDp(widthInPx: Int) {
  val density = LocalDensity.current
  val widthInDp = with (density) { widthInPx.toDp() }

And then vice versa:

fun DpToPx(widthInDp: Int) {
  val density = LocalDensity.current
  val widthInPx = with (density) { widthInDp.toPx() }

Both of the examples utilize Kotlin's with-scope function. In the scope of Density provided by LocalDensity, the extension functions for conversion are available, and we can use them.

Other Cases

When not using Jetpack Compose and/or LocalDensity is unavailable, you can, for example, write your own extension functions for conversion. Here's an example with extension functions for Float-datatype:

fun Float.toDp() = 
    (this / Resources.getSystem().displayMetrics.density)

fun Float.toPx() = 
    (this * Resources.getSystem().displayMetrics.density)

In both functions, the device's screen density is fetched from Resources.getSystem().displayMetrics.density. For the pixel-to-dp-conversion, a pixel value is divided by it, and for the dp-to-pixel-conversion, the dp value is multiplied by it.

Wrapping Up

In this blog post, we've discussed device-independent pixels. We also looked at how to convert them to pixels and back. These conversions can be useful in many cases, such as when positioning elements on layouts.

Do you have any examples of when converting between them was needed? Or when it was more complex than you would have wished? Share your thoughts!

Read More