├── .gitignore ├── README.md ├── SwipeToRefresh.sln └── SwipeToRefresh ├── Assets └── AboutAssets.txt ├── MainActivity.cs ├── OverscrollListView.cs ├── Properties └── AssemblyInfo.cs ├── Resources ├── AboutResources.txt ├── Resource.designer.cs ├── drawable │ └── Icon.png ├── layout │ └── Main.axml └── values │ ├── Strings.xml │ └── values_array.xml └── SwipeToRefresh.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.dll 3 | */obj/* 4 | *.userprefs 5 | *.apk 6 | *.mdb 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swipe Down to Refresh 2 | 3 | This is a [Xamarin.Android](http://xamarin.com/android) implementation of the swipe-down-to-refresh pattern that Google introduced with their 2013 Gmail app update. 4 | 5 | ![swipe down to refresh screenshot](https://blog.neteril.org/wp-content/uploads/swipe-to-refresh/device-swipe-down-refresh.png) 6 | 7 | The idea of the pattern is that when a 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 of the content. 8 | 9 | ## Links 10 | 11 | - [Blog post](https://blog.neteril.org/blog/2013/06/07/xamarin-android-swipe-down-to-refresh/) explaining some key parts of this implementation 12 | - [Video](https://neteril.org/xamarin/swipe-down-to-refresh-video.html) of the pattern in action 13 | 14 | ## License 15 | 16 | This code is available under the [Apache 2.0 license](http://www.apache.org/licenses/LICENSE-2.0). Reuse as much as you want. 17 | -------------------------------------------------------------------------------- /SwipeToRefresh.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwipeToRefresh", "SwipeToRefresh\SwipeToRefresh.csproj", "{A0593BA4-A941-4D7D-8782-E4E11233469D}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {A0593BA4-A941-4D7D-8782-E4E11233469D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {A0593BA4-A941-4D7D-8782-E4E11233469D}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {A0593BA4-A941-4D7D-8782-E4E11233469D}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {A0593BA4-A941-4D7D-8782-E4E11233469D}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(MonoDevelopProperties) = preSolution 18 | StartupItem = SwipeToRefresh\SwipeToRefresh.csproj 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /SwipeToRefresh/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with your package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); 20 | -------------------------------------------------------------------------------- /SwipeToRefresh/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Android.App; 3 | using Android.Content; 4 | using Android.Runtime; 5 | using Android.Views; 6 | using Android.Widget; 7 | using Android.OS; 8 | using Android.Graphics; 9 | using Android.Graphics.Drawables; 10 | using Android.Animation; 11 | using Android.Views.Animations; 12 | 13 | using Runnable = Java.Lang.Runnable; 14 | 15 | namespace SwipeToRefresh 16 | { 17 | [Activity (Label = "SwipeToRefresh", MainLauncher = true, Theme = "@android:style/Theme.Holo.Light")] 18 | public class MainActivity : Activity 19 | { 20 | LinearLayout loadingBars; 21 | ProgressBar bar1; 22 | ProgressBar bar2; 23 | TextView swipeText; 24 | 25 | bool setup = false; 26 | int accumulatedDeltaY = 0; 27 | 28 | ObjectAnimator bar1Fade, bar2Fade; 29 | 30 | protected override void OnCreate (Bundle bundle) 31 | { 32 | base.OnCreate (bundle); 33 | 34 | RequestWindowFeature (WindowFeatures.ActionBarOverlay); 35 | ActionBar.SetBackgroundDrawable (new ColorDrawable (Color.Transparent)); 36 | SetContentView (Resource.Layout.Main); 37 | 38 | var list = FindViewById (Resource.Id.listView1); 39 | loadingBars = FindViewById (Resource.Id.loadingBars); 40 | bar1 = FindViewById (Resource.Id.loadingBar1); 41 | bar2 = FindViewById (Resource.Id.loadingBar2); 42 | swipeText = FindViewById (Resource.Id.swipeToRefreshText); 43 | 44 | // Remove progress bar background 45 | foreach (var p in new[] { bar1, bar2 }) { 46 | var layer = p.ProgressDrawable as LayerDrawable; 47 | if (layer != null) 48 | layer.SetDrawableByLayerId (Android.Resource.Id.Background, 49 | new ColorDrawable (Color.Transparent)); 50 | } 51 | 52 | list.OverScrolled += deltaY => { 53 | ShowSwipeDown (); 54 | 55 | accumulatedDeltaY += -deltaY; 56 | bar1.Progress = bar2.Progress = accumulatedDeltaY; 57 | if (accumulatedDeltaY == 0) 58 | HideSwipeDown (); 59 | }; 60 | list.OverScrollCanceled += HideSwipeDown; 61 | } 62 | 63 | void ShowSwipeDown () 64 | { 65 | if (!setup) { 66 | ActionBar.Hide (); 67 | if (bar1Fade != null) { 68 | bar1Fade.Cancel (); 69 | bar1Fade = null; 70 | } 71 | if (bar2Fade != null) { 72 | bar2Fade.Cancel (); 73 | bar2Fade = null; 74 | } 75 | loadingBars.Visibility = ViewStates.Visible; 76 | swipeText.TranslationY = -(ActionBar.Height + swipeText.Height + 4); 77 | swipeText.Visibility = ViewStates.Visible; 78 | swipeText.Animate ().TranslationY (0).SetStartDelay (50).Start (); 79 | accumulatedDeltaY = 0; 80 | setup = true; 81 | } 82 | } 83 | 84 | void HideSwipeDown () 85 | { 86 | ActionBar.Show (); 87 | swipeText.Visibility = ViewStates.Invisible; 88 | bar1Fade = ObjectAnimator.OfInt (bar1, "progress", bar1.Progress, 0); 89 | bar1Fade.SetDuration (250); 90 | bar1Fade.Start (); 91 | bar2Fade = ObjectAnimator.OfInt (bar2, "progress", bar2.Progress, 0); 92 | bar2Fade.SetDuration (250); 93 | bar2Fade.Start (); 94 | bar2Fade.AnimationEnd += (sender, e) => loadingBars.Visibility = ViewStates.Gone; 95 | setup = false; 96 | } 97 | } 98 | } 99 | 100 | 101 | -------------------------------------------------------------------------------- /SwipeToRefresh/OverscrollListView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Android.App; 6 | using Android.Content; 7 | using Android.OS; 8 | using Android.Runtime; 9 | using Android.Util; 10 | using Android.Views; 11 | using Android.Widget; 12 | 13 | namespace SwipeToRefresh 14 | { 15 | public class OverscrollListView : ListView, AbsListView.IOnScrollListener 16 | { 17 | public event Action OverScrolled; 18 | public event Action OverScrollCanceled; 19 | 20 | public OverscrollListView (Context context) : 21 | base (context) 22 | { 23 | Initialize (); 24 | } 25 | 26 | public OverscrollListView (Context context, IAttributeSet attrs) : 27 | base (context, attrs) 28 | { 29 | Initialize (); 30 | } 31 | 32 | public OverscrollListView (Context context, IAttributeSet attrs, int defStyle) : 33 | base (context, attrs, defStyle) 34 | { 35 | Initialize (); 36 | } 37 | 38 | void Initialize () 39 | { 40 | SetOnScrollListener (this); 41 | } 42 | 43 | protected override bool OverScrollBy (int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, bool isTouchEvent) 44 | { 45 | if (OverScrolled != null) 46 | OverScrolled (deltaY); 47 | return base.OverScrollBy (deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); 48 | } 49 | 50 | public void OnScroll (AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) 51 | { 52 | if (OverScrollCanceled != null) 53 | OverScrollCanceled (); 54 | } 55 | 56 | public void OnScrollStateChanged (AbsListView view, ScrollState scrollState) 57 | { 58 | if (OverScrollCanceled != null 59 | && (scrollState == ScrollState.Idle || scrollState == ScrollState.Fling)) 60 | OverScrollCanceled (); 61 | } 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /SwipeToRefresh/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using Android.App; 4 | 5 | // Information about this assembly is defined by the following attributes. 6 | // Change them to the values specific to your project. 7 | 8 | [assembly: AssemblyTitle("SwipeToRefresh")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("")] 13 | [assembly: AssemblyCopyright("jeremie")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 18 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 19 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 20 | 21 | [assembly: AssemblyVersion("1.0.0")] 22 | 23 | // The following attributes are used to specify the signing key for the assembly, 24 | // if desired. See the Mono documentation for more information about signing. 25 | 26 | //[assembly: AssemblyDelaySign(false)] 27 | //[assembly: AssemblyKeyFile("")] 28 | 29 | -------------------------------------------------------------------------------- /SwipeToRefresh/Resources/AboutResources.txt: -------------------------------------------------------------------------------- 1 | Images, layout descriptions, binary blobs and string dictionaries can be included 2 | in your application as resource files. Various Android APIs are designed to 3 | operate on the resource IDs instead of dealing with images, strings or binary blobs 4 | directly. 5 | 6 | For example, a sample Android app that contains a user interface layout (main.axml), 7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 8 | would keep its resources in the "Resources" directory of the application: 9 | 10 | Resources/ 11 | drawable/ 12 | icon.png 13 | 14 | layout/ 15 | main.axml 16 | 17 | values/ 18 | strings.xml 19 | 20 | In order to get the build system to recognize Android resources, set the build action to 21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but 22 | instead operate on resource IDs. When you compile an Android application that uses resources, 23 | the build system will package the resources for distribution and generate a class called "R" 24 | (this is an Android convention) that contains the tokens for each one of the resources 25 | included. For example, for the above Resources layout, this is what the R class would expose: 26 | 27 | public class R { 28 | public class drawable { 29 | public const int icon = 0x123; 30 | } 31 | 32 | public class layout { 33 | public const int main = 0x456; 34 | } 35 | 36 | public class strings { 37 | public const int first_string = 0xabc; 38 | public const int second_string = 0xbcd; 39 | } 40 | } 41 | 42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main 43 | to reference the layout/main.axml file, or R.strings.first_string to reference the first 44 | string in the dictionary file values/strings.xml. 45 | -------------------------------------------------------------------------------- /SwipeToRefresh/Resources/Resource.designer.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 1591 2 | // ------------------------------------------------------------------------------ 3 | // 4 | // This code was generated by a tool. 5 | // Mono Runtime Version: 4.0.30319.17020 6 | // 7 | // Changes to this file may cause incorrect behavior and will be lost if 8 | // the code is regenerated. 9 | // 10 | // ------------------------------------------------------------------------------ 11 | 12 | [assembly: Android.Runtime.ResourceDesignerAttribute("SwipeToRefresh.Resource", IsApplication=true)] 13 | 14 | namespace SwipeToRefresh 15 | { 16 | 17 | 18 | [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] 19 | public partial class Resource 20 | { 21 | 22 | static Resource() 23 | { 24 | global::Android.Runtime.ResourceIdManager.UpdateIdValues(); 25 | } 26 | 27 | public static void UpdateIdValues() 28 | { 29 | } 30 | 31 | public partial class Array 32 | { 33 | 34 | // aapt resource value: 0x7f050000 35 | public const int planets_array = 2131034112; 36 | 37 | static Array() 38 | { 39 | global::Android.Runtime.ResourceIdManager.UpdateIdValues(); 40 | } 41 | 42 | private Array() 43 | { 44 | } 45 | } 46 | 47 | public partial class Attribute 48 | { 49 | 50 | static Attribute() 51 | { 52 | global::Android.Runtime.ResourceIdManager.UpdateIdValues(); 53 | } 54 | 55 | private Attribute() 56 | { 57 | } 58 | } 59 | 60 | public partial class Drawable 61 | { 62 | 63 | // aapt resource value: 0x7f020000 64 | public const int Icon = 2130837504; 65 | 66 | static Drawable() 67 | { 68 | global::Android.Runtime.ResourceIdManager.UpdateIdValues(); 69 | } 70 | 71 | private Drawable() 72 | { 73 | } 74 | } 75 | 76 | public partial class Id 77 | { 78 | 79 | // aapt resource value: 0x7f060000 80 | public const int fakeActionBar = 2131099648; 81 | 82 | // aapt resource value: 0x7f060002 83 | public const int listView1 = 2131099650; 84 | 85 | // aapt resource value: 0x7f060004 86 | public const int loadingBar1 = 2131099652; 87 | 88 | // aapt resource value: 0x7f060005 89 | public const int loadingBar2 = 2131099653; 90 | 91 | // aapt resource value: 0x7f060003 92 | public const int loadingBars = 2131099651; 93 | 94 | // aapt resource value: 0x7f060001 95 | public const int swipeToRefreshText = 2131099649; 96 | 97 | static Id() 98 | { 99 | global::Android.Runtime.ResourceIdManager.UpdateIdValues(); 100 | } 101 | 102 | private Id() 103 | { 104 | } 105 | } 106 | 107 | public partial class Layout 108 | { 109 | 110 | // aapt resource value: 0x7f030000 111 | public const int Main = 2130903040; 112 | 113 | static Layout() 114 | { 115 | global::Android.Runtime.ResourceIdManager.UpdateIdValues(); 116 | } 117 | 118 | private Layout() 119 | { 120 | } 121 | } 122 | 123 | public partial class String 124 | { 125 | 126 | // aapt resource value: 0x7f040001 127 | public const int app_name = 2130968577; 128 | 129 | // aapt resource value: 0x7f040000 130 | public const int hello = 2130968576; 131 | 132 | static String() 133 | { 134 | global::Android.Runtime.ResourceIdManager.UpdateIdValues(); 135 | } 136 | 137 | private String() 138 | { 139 | } 140 | } 141 | } 142 | } 143 | #pragma warning restore 1591 144 | -------------------------------------------------------------------------------- /SwipeToRefresh/Resources/drawable/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garuma/SwipeDownToRefresh/56ddd95972ca4b0fd674d6f144f42e87a7eafd26/SwipeToRefresh/Resources/drawable/Icon.png -------------------------------------------------------------------------------- /SwipeToRefresh/Resources/layout/Main.axml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 15 | 24 | 25 | 28 | 35 | 42 | 52 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /SwipeToRefresh/Resources/values/Strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World, Click Me! 4 | SwipeToRefresh 5 | 6 | -------------------------------------------------------------------------------- /SwipeToRefresh/Resources/values/values_array.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mercury 5 | Venus 6 | Earth 7 | Mars 8 | Saturn 9 | Pluto 10 | Uranus 11 | Tito 12 | Moon 13 | Jupiter 14 | Ceres 15 | Titan 16 | Oberon 17 | Umbriel 18 | Enceladus 19 | Callisto 20 | Europa 21 | 22 | -------------------------------------------------------------------------------- /SwipeToRefresh/SwipeToRefresh.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 10.0.0 7 | 2.0 8 | {A0593BA4-A941-4D7D-8782-E4E11233469D} 9 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10 | Library 11 | SwipeToRefresh 12 | True 13 | Resources\Resource.designer.cs 14 | Resource 15 | Resources 16 | Assets 17 | SwipeToRefresh 18 | v4.1 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug 25 | DEBUG; 26 | prompt 27 | 4 28 | None 29 | false 30 | 31 | 32 | full 33 | true 34 | bin\Release 35 | prompt 36 | 4 37 | false 38 | false 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | --------------------------------------------------------------------------------