Continuing the series on the more interesting UI pieces of the new Android Market client, today i want to talk about image reflections.

Here, the Market landing page shows two square cells with image reflections in the bottom half of each cell. If you’re coming from desktop client development, your first thought would be to take the original promo image, create a larger version of it with reflection and then set the resulting image on the ImageView displayed in the cell. There are enough code samples floating on the web that show how to write a method that gets an image and returns another image, twice as tall, with the original one on top and reflection on the bottom. There are two problems with this approach, one small and one big. Let’s start with the small one.

If you look closely at the reflection part in the screenshot above, you will see that the “amount” of gray does not change linearly as you go from the top edge of the reflection towards the full opacity near the bottom edge. It starts very slow and then accelerates to full alpha once it’s about two-thirds in. There’s nothing fancy about such an acceleration, and it can be quite faithfully approximated by a simple spline. The problem is that there are no core Shaders that do hardware-accelerated spline-based gradients. There’s a number of ways to work around this:

  • Use a multi-step LinearGradient, carefully choosing the positions to minimize visual artifacts. You may still end up with awkward alpha transitions in the final visuals around the “segment breaks”.
  • Paint the overlay programmatically. First, given the spline equation, compute the alpha for each vertical position. Now, you can draw a bunch of horizontal lines, changing the alpha on every row. Alternatively, you can create a one-pixel wide Bitmap, wrap it in a Canvas, paint the pixels based on the spline-computed alphas, create a BitmapDrawable with the resulting Bitmap, sets its bounds to stretch the bottom half of the reflected image and call the draw() method on it.
  • Finally, you can work with your designers to create a nine-patch asset that emulates the alpha overlay. There are certain advantages to this approach. The designer can tweak the reflection appearance in his tool of choice without any extra work on your side to come up with the correct math approximation of the target visuals. The main drawback is that nine-patches (or n-patches) only support linear gradients.

The second problem with creating a twice-as-tall reflection image is much bigger – memory pressure. Android runs on a wide gamut of hardware profiles, but one thing is constant – you don’t have as much memory as you do on desktop clients. Images are a particularly memory-hungry species, and given how many images the Market client displays on the landing page, we simply cannot afford consuming any more memory than absolutely necessary. Let’s see what we’re doing:

  • The entire cell is a custom class that extends RelativeLayout. Let’s call it GridSquare. Extending the RelativeLayout gives you a nice option to get access to the ViewGroup.MarginLayoutParams as the layout parameters of child views. This way you can respect the layout margins set in the XML layout definition. This also gives you an extra flexibility for more precise control over the layout.
  • GridSquare overrides the onMeasure and onLayout to compute the bounds of promo image. The end goal here is to have the promo image fit the entire cell width while maintaining the original ratio. To this end, the onMeasure gets the intrinsic width and height of the ImageView, computes the scaling ratio based on the intrinsic width of the image and the available width (computed from the width measure spec passed to onMeasure) and computes the matching height. The measure() method is then called on the ImageView child with the full width and the matching height (using MeasureSpec.makeMeasureSpec with MeasureSpec.EXACTLY). The onLayout() method fetches the measured height of the ImageView and passes it directly as the bottom position to the layout() call.
  • Reflection is done in two passes – full opacity reflection and overlay.
  • Full opacity reflection is done at runtime with Canvas APIs without creating any extra images or drawables. First, the GridSquare constructor calls setWillNotDraw(false) to let the rendering pipeline know that it has a custom onDraw logic. The overriden onDraw() method calls ImageView.getDrawable() on the view that holds the promo image, save()s the Canvas, translate()s it vertically by twice the height of the image view, computes the scale factor of the image (same logic as in onMeasure above), scale()s the Canvas based on the computed scale factor (positive X, negative Y), calls Drawable.draw() on the promo image drawable passing our Canvas and, finally, restore()s the Canvas so that we don’t end up affecting other draw operations on the Canvas object once our method is done. In addition, we paint a one-pixel black horizontal line along the bottom edge of the original image (our designers found that it adds a nice touch to the visuals).
  • The fading overlay is done with a nine-patch asset that is set as a background attribute of a special child view of the entire cell. We’ve decided to go with the third approach outlined above, where the nine-patch has a large non-stretchable vertical section that emulates the non-linear fading, and a full-opacity stretch that is “used” when the fading is complete. As child views are drawn after the parent, this overlay is painted on top of the full-opacity reflection created on the fly in the cell’s onDraw() method.

Stay tuned for more.

 

Rich wooden furniture and doors in mahogany brown colors contrast with faded emerald greens of the decrepit walls in this Bogota hotel room.

A clean, almost industrial design of the bathroom highlights the coldness and sterility of the marriage.

Devoid of any warm color, the beige-gray palette engulfs the walls, drapings and character costumes to further highlight the lack of emotional attachment between the two.

Contrasting with deep reds strewn everywhere in this kitchen.

The only glimpses of color are coming from her hair and lips in an otherwise utilitarian setting of a surveillance truck.

A melancholic setting hardly disturbed by turquoise lights and rich reds and yellows of the flowers.

More vibrant greens and capri blues highlight the emotional tension as the characters struggle to analyze their feelings towards each other.

Production design: Jeff Mann
Art direction: Keith Neely and David Sandefur
Set decoration: Victor J. Zolfo
Costume design: Michael Kaplan
Cinematography: Bojan Bazelli

 

Continuing the series on the more interesting UI pieces of the new Android Market client, today i want to talk about one of the central elements of the landing page – media selector.

This element has a couple of characteristics that, left unattended, would have broken the intended visual appearance. First, note how the vertical gradient runs across all the cells, unifying them into a single UI element. Second, there are multiple overlapping translucent elements combined at different alpha levels and Z order.

First, let’s take a look at the zoomed version of these overlapping elements:

The four elements are:

  • Lighter gray thin strip going along the top edge of the cell
  • Darker gray thin strip going along the bottom edge of the cell
  • Thick color strip going along the left edge of the cell
  • Media vertical watermark icon “peeking” from the bottom edge of the cell

If you trace the pixels along the bottom edge of each cell, you will note the overlap, where the pixels from the different elements are blended together creating a nice layered look. The cell layout is a RelativeLayout where each child view (including the text views for the media vertical text and the “see more”) is aligned to the relevant cell edges (with layout_alignParent* attributes). As child views are painted in the order in which they are defined in the layout – which means that the child defined last is painted last – this is the layout order:

  • Thick color strip going along the left edge. The background of this child view is masked with 0xC0FFFFFF to hint at the underlying vertical gradient.
  • Gray thin strips going along the top and bottom edges. These are translucent as well, achieving a nice highlighted look in the area where they overlap with the thick color strip. This allows changing the color coding of different media verticals without creating any extra assets.
  • Media vertical watermark icon in the bottom left corner. It is positioned with negative layout_marginBottom to make it “peek” from the bottom edge. It is translucent as well, further contributing to the nice highlighted overlays along the bottom edge (especially in the colored area).

The order of these child views is important:

The left screenshot shows a different order of the child views. Here, the thick color strip is placed after the thin horizontal gray strips. If you study the cell dividers closely, you will see that this breaks the visual continuity of this bevel when it transitions into the colored area. Even though the translucency is the same, the combined effect is very different.

The right screenshot shows what happens when the light and dark grays of the thin horizontal strips are switched. Following the global lighting model that has the light source at the top of the screen, we now have a raised bevel instead of a lowered bevel. This draws too much attention to the separators that appear are very visible “bumps” on the surface of the unified selector element.

This screenshot highlights the importance of a single vertical gradient applied to the entire selector. Having a separate highlight in each cell adds too much noise and bumpiness to the visual surface. Instead, the entire selector has a background nine-patch with the slight vertical gradient. A final polishing touch is done in the code that populates the content of each cell – the top horizontal line of the top cell and the bottom horizontal line of the bottom cell are two pixels thick instead of one. This further establishes the selector as one visual element while still remaining consistent with the global lighting model. The implementation itself locates the specific child view by its ID using View.findViewById and then sets the height attribute of the getLayoutParams() result.

Stay tuned for more.