The pull-to-refresh pattern, popularized by applications like Twitter and finally integrated as a native component in iOS 6, has long been frowned upon in Android land.
The main reason for this refusal is that this pattern is generally considered an iOS pattern and thus app utilizing it on Android were synonymous of “bad ports” of their iPhone counterpart.
The Android answer to the problem this pattern solves, namely refreshing a scrolling list container, has been to simply use a refresh button, either in the main UI or, more recently, in an app’s Action Bar.
The newest Gmail app though has started using another interesting method, closer to the original pull-to-refresh but integrated with Android’s Action Bar:
The idea is that if the
ListView is positioned at the beginning, starting an overscroll will, in addition to the normal edge effect, change the ActionBar to display an action message and an horizontal centered progress bar defining when the movement results in a refresh.
Here is in a few steps how we can reproduce that thing:
Hijacking the Action Bar
By default the Action bar API doesn’t allow you to supply an entirely custom view as would be needed in that case. Thus, we have to somehow find a way to supply our own custom layout while interoperating with the default Action bar.
For that purpose we will exploit two things: first, the fact that Action bar styling (appearance) is readily available through the normal Android style/theming API and, second, a special mode which allows the action bar to be overlayed on top of normal content (as in the Google Maps app).
Thanks to these two facilities, we can virtually recreate the
ActionBar look and feel by hand in a custom view and then let the system action bar draw over it with a transparent background (so that the animations applied to it are not noticeable).
Now when we hide the action bar, we can re-purpose our “fake” action bar layout to display any custom view we want.
Following is the layout XML recreating the action bar background:
And then in our main Activity
OnCreate method, we can setup the Action bar like this:
The advantage of having the ActionBar in this special overlay mode is also that it doesn’t impact the main content layout. Indeed, if the bar hadn’t been in that mode, each show/hide call would result in a full layout pass of the visual tree, which is not what you want.
A Tale of Progress Bars
When the overscroll movement is detected, we are supposed to show an horizontally-aligned progress bar to give feedback to the user. In the spirit of reusing what’s already in the framework, we want to go with the standard
Turns out that using only one progress bar is a bit awkward. Having it expand on both sides means that we would have to re-layout/offset it all the time and monitor the progress value to be even in all cases.
So the idea is that we will be using two
ProgressBar instead and sync them on the same value. Obviously, we need to somehow find a way to flip one of them since it needs to fill up in the other direction.
Again, this is very easy to do in Android by using static transformations. In our case, we will be using the
android:scaleX attribute to flip the X coordinates so that the left
ProgressBar appears reversed.
Following is the layout of that part:
For exactly the same reason than with the Action bar, we use a
FrameLayout to overlay the two progress bars on top of the main content so that making them appear and disappear doesn’t cause an unnecessary relayout.
A slight visual defect of using normal
ProgressBar here is that by default they have a background color that make them look like that:
Which is a little bit too intrusive and harder to tweak with animations since visually they still occupy the same screen space.
However, thanks to the fact that on Android progress bars are implemented with
Drawable rather than custon drawing, we can easily work around that issue.
More precisely, the default Holo styled progress bar uses a layer drawable which, as the name implies, is a special kind of drawable that layers other drawables on top of each other.
Here is the definition of this layer drawable in the framework for the default progress bar:
The layer we want to hide is the one identified with
android:id/background. It’s not that straightforward though since
LayerDrawable objects don’t allow layers to be removed at runtime.
What we can do however is swap the drawable referenced by one of the layer to something else. Thanks to that, we can hide the background by changing its original 9-patch drawable to be a transparent color drawable like so:
I haven’t covered some of the last points of the implementation like animations and event subscribing but those should be mostly straightfoward once you read the code.
Here is a video of the pattern implemented by us:
And you can grab the code from GitHub: