Design, uninterrupted #113

December 17th, 2010

Today’s post highlights the design of BBQWar.com by Ismael Burciaga. The single-column layout frames vibrant photographs with faux-vintage elements and an attractive palette of tan brown and desaturated brick orange. The beautiful Clarendon LT is used in conjunction with Cufon to render all header texts, along with Georgia and Helvetica Neue for body text and date blurbs. The brick orange color framing the main content column is also used for all links and rollover navigation elements, but i would rather see the main tag line (What kind of meat…) use the tan brown since it is not a clickable element. Note the consistent pattern of styling the separator elements – light brown dash line with highlight halo next to slightly irregular fading solid line.

Unsurprisingly, individual entries are a visual feast for meat lovers, featuring expertly framed full-width photographs of the meals, an attractive row of themed social icons and well crafted info section of the entry author. Make sure to visit the “Warriors” page as well.

Meet the Green Goblin, part 4

December 16th, 2010

Last Friday we announced a significant update to the Android Market client. A whole slew of features went into this update (and many more are to come), and this week the pixel geek in me will be talking about the new visual design of the application. Here’s a quick recap of the first three posts:

Today i’m going to cover a few miscellaneous bits and pieces. As they say, the devil is in details, and every single bit mentioned in this post is a small step towards improving the user experience and overall aesthetics of the application. I should note that these bits aren’t strictly necessary from the purely functional perspective – and are quite time-consuming to get right. But if you want your app to be less ass and more class, read on.


First, let’s take another look at the home screen of the new Market client in the portrait mode:

There’s a whole bunch of visual information that we put in the header section – market-bag icon, market title, search button, carousel, title / rating / price for the fronted app and the navigation buttons. This content can be roughly divided into three sections – the title bar, the carousel and the navigation footer. Following an established UX practice, we want to add some visual separation between the three sections. The separation between the title bar and the carousel is achieved by scaling down and fading thumbnails that are close to the edges. This creates extra vertical space directly below the market-bag icon and the search button. What about the separation between the carousel and the navigation footer?

Here we cannot “rely” on the vertical white space below the outermost thumbnails – that space is negated by a rather long row of views that display the title, rating and price for the fronted app. Instead, the design pushes the middle navigation button down towards the curved header edge. This effectively arranges the navigation buttons along the bottom edge of the container and adds extra vertical space above the middle button. Let’s take a closer look at the actual bounds of these three buttons:

How was this achieved? Here, we extend a LinearLayout with horizontal orientation, giving each button zero width and equal weight. This ensures that the buttons are perfectly placed along the X axis. We don’t override the onMeasure and rely on the parent’s implementation to compute the correct measured width and height. However, we do override the doLayout:

  • Call the super implementation so that it can compute the final left and right bounds. We’re not going to change those.
  • Compute the height of the curvature area. As mentioned before, this value depends on the pixel size of the screen and is only known at runtime.
  • For each visible child button, take two reference points. One is at 25% of its horizontal span, and another is at 75%. For each reference point, compute the matching Y position on the curvature arc. The min of these two Y values will define the bottom bound of the button.

Why choose 25% and 75%? Take a look at the “Apps” and “My apps” buttons in the screenshot above. Each one extends all the way to the container edges, and computing the Y offset based on those would result in unnecessarily high bottom bound. Here you can say – what if the button text is very long and spans the entire button width? There are a couple of other settings at play here – left / right padding and scaling down the text size (will be detailed below) that keep the texts from overlapping the curved arc.


Next up – text scaling. As mentioned in the previous entries, Android devices come in a wide variety of screen sizes and densities. In addition, if you target international markets, you should properly localize your strings. So, unless you want to see your fine-crafted UI looking (say it with me) like ass on a lower end device running under one of the more expressive European languages, you’ll need to make sure that the texts comfortably fit within their intended bounds.

Marking the view with marquee ellipsizing and flipping the selected bit to true is one option. That would enforce auto-scrolling texts that do not fully fit in the padded bounds. This is simple, but quite distracting. If evolution is to be believed, even before our ancestors were hunters, they were prey to large clawed animals. If you detected a suspicious movement in the nearby bushes, you were supposed to run away like a little girl. Or stand there and be eaten like a real caveman. Anyways. Getting back to writing pleasing UIs. User’s eye will be instinctively drawn to scrolling content – which is one of the reasons why we set a relatively large auto-advance interval on the carousel. Now imagine us scrolling the texts on all three navigation buttons at the same time. That’s not really good. They are navigation buttons, not the main content. Unless you’re absolutely sure that this is what you want, you should not marquee anything on the screen. So another option is to scale down the text size based on the available space.

Here is one example:


This is the header section of the new app details screen. We have a nice drop shadow for the thumbnail, the white texts and extra nice vertical alignment of the thumbnail and the control column to its right (both top edge and right edge). If you look closer at the two buttons, you will see that the regular / bold style is not the only difference. The second button also uses a slightly smaller text size to fit the longer string and prevent some of the characters from overflowing into the padding. This is fairly easy to achieve:

  • Extend the Button class. In the constructor, call getTextSize() – that would be the default text size, and divide it by the screen density. Store that value.
  • In addition, override the onLayout method. The following bullets are relevant to the context of that method.
  • Get the measured width and height (for restoring those values later on).
  • Starting from the saved text size, call setTextSize() and measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED). Assuming that the button is defined to be singleLine, you can now call getMeasuredWidth() to know how many pixels does the button need (including the padding) for the specific text size.
  • As long as the measured width is less than available width – use the left and right parameters passed to onLayout, decrease the text size and repeat.
  • Once the you found the max text size that makes the text fit in, call setMeasuredDimension to restore the “original” measured width and height.

And here’s another example:

Here, we have rather long Korean strings for the tab titles. The algorithm above can be applied here as well to scale these strings to completely fit on one line, but the end result will be quite unreadable. Instead, the tabs allow two lines of text, and the algorithm is slightly different:

  • Call setMaxLines(2). Call setLayoutParams with a LayoutParams set to WRAP_CONTENT on both axes.
  • Now you want to compute the two-line height of the view given the specific width limitation and see whether it fits in the available height. This will also cover the case where the text can completely fit on one line with the starting text size. Call measure passing the MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY) for width and MeasureSpec.UNSPECIFIED for Y. Now call getMeasuredHeight() and compare to the available height. If it fits, you’re good to go. Otherwise, proceed.
  • Now call setMaxLines(10). This is an arbitrary setting, but it’s important that the value is larger than 2.
  • Starting from the initial text size, call measure with the same values as above. Check two conditions – that the measured height is less than the available height and that getLineCount() is at most 2. The second condition will prevent displaying more than two lines of text – even if three (or more) will fit in. Here, we prefer to show two lines at a smaller size than three lines at a larger size – to maintain visual balance across all three tabs that may have radically different text lengths for their titles.

Now let’s talk about padding. In landscape orientation we display content side by side – the carousel / overview go on the left, and the vertical listing goes on the right. Here is an example of such layout:

We want to prevent the curved right edge of the info section from partially hiding some of the content in the vertical column to its right. This can be achieved by setting the left padding based on the horizontal span of the overlapping area. As mentioned before, we want the section headers (fading vertical gray gradient) to fully extend below the arc – effectively preventing us from setting the left padding on the entire column. This means that we set the left padding on each child view. However, some of the child views may have their own left padding defined in their layout XMLs – for purposes of balance, alignment or hierarchy.

Our first attempt was to call getPaddingLeft(), add our extra padding and then call setPadding(). Surprisingly simple, but not quite working. The layout pipeline can make several passes at measuring and laying out the views, and if you add extra padding on each such pass, you will end up with too much padding. There’s a simple workaround:

  • Create a res/ids.xml file and add an item with type=”id” and some name and recompile your project. You should have the new R.id.yourName constant.
  • Every time you need to tweak the padding, call getTag(R.id.yourName) and check if it’s null. If it is, get the current left padding and store it with setTag. If it is not null, then the current tag value is the original left padding.

As with everything else, adding animations should be done with restraint. It’s very easy to go over the top and make everything pop, scroll, slide, fly, cross fade and in the process cause great deal of misorientation, distraction and frustration (which, unless you’re selling shady prescription-only drugs or cheesy wedding videos is not a good thing). In the new Market client you should see only two types of non-user-initiated animations: auto-advance on the carousel and marquee scrolling of long app titles. The auto-advance is scheduled to run every 20 seconds, and is bumped to 40 seconds once the user starts interacting with the carousel. It goes down to 20 in decrements of 2 as long as there is no user interaction. This allows us to show that there is more than is currently visible without overloading the screen too much. Long app titles should be scrolled in order to see the full application title without going to the details page.

However, some aspects of the new UI deserve an extra dynamic facet to guide the user through the complete flow.

This is how the new application details page looks in landscape mode. Note the “More” button in the description section. Application descriptions can be quite long (especially with the newly added “recent changes”). The long descriptions effectively push the screenshot section down and result in a large amount of text displayed on the screen by default. Our interaction designers felt that the screenshot section deserves to be at least partially visible above the fold, with the description section collapsed by default to three lines. Note the nice fade-to-white transition on the last line of the description section, hinting that there is more content, and ending in the “More” button. Tapping the button expands the description to show the full contents – in a nice and smooth transition. We felt that this short (around 300ms) transition helps to guide the eye between the two views without being too annoying (==long).

The implementation is quite simple and works well even on older devices going all the way back to 1.6 (looking at you in disapproval, Streak). The vertical column on the right is a LinearLayout with vertical orientation. When the “More” button is tapped, we initiate an animation. On every animation callback, we call getLayoutParams() of the description view and change the height attribute based on the current animation position. This is followed by calling requestLayout(). The framework takes care of the rest – detecting that the child view layout params has been modified and going to the parent (our LinearLayout) to re-layout all its children.

The same transition sequence happens when you click the price button in the info section. The content on the right is replaced with the permission list which slides from the top edge, guiding the user’s eye to connect the tapping of the button and the content change on the screen.


There’s a whole lot more going under the hood and this has been just a small glimpse into the redesign of the Android market client which has been a truly collaborative effort across our designers, developers, testers, dev rel, product management and the guy sitting in the basement with his red stapler. If you’re interested in joining us and seeing your app user base growing by 300,000 people every day, let us know.

Design, uninterrupted #112

December 15th, 2010

Today’s post highlights the design of JBrewer.me by Joshua Brewer, co-creator of 52 Weeks Of UX. A minimal single-page design that fits the entire content above the fold and uses a clever graphic illustration to further reinforce the main tagline. A denim-style slightly worn out pattern is a perfect background for the illustration, and clean typography highlights the austere two-column grid and ample white space.

Meet the Green Goblin, part 3

December 15th, 2010

Last Friday we announced a significant update to the Android Market client. A whole slew of features went into this update (and many more are to come), and this week the pixel geek in me will be talking about the new visual design of the application. Today it’s time to dive into the mechanics behind the swooshy header. The new green header is used in all Market client screens, creating visual continuity throughout the various browsing and purchasing flows. It has a whole bunch of different gradients and highlight streaks, but there’s one important thing to notice – the visuals are the same no matter the size of the header or the orientation of the device. Here is the new header in home screen under portrait mode:

And here is the header of the same screen under landscape mode:

Note how the long diagonal highlight streak that starts around the top-left corner and goes towards the bottom-right corner always intersects the curved edge – bottom in portrait and right in landscape. Let’s see the header in the portrait category listing page:

There are a couple of things worth noticing. First, the visual design maintains the connection to the main browsing pages by adding a curved bottom edge to the tab strip. Second, note how the main header visuals are maintained here without being vertically squished. The long diagonal highlight streak still intersects the curved edge, but much closer to the left edge, and you don’t even see the double converging highlights that run in the bottom part of the taller header. And here is the same header for search results (also used in my apps and all the purchase pages):

With even less height, squishing the full visuals would look very bad; instead, we only show some of the highlight streaks.

And now the fun part – how is this implemented? It’s highly recommended to always consider using nine-patch images as your first choice. It allows you to separate the visual styling from the application functionality, skin controls of different sizes in a consistent manner and take advantage of hardware acceleration (when available) since the vast majority of the system controls are styled using nine-patches. Is this what we’re currently using for the new Market client? No. Let’s see why.

Nine-patches are great if you have visual areas that are “stretchable”. Take a look at the images in the official documentation:

Nine-patch is just a 3*3 grid. The four corners are not scaled at all and are well-suited for rounded corners and varying drop-shadows. The top and bottom pieces are stretched horizontally, while the left and right pieces are scaled vertically. Finally, the center piece is scaled along both axes. This means that if you place any non-linear gradient or path in one of the non-corner areas, it will be stretched. Looking back at the target visuals of the new green header, you can see that there is nothing that can be stretched without ruining the visual appearance. While you can stretch most of the gradients, what about the highlight streaks? Scaling them up or down would result in blurry or pixelated paths. What about the curved edges? As mentioned in the first installment, the actual arc curvature depends on the pixel size of the screen, and scaling a curved anti-aliased path would result, once again, in either blurry or pixelated visuals. Less ass than what we had before, but ass nonetheless.

So instead, we’re drawing the new header visuals in code. There are no images, just a whole bunch of trigonometry, geometry and a few selected Canvas calls. The next screenshot shows a rough analysis of the swooshy header

Here we identify a number of different areas, each one with its own contour and gradient, and a number of highlight streaks, each with its own path, thickness and gradient. I could probably write another 3-4 blog entries documenting all the pixel-level details of the implementation; join the team to see the code. Just a few points worth mentioning:

  • Never ever ever allocate new objects in your custom layout or draw methods, especially if you’re doing any type of animation. Allocating lots of small objects makes garbage collector sad and your animations jerky.
  • Call setWillNotDraw(false) in the constructor of your custom view group that implements the onDraw(Canvas). Otherwise your custom drawing will not be called. If your custom view does not extend the ViewGroup, no need to call this method.
  • Canvas.clipPath(Path) is your friend for any non-trivial custom painting code. Surround it with Canvas.save() and Canvas.restore() so that you don’t need to worry about subsequent graphic calls on the Canvas object. But make absolute sure that they match – if you call Canvas.restore() one too many times, it will affect the visual appearance in most unpredictable ways. You can also use a more reliable Canvas.restoreToCount() – thanks to Romain for the tip.
  • If your clip path contains diagonal lines or arcs and you then call Canvas.drawPath when the paint style is FILL, you will end up with aliased edge (diagonal or curved). Instead, you will need to compute the clipped path yourself (brush up on your trig). Don’t forget to call Paint.setAntiAlias(true). This may be less noticeable on higher-density screens such as Droid X or Nexus S, but is extremely visible on lower end hardware such as G1 or Flipout.
  • Drop shadows that follow custom paths are tricky. Our design calls for a translucent drop shadow that follows the curved arc. After trying a number of options, with Paint.setShadowLayer and RadialGradient among them, the most performant one turned out to be drawing a series of arcs. The arcs start from the thickest stroke with the lowest alpha and progress towards the thinnest stroke with the highest alpha. There is no best option that fits all requirements. It depends on the specific visuals that you’re looking for and how heavy the performance aspect of each specific implementation is.
  • Gradients that use translucent or transparent colors should use the RGB values that match the background color (more info here).
  • Don’t use direct pixel values. Multiply all such values by the Context.getResources().getDisplayMetrics().density.
  • Don’t use hard coded colors. Extract them to res/colors.xml and load them once with Resources.getColor().
  • Math is your friend. Don’t be afraid of it.
  • Never ever ever allocate new objects in your custom layout or draw methods, especially if you’re doing any type of animation. Allocating lots of small objects makes garbage collector sad and your animations jerky.


That’s it for today. Tomorrow i’ll talk about various random bits and pieces.