Off by a pixel August 4th, 2015

About a month ago I was tagged by Ron Amadeo who spotted an off-by-a-pixel misalignment in some of the content rows in the Play Store app. This is the story of that extra pixel – as illustrated above with zoomed in portion in the inset showing that the last card in the row is by one pixel taller than the other two cards.

We start with a Nexus 6 device (which showed this problem). The screen is 1440px wide and we have margins of 28px on each side. This leaves us with 1384px horizontal space for the three cards. This is where things get interesting:

Dividing 1384 by 3 gives us 461px for each card with one pixel remaining. Where should that pixel go?

In the previous implementation of the layout the custom onMeasure pass iterated over all child views in the row keeping track of the horizontal space already “given” to the previous children. Starting with the first child, it gave it 1384/3=461px, leaving us with 923 pixels. The second child got 923/2=461px, leaving us with 462 pixels. And those pixels are given to the last child. All is nice and good since we’ve used all the horizontal space available to the card content.

However, we’re operating in a two-dimensional space and each card type is responsible for determining its height based on how much horizontal space is available to it. As we operate in a continuum of screen sizes and screen densities, the grid spec starts from the edges of the screen and proceeds inwards. That means that instead of predefining hard-coded sizes for cards, it instead defines the margins and the maximum width of the content area and that, in turn, defines the width of individual cards within that content area – as illustrated above.

Once the card width is determined, each card proceeds to determine how much height it needs. Cards in this cluster lay out their content as a vertical “stack”. It starts with the cover image and then proceeds downwards to item title, subtitle, price and other textual elements that can be displayed in the card. The height of the cover image is determined based on the available width and the aspect ratio of the image itself:

And this is how we’ve ended up with the last card being by one pixel taller than the other two – that extra horizontal pixel bubbled through the measure pass of the card itself, respecting the aspect ratio of the cover art and maintaining the edge-to-edge layout of the cover image.

After considering possible options, the layout logic in a card row has been revised to use constant card width, in this case 1384/3=461px:

This means that on some devices the right margin is going to be by one (or two on larger screens) pixel wider than the left one. What are other alternatives?

We can keep the current logic that makes the trailing card(s) by one pixel wider than the leading ones. In our case, the trailing card is 462px wide as compared to 461px wide. Then tweak the measure logic within individual cards to account for this off-by-a-pixel difference to enforce consistent height of all cards in the row. What are we going to do? If all the cards are 462px tall, then the first two need to account for an extra vertical pixel. It can go above the image, between the image and the texts or below the last line of texts. In all the cases that extra pixel will create keyline inconsistency within the row. And if the last card is 461px tall, we need to take that extra vertical pixel out of something. We can crop off the image or reduce the vertical space above, between or below the texts. In the first case we’re removing visual information from the cover image – and we don’t want to do that. In the rest of the cases we’re back to breaking the keylines.

Alternatively, we can push that extra pixel in between the cards. That would break the horizontal spacing rhythm of the entire grid instead of the vertical one.

To summarize, there is no magic solution here. In an ecosystem with a continuum of screen sizes and screen densities you cannot create pixel-perfect designs. You can always end up with an extra pixel or two to account for. There are multiple options to shuffle them around, each with its own consequences on what it breaks in the grid. In the absence of a solution that eliminates the visual disruption the next best thing is the solution that minimizes it. In this particular case the visual “barrier” (card content) between the left and the right side margin would be such a minimizer.