Cyril Mottier

“It’s the little details that are vital. Little things make big things happen.” – John Wooden

The Making of Prixing #1: Fly-in App Menu

Note: Everything I published on this website in the past had nothing to do with what I was doing at work. But now for Prixing I’m changing that, because I think it can be a great opportunity for me to give other developers some advanced Android UI development tips and tricks. As this is the first article linked to my professional work I also have to add a full disclosure: I work for Prixing, but the opinions expressed in my blogs or anywhere else, are my own, and have nothing to do with my employer.

We, at Prixing, released a brand new version of our Android application in the middle of April. To sum up, Prixing is a startup created by the French entrepreneur Eric Larchevêque. It aims to help people find product information and the best prices in a localized manner. The company currently only targets the French market but the application can be downloaded regardless of your location. Go to the Prixing’s Google Play page to download it on your Android device (the current version is only dedicated to handsets) and play with it!

I started working at Prixing as the lead mobile software engineer in January of this year. As a huge fan of the Android platform, I mainly worked on making the Android application easier to use and fancier. As a result, tons of changes and improvements were made. After the release of the application, I received several questions regarding the implementation of the UI. I sincerely appreciate having curious developers asking me how I implemented this or that. This series is an attempt to answer all of the questions I received. Instead of sharing great tips to a very restricted set of developers I thought it was way better to share them globally via some blog posts.

Note: Everything I will talk about in this series will probably never feature source code. It is absolutely intentional. Prixing’s source code is obviously not open-source and chances are high it will remain closed-source in the future. The purpose of these articles is to describe the techniques and methods to use to make astonishing UIs rather than giving ready-to-use snippets of code. Nevertheless, I may open source some interesting and reusable snippets of code in the future. Feel free to leave a comment below so I can anticipate the average number of motivated developers.

In this first post we will start with one of the most important UI widgets in Prixing: the fly-in app menu. The figure below gives you a screenshot of the UI widget on a Galaxy Nexus. You can also click on the picture to access a high resolution screenshot and look at the details but I strongly suggest you download the application to fully see how the Prixing fly-in app menu works and what it looks like.

I can already hear some of you saying “That’s a Facebook-like menu!”, “It looks like Path on iOS!”1 etc. Indeed, in addition to technical questions, I also received some messages from people who don’t consider the fly-in app menu as an Android UI pattern. I think I talked a lot lately about what I consider Androidy or not (cf my thoughts dealing with splash screens and the pull-to-refresh UI patterns). I am not going to talk about it in this series mainly dedicated to development techniques. I am sure some people will soon talk about it and describe whether it is good or not. However, I would like to outline my point and make it clear. Personally, I don’t think the fly-in app menu is a straight Android UI pattern. I actually believe it is a UI pattern not related to any platform at all. Applied to Android, this pattern works pretty well and doesn’t go fundamentally against Android principles. The only problem I have with it is the back stack is totally messed up. When looking closer at the pattern you can notice it is pretty much like a classical two-panes screen (in Google Talk or GMail for tablets) mixed with a plain old SlidingDrawer. We decided to use the fly-in app menu pattern in Prixing for three main reasons:

  • It is directly accessible from everywhere, regardless of how deep you are in the screen hierarchy, using a simple click and/or swipe gesture

  • It presents items in list-manner rather than a paged-manner. This is a pretty big advantage when dealing with a large amount of items as a list-based menu is more understandable than paged-dashboards

  • It is not dedicated to Android nor iOS. Consequently, we can have overall application usage consistency among our mobile applications

I recently found a lot of fly-in app menu implementations (Facebook, 8tracks, Evernote, Spotify, etc.) but found none as polished as the Prixing one. Of course, most implementations are running great but they lack naturalness, easiness, smoothness, etc. or a combination of it. I am not saying the implementation included in Prixing is perfect as we still have some improvements to make but I think it contains a lot of interesting concepts you may need if you want to implement your fly-in app menu or simply enhance it.

Introduction

Implementing a fly-in menu requires building a custom layout. Creating a class inheriting from ViewGroup helps you to precisely control how components are being laid out and drawn. Being the root View in Prixing’s View hierarchy, we named it RootView. The RootView basically contains two child Views: the menu that has to be laid out on the left of the screen and the content view we called the host (mainly because it is always a TitleBarHost2, a custom implementation of the ActionBar holder paradigm)). The purpose of the RootView’s onLayout is to lay out the menu and the host in a stacked fashion as a FrameLayout would do. The only difference remains in the width taken by the menu which is currently the entire available width minus the app menu button width (44dp).

RootView == SlidingDrawer?

As we discussed earlier, the RootView is pretty much like a SlidingDrawer. As a consequence, APIs are very similar and include methods to action the RootView in animated fashion - animate[Close|Open|Toggle]() - or not - [close|open|toggle]() - and some methods to retrieve the current RootView’s state - is[Opened|Moving|Animating](). The first natural feature to implement is the “open-by-clicking” the app-menu button. In order to do so, we simply applied an OnClickListener to the app-menu button that does nothing more than calling RootView#animateToggle().

Scrolling content using Scroller

Implementing open/close animations can be pretty simple starting from Android 3.0, thanks to the new animation framework. Unfortunately, Prixing had to be compatible with devices running on Android down to the version 2.1. As a consequence, we decided to implement the open/close animation manually. To be honest, it can be done fairly easily using classes from the Android framework. Animating a value can be done manually or using a Scroller/OverScroller. Scroller is a helper class that can be used to compute position values of objects scrolling or flinging in a 2 dimensional space. Once you want to start animating the menu simply call Scroller#startScroll and keep calling Scroller#computeOffset every 16ms (60 frames per second) using a Handler and the Handler#postDelayed(Runnable, long) method. In the Runnable that gets executed every 16ms, reading the current position of the Scroller gives you the position at which your object should be translated to.

Actually translating the host using offsetLeftAndRight

Translating the host value is done by adding, in the onLayout an integer containing the value to add to the original X position of the host. We will call this variable mHostOffsetX. Updating mHostOffsetX in the previously described Runnable and calling the well known requestLayout() would get the job done. Using this method would run perfectly but your application may suffer from severe lags. Indeed, using requestLayout() repeatedly is usually a bad idea as it forces the system to measure and layout all Views in the hierarchy. Translating the host view (in other words, your entire content) doesn’t require it to be laid out again as nothing but the position of the host has changed. Fortunately, the system provides a great method called offsetLeftAndRight(int) that will translate the host by the given amount of pixels from its current position. Do not forget to call invalidate() to force the RootView to redraw itself and reflect the translation on screen.

Optimized rendering

Since Android 2.1, the system avoids rendering a View when it is not visible. A View is considered as “not visible” when its bounds are outside screen bounds or if it is entirely covered by a View which returns true to a call to View#isOpaque(). To ensure the rendering pipeline does not try to uselessly draw the menu when it is completely hidden (closed state), just set its visibility to View.GONE. It won’t help in many cases (API level greater to 2.1) but using this trick is pretty straightforward and easy to include in your code. In other words, setting the menu visibility to View#GONE will ensure your application runs the same as it would have without the menu.

Smoother closing/opening animations

If you look attentively to the opening/closing animation you will probably notice it is pretty “brutal”. Indeed, the drawer opens/closes in a linear fashion. Because of this, the user has a strange and negative feeling the menu “crashes” abruptly when it has arrived at its final position. This “crash” is actually due to the default behavior of the Scroller class. By default, the Scroller computes the current position of an object using a LinearInterpolator. This means the current position is determined by the following formula (simplified to a single axis):

xcur(tcur) = xstart + distance * ((tcur - tstart) / duration)

In this formula the position (xcurrent) is a function of the current time. distance indicates the overall distance of the animation and duration the total duration of the animation. The main problem with this formula is it doesn’t include an interpolation function. This is actually a simplified version of the complete formula based of the hypothesis interpolator(t) = t. The complete formula is given below:

xcur(tcur) = xstart + distance * interpolator((tcur - tstart) / duration)

interpolator(t) is a function defined on [0, 1]. It usually returns values in the [0, 1] range but this is not mandatory as having returned values outside of this range will indicate overshooting animations. By modifying the Interpolator, you can easily change the way the animation performs. In Prixing, we started by using some of the framework-provided Interpolators but weren’t satisfied so we created a SmoothInterpolator which is basically a polynomial function:

interpolator(t) = (t-1)5 + 1

The difference between the two interpolators is given on the figure below. The green curve describes the Interpolator used in the current build of Prixing. The red curve is the Scroller’s default Interpolator:

As you can easily see, passing a SmoothInterpolator when creating your Scroller will make the animation way smoother. The drawer will open/close very rapidly at the beginning but will slow down at the end to reach quietly and smoothly its final position. Do not hesitate to play around with Interpolators as it helps you create stunning animation curves and may make your UI way more polished from a user point of view.

Some interesting additional features

This first article concentrates on having a basic RootView that can be opened/closed programmatically or via a simple click on the app-menu button. In the manner of Angry Birds, Prixing uses the system menu button to easily toggle the RootView. It is implemented at the Activity level by calling RootView#animateToggle() in Activity#onKeyUp(int, KeyEvent) but it could also be implemented at the View level using View#onKeyUp(int, KeyEvent)

Note: Eagle eyed people may have noticed the current implementation of Prixing is based on Activity#onKeyDown(int, KeyEvent) rather than Activity#onKeyUp(int, KeyEvent). This is definitely a bug of mine that has been fixed in a recent release.

To prevent the user from finishing the current screen when the menu is open, it could be smart to first ensure the menu is closed. You can do it by simply calling RootView#animateClose() in Activity#onBackPressed() when the menu is fully opened.

Being an actively used control, we need to ensure the app menu button is very easily accessible. The best way to do so is to virtually enlarge the touchable area of the button. I seriously don’t know why but this is not something that is done by default on the ActionBar. The UINavigationBar - iOS ActionBar equivalent has a built-in similar mechanism which makes all buttons impressively accessible. Prixing is based on a custom implementation of the ActionBar so we have simply set a TouchDelegate to the app menu button. It enlarges the touchable area with a 30dp wide frame. If you want to do it I strongly suggest you read a previous article of mine dealing with enlarged touchable areas.

Conclusion

That’s it, at least for now! We have covered everything you need to create a panel that can slide from a closed state to an open state or vice versa. In future articles we will continue enhancing our RootView by managing state of the art features such as bezel swiping, parallax translation, menu fading, selection arrow, menu hint, etc. I have tried to cover everything in this article but I may have forgot some interesting techniques. Do not hesitate to tell me if you want more explanations on a particular feature. Stay tuned for more.

Thanks to @foxykeep and @franklinharper for reading drafts of this


  1. When I wrote the article dealing with the scrolling info panel, I criticized how poor, in term of functionalities, the Android version of the application was compared to the iOS one. I explained how to implement a scrolling info panel and I recently received an update of the Path application that includes the UI widget. I really don’t know if Path used my article to implement it. This fly-in app menu is something the Android Path app is also missing. If you are a developer at Path, feel free to use these tricks and post a comment to thank me in return!

  2. Implementing a fly-in app menu with the built-in Android ActionBar requires using several awful hacks one of which consists of considering the View returned by Window#getDecorView() as a FrameLayout. As a bitter enemy of hacks I couldn’t resolve myself to do it and decided to write my own implementation of the ActionBar paradigm. Let’s be honest: this is far from being ideal…