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:

<!-- Custom activity content layout -->
<FrameLayout
	android:layout_width="fill_parent"
	android:layout_height="?android:attr/actionBarSize"
	style="?android:attr/actionBarStyle"
	android:id="@+id/fakeActionBar">
	<TextView
		android:text="Swipe down to refresh"
		android:layout_width="wrap_content"
 		android:layout_height="wrap_content"
		android:id="@+id/swipeToRefreshText"
		android:layout_gravity="center"
		android:visibility="invisible"
		android:textColor="#0099cc"
		android:textSize="18sp" />
</FrameLayout>

And then in our main Activity OnCreate method, we can setup the Action bar like this:

// Activity OnCreate's method
protected override void OnCreate (Bundle bundle)
{
	base.OnCreate (bundle);

	RequestWindowFeature (WindowFeatures.ActionBarOverlay);
	ActionBar.SetBackgroundDrawable (new ColorDrawable (Color.Transparent));
	SetContentView (Resource.Layout.Main);
}

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 ProgressBar widget.

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:

<!-- Our centered progress bar -->
<FrameLayout
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<!-- Main content view -->
	<ListView
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:id="@+id/listView1"
		android:entries="@array/planets_array" />
	<!-- Overlayed progress bars-->
	<LinearLayout
		android:orientation="horizontal"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:visibility="invisible"
		android:id="@+id/loadingBars">
		<ProgressBar
			style="?android:attr/progressBarStyleHorizontal"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:id="@+id/loadingBar1"
			android:layout_weight="1"
			android:progress="50"
			android:layout_marginTop="-7dp"
			android:scaleX="-1.0"
			android:scaleY="1.0" />
		<ProgressBar
			style="?android:attr/progressBarStyleHorizontal"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:id="@+id/loadingBar2"
			android:layout_weight="1"
			android:progress="50"
			android:minHeight="0dp"
			android:layout_marginTop="-7dp" />
	</LinearLayout>
</FrameLayout>

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:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background"
          android:drawable="@android:drawable/progress_bg_holo_dark" />
    <item android:id="@android:id/secondaryProgress">
        <scale android:scaleWidth="100%"
               android:drawable="@android:drawable/progress_secondary_holo_dark" />
    </item>
    <item android:id="@android:id/progress">
        <scale android:scaleWidth="100%"
               android:drawable="@android:drawable/progress_primary_holo_dark" />
    </item>
</layer-list>

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:

foreach (var p in new ProgressBar[] { bar1, bar2 }) {
	var layer = p.ProgressDrawable as LayerDrawable;
	if (layer != null)
		layer.SetDrawableByLayerId (Android.Resource.Id.Background,
		                            new ColorDrawable (Color.Transparent));
}

The Result

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:

garuma/SwipeDownToRefresh