{"id":5837,"date":"2013-06-17T10:00:31","date_gmt":"2013-06-17T14:00:31","guid":{"rendered":"http:\/\/blog.xamarin.com\/?p=5837"},"modified":"2013-06-17T10:00:31","modified_gmt":"2013-06-17T14:00:31","slug":"android-the-swipe-down-to-refresh-pattern","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/xamarin\/android-the-swipe-down-to-refresh-pattern\/","title":{"rendered":"Android: The Swipe-Down-to-Refresh Pattern"},"content":{"rendered":"<p>\t\t\t\tThe 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. Because the feature is generally considered an iOS pattern, apps utilizing it on Android are sometimes regarded as &#8220;bad ports&#8221; of their iPhone counterparts.<\/p>\n<p>Instead of supporting pull-to-refresh, Android applications typically include a refresh button: either in the main UI or (more recently) in an app&#8217;s Action Bar. The latest version of the Gmail app, however, has started using another interesting method&mdash;one that is closer to pull-to-refresh, but integrated with Android&#8217;s Action Bar:<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/sites\/44\/2019\/04\/device-swipe-down-refresh.png\" alt=\"device-swipe-down-refresh\" width=\"768\" height=\"175\" class=\"aligncenter size-full wp-image-5838\" \/><\/p>\n<p>When the <code>ListView<\/code> is positioned at the beginning, starting an overscroll will&mdash;in addition to displaying the normal edge effect&mdash;change the ActionBar to display an action message and a horizontally-centered progress bar defining when the movement results in a refresh. You can reproduce that user interface element in only a few steps.<\/p>\n<h3>Hijacking the Action Bar<\/h3>\n<p>By default, the Action bar API doesn\u2019t allow you to supply a custom view. 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&#8217;s style (appearance) is readily available through the normal Android style\/theming API and, second, a special mode which allows the action bar to be overlaid on top of normal content (as in the Google Maps app).<\/p>\n<p>Thanks to these two facilities, we can virtually recreate the <code>ActionBar<\/code> 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). When we hide the action bar, we can re-purpose our \u201cfake\u201d action bar layout to display any custom view we want.<\/p>\n<p>Following XML is the layout that recreates the action bar background:<\/p>\n<pre class=\"lang:xml decode:true\">\n&lt;FrameLayout\n\tandroid:layout_width=&quot;fill_parent&quot;\n\tandroid:layout_height=&quot;?android:attr\/actionBarSize&quot;\n\tstyle=&quot;?android:attr\/actionBarStyle&quot;\n\tandroid:id=&quot;@+id\/fakeActionBar&quot;&gt;\n\t&lt;TextView\n\t\tandroid:text=&quot;Swipe down to refresh&quot;\n\t\tandroid:layout_width=&quot;wrap_content&quot;\n \t\tandroid:layout_height=&quot;wrap_content&quot;\n\t\tandroid:id=&quot;@+id\/swipeToRefreshText&quot;\n\t\tandroid:layout_gravity=&quot;center&quot;\n\t\tandroid:visibility=&quot;invisible&quot;\n\t\tandroid:textColor=&quot;#0099cc&quot;\n\t\tandroid:textSize=&quot;18sp&quot; \/&gt;\n&lt;\/FrameLayout&gt;\n<\/pre>\n<p>And then in our main Activity <code>OnCreate<\/code> method, we can setup the Action bar like this:<\/p>\n<pre class=\"lang:csharp decode:true\">\nprotected override void OnCreate (Bundle bundle)\n{\n\tbase.OnCreate (bundle);\n\n\tRequestWindowFeature (WindowFeatures.ActionBarOverlay);\n\tActionBar.SetBackgroundDrawable (new ColorDrawable (Color.Transparent));\n\tSetContentView (Resource.Layout.Main);\n}\n<\/pre>\n<p>The advantage of having the ActionBar in this special overlay mode is also that it doesn\u2019t impact the main content layout. Indeed, if the bar hadn\u2019t 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.<\/p>\n<h3>A Tale of Progress Bars<\/h3>\n<p>When the overscroll movement is detected, we are supposed to show a horizontally-aligned progress bar to give feedback to the user. In the spirit of reusing what\u2019s already in the framework, we want to go with the standard <code>ProgressBar<\/code> widget.<\/p>\n<p>It 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 in order to be even in all cases.<\/p>\n<p>We will use two <code>ProgressBar<\/code> controls 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.<\/p>\n<p>Again, this is very easy to do in Android by using static transformations. In our case, we will use the <code>android:scaleX<\/code> attribute to flip the X coordinates so that the left <code>ProgressBar<\/code> appears reversed.<\/p>\n<p>Following is the layout of that part:<\/p>\n<pre class=\"lang:xml decode:true\">\n&lt;FrameLayout\n\tandroid:layout_width=&quot;fill_parent&quot;\n\tandroid:layout_height=&quot;fill_parent&quot;&gt;\n\t&lt;!-- Main content view --&gt;\n\t&lt;ListView\n\t\tandroid:layout_width=&quot;fill_parent&quot;\n\t\tandroid:layout_height=&quot;fill_parent&quot;\n\t\tandroid:id=&quot;@+id\/listView1&quot;\n\t\tandroid:entries=&quot;@array\/planets_array&quot; \/&gt;\n\t&lt;!-- Overlayed progress bars--&gt;\n\t&lt;LinearLayout\n\t\tandroid:orientation=&quot;horizontal&quot;\n\t\tandroid:layout_width=&quot;fill_parent&quot;\n\t\tandroid:layout_height=&quot;wrap_content&quot;\n\t\tandroid:visibility=&quot;invisible&quot;\n\t\tandroid:id=&quot;@+id\/loadingBars&quot;&gt;\n\t\t&lt;ProgressBar\n\t\t\tstyle=&quot;?android:attr\/progressBarStyleHorizontal&quot;\n\t\t\tandroid:layout_width=&quot;wrap_content&quot;\n\t\t\tandroid:layout_height=&quot;wrap_content&quot;\n\t\t\tandroid:id=&quot;@+id\/loadingBar1&quot;\n\t\t\tandroid:layout_weight=&quot;1&quot;\n\t\t\tandroid:progress=&quot;50&quot;\n\t\t\tandroid:layout_marginTop=&quot;-7dp&quot;\n\t\t\tandroid:scaleX=&quot;-1.0&quot;\n\t\t\tandroid:scaleY=&quot;1.0&quot; \/&gt;\n\t\t&lt;ProgressBar\n\t\t\tstyle=&quot;?android:attr\/progressBarStyleHorizontal&quot;\n\t\t\tandroid:layout_width=&quot;wrap_content&quot;\n\t\t\tandroid:layout_height=&quot;wrap_content&quot;\n\t\t\tandroid:id=&quot;@+id\/loadingBar2&quot;\n\t\t\tandroid:layout_weight=&quot;1&quot;\n\t\t\tandroid:progress=&quot;50&quot;\n\t\t\tandroid:minHeight=&quot;0dp&quot;\n\t\t\tandroid:layout_marginTop=&quot;-7dp&quot; \/&gt;\n\t&lt;\/LinearLayout&gt;\n&lt;\/FrameLayout&gt;\n<\/pre>\n<p>For exactly the same reason as with the Action bar, we use a <code>FrameLayout<\/code> to overlay the two progress bars on top of the main content so that making them appear and disappear doesn\u2019t cause an unnecessary relayout.<\/p>\n<p>Using normal <code>ProgressBar<\/code> controls introduces a slight visual defect&mdash;by default, they have a background color that makes them look like this:<\/p>\n<p><img decoding=\"async\" src=\"\/wp-content\/uploads\/sites\/44\/2019\/04\/progress-bar-with-bg.png\" alt=\"progress-bar-with-bg\" width=\"768\" height=\"76\" class=\"aligncenter size-full wp-image-5839\" \/><\/p>\n<p>It 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 <code>Drawable<\/code> 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.<\/p>\n<p>Here is the definition of this layer drawable in the framework for the default progress bar:<\/p>\n<pre class=\"lang:xml decode:true\">\n&lt;layer-list xmlns:android=&quot;http:\/\/schemas.android.com\/apk\/res\/android&quot;&gt;\n    &lt;item android:id=&quot;@android:id\/background&quot;\n          android:drawable=&quot;@android:drawable\/progress_bg_holo_dark&quot; \/&gt;\n    &lt;item android:id=&quot;@android:id\/secondaryProgress&quot;&gt;\n        &lt;scale android:scaleWidth=&quot;100%&quot;\n               android:drawable=&quot;@android:drawable\/progress_secondary_holo_dark&quot; \/&gt;\n    &lt;\/item&gt;\n    &lt;item android:id=&quot;@android:id\/progress&quot;&gt;\n        &lt;scale android:scaleWidth=&quot;100%&quot;\n               android:drawable=&quot;@android:drawable\/progress_primary_holo_dark&quot; \/&gt;\n    &lt;\/item&gt;\n&lt;\/layer-list&gt;\n<\/pre>\n<p>The layer we want to hide is the one identified with <code>android:id\/background<\/code>. It\u2019s not that straightforward, however, since <code>LayerDrawable<\/code> objects don\u2019t allow layers to be removed at runtime.<\/p>\n<p>What we can do is swap the drawable referenced by one of the layers to something else. Thanks to that approach, we can hide the background by changing its original 9-patch drawable to be a transparent color drawable like so:<\/p>\n<pre class=\"lang:csharp decode:true\">\nforeach (var p in new ProgressBar[] { bar1, bar2 }) {\n\tvar layer = p.ProgressDrawable as LayerDrawable;\n\tif (layer != null)\n\t\tlayer.SetDrawableByLayerId (Android.Resource.Id.Background,\n\t\t                            new ColorDrawable (Color.Transparent));\n}\n<\/pre>\n<h3>The Result<\/h3>\n<p>I haven\u2019t covered some of the last points of the implementation like animations and event subscription, but those should be mostly straightfoward once you read the code.<\/p>\n<p>Here is a video of the pattern implemented by us:<\/p>\n<p align=\"center\"><video width=\"289\" height=\"480\" preload=\"none\" controls=\"\" poster=\"\/wp-content\/uploads\/sites\/44\/2019\/04\/screencast-pattern.jpg\"><\/video><\/p>\n<p>And you can grab the code from GitHub:<\/p>\n<div style=\"text-align:center\">\n<p>\n        <img decoding=\"async\" src=\"https:\/\/github.com\/favicon.ico\" class=\"raw\" style=\"vertical-align: middle\"> <a href=\"https:\/\/github.com\/garuma\/SwipeDownToRefresh\" style=\"font-size: 110%\">garuma\/SwipeDownToRefresh<\/a>\n    <\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>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. Because the feature is generally considered an iOS pattern, apps utilizing it on Android are sometimes regarded as &#8220;bad ports&#8221; of their iPhone counterparts. Instead of supporting pull-to-refresh, Android [&hellip;]<\/p>\n","protected":false},"author":1925,"featured_media":39167,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[2],"tags":[4],"class_list":["post-5837","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-developers","tag-xamarin-platform"],"acf":[],"blog_post_summary":"<p>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. Because the feature is generally considered an iOS pattern, apps utilizing it on Android are sometimes regarded as &#8220;bad ports&#8221; of their iPhone counterparts. Instead of supporting pull-to-refresh, Android [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts\/5837","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/users\/1925"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/comments?post=5837"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts\/5837\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/media\/39167"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/media?parent=5837"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/categories?post=5837"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/tags?post=5837"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}