May 9th, 2013
The latest release of Play Store has introduced a more streamlined look-and-feel for the tab strip of our ViewPagers. If you don’t use it in your project, you should.
ViewPager comes with two implementations of the tab strip – PagerTitleStrip and PagerTabStrip. The first one is not really worth talking about. The second one is much nicer, allowing tapping the titles to switch to the relevant tab and exposing APIs to control a couple of visual aspects. We were using PagerTabStrip up until the latest iteration of the store app. That is, until our visual designers wanted a nicer appearance that is more inline with the general direction of where the platform tab indicator is heading.
On the visual side of things, we wanted to:
- Use different colors for full underline and the selected tab indicator.
- Use translucent vertical separators between the tabs.
- Have the left-of-the current tab “peek out” to indicate that there’s additional content available.
- More compact display of tabs – instead of having left/right title all the way at the edges (which looked particularly bad on wide tablets), display the title tabs as a “connected” chain.
- Nicer swiping feedback as the user swipes between tabs.
As it turns out, you can write your own tab strip control.
Step 1: make yourself familiar with the source code of the existing components. That always helps.
Step 2: throw some code around and see how it looks like on the screen. At some point, after enough Mountain Dew has been consumed, it starts looking somewhat decent. Rinse and repeat.
Let’s talk about the particular implementation details.
- Each tab title is a TextView. This provides nice accessibility support. Mark it as focusable and set a background drawable that has your app’s assets for pressed and focused state.
- If the content of your ViewPager is static, iterate over all page titles and create a TextView for each one. Set a click listener on each text to call ViewPager.setCurrentItem.
- Add a global layout listener and call scrollToChild with ViewPager.getCurrentItem. Don’t forget to remove that listener after you’re done with the initial scroll. The intent here is to respect the initial tab selection and scroll the selected tab into view.
- Implement ViewPager.OnPageChangeListener (somewhere outside your view pager / tab strip).
- In onPageScrollStateChanged remember the current scroll state.
- In onPageSelected if the current scroll state is SCROLL_STATE_IDLE, remember the selected index, set offset (more on this in the next points) to 0 and call invalidate on your tab strip.
- In onPageScrolled remember the selected index and the offset, call invalidate on your tab strip and also call scrollTo on the tab strip to scroll its contents based on the selected index and the offset.
- You want to keep the scroll state of the tab strip in sync with what the user is doing with the your view pager. If the user swipes right to go to the tab that is currently to the left, you want to start scrolling the title of that tab as well. How much? That depends on your design. Our target is to have some part of that title always peeking in. So you would need to compute the value of the first parameter of scrollTo based on the tab index, relative scroll offset and that extra peeking delta.
- Now about the pretty pixels. Designers love assets. You can create a whole bunch of assets for the individual tab – normal state, pressed state, focused state, pressed+focused state, let’s throw the selected state in, and what about the disabled state? That’s nice. But. Take a look at the second screenshot – the selected underline “slides” between the two tabs. That’s not something that you can do with assets set on each individual TextView behind these two tabs. I guess you can do an empty View, set those assets on it and start laying it out dynamically as you slide. Kind of gross.
- So instead, right now we’re painting the underlines in code. Canvas.drawRect is your friend. Just don’t create the Paint objects every single time in onDraw.
- First layer the thick colored selection underline. This is where you need the index of the selected tab and the relative scroll amount. During scroll, this underline “slides” from one tab to the next (left or right). So you use the scroll amount to interpolate the X coordinate of both the left and the right edge of that underline based on the X coordinates of the two tabs.
- Second layer is the thin transparent black underline that goes across the entire tab strip. Using transparency creates a nice layered effect across the bottom edge of the colored underline.
- Finally, since we’re already drawing lines in code, why not draw the vertical separators in code too? These can be done with drawables set on each title TextView, but you have the edge case of the first/last tab. Since we want separators between the tabs, but not on the outside, you’d need to create two sets of assets – one for the tabs that show that separator (say, along the right edge), and one for that special tab that doesn’t (in this case, the very last tab). If you use translucent black for these separators, you’ll get a nice blend with the press/focus highlight assets set on the tabs – provided that they are translucent too.
- You’ll need to provide a way to swipe the tabs themselves. Our current solution is to have the tab views live in a horizontal LinearLayout that lives inside a HorizontalScrollView.
So, to summarize. Track the scrolling. Draw translucent lines. Make your designers happy. Oh, almost forgot. Don’t ignore edge cases. Such as, say, all the tabs fitting in on the screen – in which case you don’t need anything to “peek in” from the left edge.
April 26th, 2013
I’ve talked about item details pages in the Play Store before. It’s a very interesting content hierarchy, with blocks that vary by their internal complexity and the overall logical importance.
Some blocks are more important than others. For example, the item cover and (in this case) the movie trailer are very visual, and we want to put them above the fold. There’s the item name, and some additional secondary info (such as movie genre, ratings, release date or running time) that “deserve” to be above the fold. There’s a certain logical hierarchy to that information that needs to be consistently exposed on the screen no matter what the device configuration is – some blocks “belong” together, no matter if it’s a single-column or a double-column layout.
And then there are action buttons. The buttons that keep us in business. We want them to be immediately identifiable, and – as much as we can – always there no matter how far you scroll the content.
Our previous solution for the buttons was twofold. Visually, the buttons are using a light blue color that sets them apart from all the other elements on the screen (apart from the rather awkward rating stars). Then, as you scroll the left column, we have the scroll-to-snap thingie where the buttons actually snap to the top edge of the viewport and stay there as you keep on scrolling that column.
As we started working on redesigning the main streams, it became quite clear that we need to redesign the details pages as well. Removing the heavy dark grey boxes and pinstripe textures was kind of a given, but that left us with the question of how to create a lighter, flatter look while still maintaining the same logical hierarchy of content. What you see in the latest Play Store release is the first lightweight iteration of where we’re heading.
The summary section has been redesigned to be more consistent across all device configurations. The title is more prominent, and the action buttons have moved to the right edge of the screen. The global rating stars and count have moved into what we call the byline section, going back to a consistent layout across the devices as well. And now, the space vacated by the action buttons and the stars in the left column can be given back to display larger, more visceral cover art. This is particularly relevant for “traditional” media that encodes additional information in the cover art – information that is lost if you start downscaling it by too much.
Going beyond the summary section, we’ve “lost” quite a few visual elements that helped separating the sections. No more colored headers, no more fancy textured footers. We’re going for a simpler, flatter look that uses typography and thin separators. Losing all this color has a nice side effect of making the action buttons maintain their visual importance. We removed the blue color that helped them stand out. But not having too many elements that use the same main color makes them stand out in the new design.
Finally, one of my favorite pieces is how the new design keeps the nice alignment of content above the fold. If you trace the bottom edge of the dark gray box in the left column and the bottom edge of the trailer in the right column, you’ll see that they align perfectly. This helps delineating the blocks that we consider to have more logical importance. The same delineation is maintained in the new design as well. Note how we’re able to move more information into the “main” section while still displaying larger cover art. Also, note how the visual alignment works across the bottom edge of the trailer and the bottom edge of the white area in the left column – not its drop shadow.
April 26th, 2013
The EdgeEffect class provides a standard way to draw overscroll effects at the edges of scrollable containers. The EdgeEffectCompat class from the support library wraps it so that it can be used across multiple platform versions in a backwards compatible way. I’ve had my eye on this particular class for a while, and I finally got a chance to use it when we were working on improving the accessibility support for the video/screenshot gallery section on app details pages.
For a number of design requirements we have implemented our own custom scrolling component for the section that displays the application screenshots and the optional video trailer. The downside of doing custom scrolling handling is that you have to take over pretty much everything, including accessibility. This has been the last major piece that was lacking proper support in that area, and the latest Play Store release has finally closed that gap. And while we were in that section, I also fixed the missing overscroll indication.
- You get proper overscroll effects for “free” when you’re using the core views, such as ScrollView or ViewPager. And if you’re doing a custom scrolling implementation, EdgeEffectCompat is your friend. Here’s what you need to do in order to do it properly:
- For every edge that should show overscroll, define its own EdgeEffectCompat object.
- Call setWillNotDraw(false) on your container.
- For MotionEffect.ACTION_MOVE, call EdgeEffectCompat.onPull() when you detect overscroll at the matching edge. If that method returns true on at least one object, call invalidate() on your container.
- For MotionEffect.ACTION_UP and MotionEffect.ACTION_CANCEL, call EdgeEffectCompat.onRelease(). If that method returns true on at least one object, call invalidate() on your container.
- Override draw() method, and after calling super.draw() go over all EdgeEffectCompat objects. For each one that returns false from its isFinished(), apply the matching chain of transformations (rotate, translate), call EdgeEffectCompat.setSize() and EdgeEffectCompat.draw(). If at least one draw() method returns true, call invalidate() on your container as the last line of your draw() method.
There’s the usual hand-waving involved here, and the source of ViewPager provides a complete example of doing custom overscroll draws. The two more complicated points are about updating the objects only during dragging, and about applying the correct sequence of transformations depending on which edge you’re drawing.
January 7th, 2013
Circling back to the topic of unsolicited redesigns, the discussion over at Branch largely talks on one of the points I mentioned last week – focusing on visual instead of interaction.
Looking at the existing screens of a product (be it a web site, web app, mobile app or desktop app) and making them prettier is all about visual design. Playing with colors, fonts, alignments, paddings, margins, gaps – or adding crisp stock photography – is a very visceral way to show your skills as a visual designer. This process sometimes omits certain technical aspects, such as, say the impact on the loading time and network (monthly bill) consumption, limitations of the underlying platform and the impact on framerate for some transitions, font rendering capabilities that can make your nice typography look quite bad and more.
But what about the interaction design? What about taking a hard look at some of the products you’re using on the daily basis and seeing how you can smooth the bumps that you encounter along the way of completing a certain task.
How about the process of finding the certain episode of your favorite TV show, buying it and watching it? How many screens does it take? How many taps, swipes and flings does it take? How much do you type on that small virtual keyboard? How much of that annoyance can you shave away without degrading the functionality scope?
What about making an hotel reservation? Direct messaging somebody on Twitter? Muting an annoying hash tag? Finding what is the closest movie theater that is playing The Hobbit in HFR 3D? Buying a ticket to that movie? Bookmarking the location of that movie theater? Navigating to that theater a few hours later? Taking a few pictures of yourself and your friends in the theater lobby and creating a booth-style photo strip? Ordering a pizza after you get back home?
There’s plenty of apps that do these tasks. You can redesign the outer layer of each one of those. But what about looking at the navigation models of each one, really looking at them and trying to improve them? Talking about what bothers you in the particular flow, showing how you restructure it and convincing the reader that your changes are an actual improvement? Taking those changes and applying them to the rest of the app? Making those change consistently better across various form factors where larger devices may combine two or more smaller screens at the time?
This is not shiny. This is not sexy. Interaction designers operate on the level of wireframes and flow charts that show transitions between various screens. A vibrant pixel-perfect mock with glamorous stock photography is visceral. Easy to look at. Easy to consume. Easy to understand the change. A wireframe that rearranges the content to move some things above the fold, or group related things is not. It takes time to understand. Words from you to describe why your change is better. Attention from the reader to look at how things were arranged before, how they are arranged now and what is your reasoning behind this.