455

In Android applications such as Twitter (official app), when you encounter a ListView, you can pull it down (and it will bounce back when released) to refresh the content.

I wonder what is the best way, in your opinion, to implement that?

Some possibilities I could think of:

  1. An item on top of the ListView - however I don't think scrolling back to item position 1 (0-based) with animation on the ListView is an easy task.
  2. Another view outside the ListView - but I need to take care of moving the ListView position down when it is pulled, and I'm not sure if we can detect if the drag-touches to the ListView still really scroll the items on the ListView.

Any recommendations?

P.S. I wonder when the official Twitter app source code is released. It has been mentioned that it will be released, but 6 months has passed and we haven't heard about it since then.

Michael Celey
  • 12,087
  • 6
  • 53
  • 60
Randy Sugianto 'Yuku'
  • 64,635
  • 54
  • 168
  • 216
  • Is this functionality can be implemented in a dynamically created TableLayout. Please Help..... – Arun Badole Dec 28 '11 at 13:53
  • https://github.com/fruitranger/PulltorefreshListView is a another implementation of mine. Smooth and multi-touch support. – Changwei Yao Jul 16 '12 at 15:03
  • 6
    Related: [“Pull-to-refresh”: an anti UI pattern on Android](http://android.cyrilmottier.com/?p=598) is an article arguing that this UI *isn't* something that should be used on Android and sparked a lot of discussion about its appropriateness. – blahdiblah Jan 30 '13 at 20:51
  • 32
    Somebody should let Google know because the latest version of Gmail for Android uses this "anti pattern". – Nick Jul 06 '13 at 20:28
  • The pull to refresh used in Gmail does not behave the same way as the one in Twitter etc. And I think Google added it there for people who are used to the pull to refresh pattern and expect to see it. While using it, at the same time Google has added a Refresh button in the action bar, this way conforming to the Android guidelines. Google goes the same way about other apps: Google+, Google Play Newsstand, possibly others as well, so it is consistent. Check this http://www.androiduipatterns.com/2013/06/googles-first-pull-to-refresh-good.html – Stan Feb 27 '14 at 13:47
  • 4
    Pull to refresh is (and has been for a while) a standard adopted pattern in iOS and Android and it's very natural, so this anti-pattern discussion is outdated. Users will expect to see it and behave as such. – Martin Marconcini Feb 28 '14 at 01:26

16 Answers16

344

Finally, Google released an official version of the pull-to-refresh library!

It is called SwipeRefreshLayout, inside the support library, and the documentation is here:

  1. Add SwipeRefreshLayout as a parent of view which will be treated as a pull to refresh the layout. (I took ListView as an example, it can be any View like LinearLayout, ScrollView etc.)

     <android.support.v4.widget.SwipeRefreshLayout
         android:id="@+id/pullToRefresh"
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
         <ListView
             android:id="@+id/listView"
             android:layout_width="match_parent"
             android:layout_height="match_parent"/>
     </android.support.v4.widget.SwipeRefreshLayout>
    
  2. Add a listener to your class

     protected void onCreate(Bundle savedInstanceState) {
         final SwipeRefreshLayout pullToRefresh = findViewById(R.id.pullToRefresh);
         pullToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
             @Override
             public void onRefresh() {
                 refreshData(); // your code
                 pullToRefresh.setRefreshing(false);
             }
         });
     }
    

You can also call pullToRefresh.setRefreshing(true/false); as per your requirement.

UPDATE

Android support libraries have been deprecated and have been replaced by AndroidX. The link to the new library can be found here.

Also, you need to add the following dependency to your project:

implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'

OR

You can go to Refactor>>Migrate to AndroidX and Android Studio will handle the dependencies for you.

MSpeed
  • 7,579
  • 6
  • 42
  • 54
Randy Sugianto 'Yuku'
  • 64,635
  • 54
  • 168
  • 216
80

I've made an attempt to implement a pull to refresh component, it's far from complete but demonstrates a possible implementation, https://github.com/johannilsson/android-pulltorefresh.

Main logic is implemented in PullToRefreshListView that extends ListView. Internally it controls the scrolling of a header view using smoothScrollBy (API Level 8). The widget is now updated with support for 1.5 and later, please read the README for 1.5 support though.

In your layouts you simply add it like this.

<com.markupartist.android.widget.PullToRefreshListView
    android:id="@+id/android:list"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent"
    />
Johan Berg Nilsson
  • 1,574
  • 1
  • 11
  • 10
  • 3
    Hi johan, I have downloaded your sample code. It is worked in android 2.2 device but not worked in 2.1 device. I think because it used **smoothScrollBy** method which only available in 2.2 or later, is correct? And do you have any idea to implement this effect into 2.1 or earlier version? Thanks –  Jan 16 '11 at 15:18
  • Yes that's correct as I stated in the answer smoothScrollBy was introduces in API Level 8 (2.2). I haven't figured out a proper way to implement it for other versions yet, but it guess it should be possible to port the implementation of smoothScrollBy but I guess that discussion should be kept at the project site and not on stack overflow? – Johan Berg Nilsson Jan 16 '11 at 18:09
  • Is it on purpose that the id is @+id/android:list and not @android:id/list, it looks weird? The project throws an inflation error here on my side, I'm currently checking on that... – Mathias Conradt May 17 '11 at 09:28
  • No it works fine with just `@id/android:list` too, it behaves just as a normal `ListView` would do. Errors like that is most likely because it's not set up as a library project. – Johan Berg Nilsson May 17 '11 at 14:51
  • Hi Johan, I have downloaded your sample code from above given link. But it doesn't have your library/jar to get "com.markupartist.android.widget.PullToRefreshListView, can you just give link for your lib also. – amsiddh Oct 01 '11 at 13:11
  • @amsiddh Only source is provided. Android library projects does not support jars, more details on how this works (and how this project is built) can be found [here](http://developer.android.com/guide/developing/projects/index.html#LibraryProjects). – Johan Berg Nilsson Oct 02 '11 at 15:05
  • In addition to the great work made by @johan, I add a link to a post that clarify how to use and style its PullToRefreshListView: http://www.recursiveawesome.com/blog/2011/04/29/implementing-pull-to-refresh-in-your-android-app/ – Rainbowbreeze Oct 25 '11 at 16:37
  • 12
    This is the best library for pull to refresh I have found..https://github.com/chrisbanes/Android-PullToRefresh. It works with ListViews, GridViews and Webviews. Also has Pull up to refresh pattern implemented. – rOrlig Apr 01 '12 at 23:07
  • Great, I have download the two projects and integrate them into one(I don't like the example-lib mode). It works well. The scroll state of listview can follow the gestures, thanks a lot – jowett May 10 '12 at 03:57
  • 1
    How do I add the project into library folder while working on Intellij Idea? Can anybody help me? – Mustafa Güven Jan 14 '13 at 12:35
  • Hell0 @JohanNilsson I implement your code for pull to refresh in my application, But one major issue I faced,When I select any row's item it's give the rong position,Like I select 5 row it's give me 6 the position, So my selection is not working properly ,So please if have any idea about this please post here. – Zala Janaksinh Jun 18 '14 at 10:42
  • Hello @JohanNilsson When all data of list is visible at the time, then pull to refresh view doesn't hides completely. Can you suggest a fix for this? – seema Sep 22 '14 at 06:53
55

I've also implemented a robust, open source, easy to use and highly customizable PullToRefresh library for Android. You can replace your ListView with the PullToRefreshListView as described in the documentation on the project page.

https://github.com/erikwt/PullToRefresh-ListView

Erik
  • 5,611
  • 8
  • 26
  • 32
  • I tried all of the implementations listed here and yours is the best, in terms of a simple/pure/smooth pull to refresh implementation (no tap to refresh weirdness like Johan's). Thanks! – Gady Mar 16 '12 at 16:13
  • 1
    Can this replace a standard ListView to work with ListActivity, ListFragment and so on? – davidcesarino Apr 08 '12 at 02:00
  • Yes, just give the PullToRefreshListView the right id. In XML: android:id="@android:id/list" – Erik Apr 08 '12 at 11:21
  • 1
    this is absolutely awesome! google brought me here, and looking through the examples, your's looks the easiest to implement. bravo and thank you sir! – Evan R. Aug 23 '12 at 07:08
  • Very sensibly designed component, nice and simple. This should definitely be the accepted answer. – Adam Dec 03 '12 at 04:17
  • @Erik, would you please tell, how can I change pull functionality to the bottom of `ListView`? – azizbekian Dec 05 '12 at 15:54
  • This customization is great. Only drawback is my listview item is getting clicked when I try to refresh by pulling :( – StarDust Feb 19 '13 at 10:11
  • Hi @Erik I'm testing your library and I have found a details on it. If there is another component(TextView, Button, etc) above the ListView, when you pull it down and release it the header view goes over your first component. This is what I'm talking about http://tinypic.com/r/72t5x1/5 Is there a way to fix this? – Fernando Velazquez Jun 02 '13 at 17:46
  • Hi Nando. Yes, the current workaround would be to wrap the ListView in a FrameLayout. I'm working on a more elegant fix. Thanks for your feedback. Erik – Erik Jun 03 '13 at 11:37
  • It looks very good, but is it possible to use it replacing a ExpandableListView instead of a regular ListView? – AlvaroSantisteban Jul 31 '13 at 08:30
  • @Erik I was having troubles using your library, could you help me at http://stackoverflow.com/questions/19820329/why-does-pull-to-refresh-fail-once-you-have-navigated-to-the-bottom-of-the-listv – Akshat Agarwal Nov 06 '13 at 19:02
  • @Erik I found your library quite useful and simple.But I have problem in that ,I am not able to get the item click event when the screen is refreshing ,Any sujestion? – Abx Nov 21 '13 at 05:48
  • Just a heads up if you use this with isitemchecked or setitemchecked - it appears index 0 of your listview will be reserved for the "refresh" area, so your first item will be index 1. – Adam Johns Apr 17 '14 at 15:26
42

The easiest way i think is as provided by the android support library:

android.support.v4.widget.SwipeRefreshLayout;

once that is imported then you can have your layout defined as follows:

  <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/refresh"
        android:layout_height="match_parent"
        android:layout_width="match_parent">
    <android.support.v7.widget.RecyclerView
        xmlns:recycler_view="http://schemas.android.com/apk/res-auto"
        android:id="@android:id/list"
        android:theme="@style/Theme.AppCompat.Light"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/button_material_light"
        >

    </android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>

I assume that you use recycler view instead of listview. However, listview still works so you just need to replace recyclerview with listview and update the references in the java code (Fragment).

In your activity fragment, you first implement the interface, SwipeRefreshLayout.OnRefreshListener: i,e

public class MySwipeFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener{
private SwipeRefreshLayout swipeRefreshLayout;

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_item, container, false);
        swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh);
        swipeRefreshLayout.setOnRefreshListener(this);
}


 @Override
  public void onRefresh(){
     swipeRefreshLayout.setRefreshing(true);
     refreshList();
  }
  refreshList(){
    //do processing to get new data and set your listview's adapter, maybe  reinitialise the loaders you may be using or so
   //when your data has finished loading, cset the refresh state of the view to false
   swipeRefreshLayout.setRefreshing(false);

   }
}

Hope this helps the masses

Akah
  • 1,160
  • 12
  • 17
  • It works well. Though, one thing regarding `setRefreshing`: "Do not call this when refresh is triggered by a swipe gesture" as described in http://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html#setRefreshing%28boolean%29 – gabriel14 Nov 10 '15 at 08:30
  • Good job! Thanks. – technik Apr 02 '17 at 22:05
23

In this link, you can find a fork of the famous PullToRefresh view that has new interesting implementations like PullTorRefreshWebView or PullToRefreshGridView or the possibility to add a PullToRefresh on the bottom edge of a list.

https://github.com/chrisbanes/Android-PullToRefresh

And the best of it is that work perfect in Android 4.1 (the normal PullToRefresh doesn't work )

Aracem
  • 6,928
  • 3
  • 35
  • 71
  • 3
    A big comment at the top of the readme indicates: THIS PROJECT IS NO LONGER BEING MAINTAINED. Anyway, this lib is the best one I have seen so far! Great work! – Milton May 09 '13 at 13:10
  • 3
    Just because it's no longer being maintained, doesn't mean it's not good. Still use it for all my pull to refresh needs :) – Muz Jun 02 '13 at 07:17
19

To implement android Pull-to-Refresh try this piece of code,

<android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/pullToRefresh"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </ListView>

</android.support.v4.widget.SwipeRefreshLayout>

Activity class:

ListView lv = (ListView) findViewById(R.id.lv);
SwipeRefreshLayout pullToRefresh = (SwipeRefreshLayout) findViewById(R.id.pullToRefresh);


lv.setAdapter(mAdapter);

pullToRefresh.setOnRefreshListener(new OnRefreshListener() {

        @Override
        public void onRefresh() {
            // TODO Auto-generated method stub

            refreshContent();

        }
    });



private void refreshContent(){ 

     new Handler().postDelayed(new Runnable() {
            @Override public void run() {
                pullToRefresh.setRefreshing(false);
            }
        }, 5000);

 }
mani
  • 2,946
  • 2
  • 27
  • 33
18

I have very easy way to do this but now sure its the foolproof way There is my code PullDownListView.java

package com.myproject.widgets;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;

/**
 * @author Pushpan
 * @date Nov 27, 2012
 **/
public class PullDownListView extends ListView implements OnScrollListener {

    private ListViewTouchEventListener mTouchListener;
    private boolean pulledDown;

    public PullDownListView(Context context) {
        super(context);
        init();
    }

    public PullDownListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PullDownListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        setOnScrollListener(this);
    }

    private float lastY;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            lastY = ev.getRawY();
        } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            float newY = ev.getRawY();
            setPulledDown((newY - lastY) > 0);
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (isPulledDown()) {
                        if (mTouchListener != null) {
                            mTouchListener.onListViewPulledDown();
                            setPulledDown(false);
                        }
                    }
                }
            }, 400);
            lastY = newY;
        } else if (ev.getAction() == MotionEvent.ACTION_UP) {
            lastY = 0;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        setPulledDown(false);
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
    }

    public interface ListViewTouchEventListener {
        public void onListViewPulledDown();
    }

    public void setListViewTouchListener(
            ListViewTouchEventListener touchListener) {
        this.mTouchListener = touchListener;
    }

    public ListViewTouchEventListener getListViewTouchListener() {
        return mTouchListener;
    }

    public boolean isPulledDown() {
        return pulledDown;
    }

    public void setPulledDown(boolean pulledDown) {
        this.pulledDown = pulledDown;
    }
}

You just need to implement ListViewTouchEventListener on your activity where you want to use this ListView and set the listener

I have it implemented in PullDownListViewActivity

package com.myproject.activities;

import android.app.Activity;
import android.os.Bundle;

/**
 * @author Pushpan
 *
 */
public class PullDownListViewActivity extends Activity implements ListViewTouchEventListener {

    private PullDownListView listView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        listView = new PullDownListView(this);
        setContentView(listView);
        listView.setListViewTouchListener(this);

        //setItems in listview
    }

    public void onListViewPulledDown(){
        Log.("PullDownListViewActivity", "ListView pulled down");
    }
}

It works for me :)

Pushpan
  • 277
  • 3
  • 3
9

Nobody have mention the new type of "Pull to refresh" which shows on top of the action bar like in the Google Now or Gmail application.

There is a library ActionBar-PullToRefresh which works exactly the same.

tomrozb
  • 23,522
  • 30
  • 89
  • 110
7

Note there are UX issues to contend with when implementing on Android and WP.

"A great indicator for why designers/devs should not implement pull-to-refresh in the style iOS apps do is how Google and their teams never use pull-to-refresh on Android while they do use it in iOS. "

https://plus.google.com/109453683460749241197/posts/eqYxXR8L4eb

QED
  • 9,439
  • 6
  • 46
  • 82
uobroin
  • 181
  • 1
  • 5
  • 3
    I know this is over a year old but the Gmail app does use pull-to-refresh. However it's not exactly the same in terms of the UI, the list does not scroll down, rather a new view shows at the top of the screen. – ashishduh Dec 10 '13 at 17:35
4

If you don't want your program to look like an iPhone program that is force fitted into Android, aim for a more native look and feel and do something similar to Gingerbread:

alt text

Lie Ryan
  • 55,117
  • 12
  • 87
  • 139
  • 2
    @Rob: I meant having the orange shadow on the top of the list when you overpull, instead of having the list bounce back like on iPhone. This is meant as a comment (not answer), but comments can't have images. – Lie Ryan Feb 26 '11 at 12:42
  • Ahh, sorry, great Idea. I haven't played with gingerbread yet, so hadn't seen the effect. Still waiting for google to roll it to the nexus one. – Rob Mar 01 '11 at 12:00
  • 9
    I have Gingerbread and the orange glow works great when a list is static. But the pull-down-refresh is a great UI mecanism to refresh a dynamic list. Although it is prevalent in iOS world, it's a UI trick that doesn't feel wierd in Android ecosystem. I strongly suggest you check it out in the official Twitter app. :) – Thierry-Dimitri Roy Apr 18 '11 at 15:19
  • The native behaviour here is to indicate that you have reached the end of a list. Taking this, and performing a refresh is identically non native as you are not doing what the platform does. This means you are more likely to surprise the user. I certainly wouldn't expect nor want a refresh just because I got to the end of a list. Pull down to refresh is a lot more intuitive and clear. And as the native twitter app uses it I think its fair to say it is a UI concept a large amount of people are familiar with. – steprobe Apr 20 '12 at 17:58
4

I've written a pull to refresh component here: https://github.com/guillep/PullToRefresh It works event if the list does not have items, and I've tested it on >=1.6 android phones.

Any suggestion or improvement is appreciated :)

Guille Polito
  • 854
  • 7
  • 3
2

I think the best library is : https://github.com/chrisbanes/Android-PullToRefresh.

Works with:

ListView
ExpandableListView
GridView
WebView
ScrollView
HorizontalScrollView
ViewPager
Jossy Paul
  • 1,127
  • 13
  • 23
2

We should first know what is Pull to refresh layout in android . we can call pull to refresh in android as swipe-to-refresh. when you swipe screen from top to bottom it will do some action based on setOnRefreshListener.

Here's tutorial that demonstrate about how to implement android pull to refresh. I hope this helps.

Hasan Raza
  • 31
  • 2
1

To get the latest Lollipop Pull-To Refresh:

  1. Download the latest Lollipop SDK and Extras/Android support library
  2. Set Project's Build Target to Android 5.0 (otherwise support package can have errors with resources)
  3. Update your libs/android-support-v4.jar to 21st version
  4. Use android.support.v4.widget.SwipeRefreshLayout plus android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener

Detailed guide could be found here: http://antonioleiva.com/swiperefreshlayout/

Plus for ListView I recommend to read about canChildScrollUp() in the comments ;)

goRGon
  • 4,104
  • 1
  • 41
  • 43
1

Very interesting Pull-to-Refresh by Yalantis. Gif for iOS, but you can check it :)

<com.yalantis.pulltorefresh.library.PullToRefreshView
android:id="@+id/pull_to_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
    android:id="@+id/list_view"
    android:divider="@null"
    android:dividerHeight="0dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

edbaev
  • 167
  • 1
  • 9
0

Those who are looking to implement pull to refresh functionality for RecyclerView can following my simple tutorial How to implement Pull To Refresh for RecyclerView in Android.

Libraries To Import

implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.gridlayout:gridlayout:1.0.0'

XML Code

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android:id="@+id/swipe_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

Activity JAVA Code

import androidx.appcompat.app.AppCompatActivity;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.os.Bundle;
import android.os.Handler;

public class MainActivity extends AppCompatActivity {

private SwipeRefreshLayout swipeRefreshLayout;

...

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        ...
        swipeRefreshLayout = findViewById(R.id.swipe_layout);
        initializeRefreshListener();
}

    void initializeRefreshListener() {

        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                // This method gets called when user pull for refresh,
                // You can make your API call here,
                // We are using adding a delay for the moment
                final Handler handler = new Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if(swipeRefreshLayout.isRefreshing()) {
                            swipeRefreshLayout.setRefreshing(false);
                        }
                    }
                }, 3000);
            }
        });
    }
Asad Ali Choudhry
  • 3,533
  • 4
  • 18
  • 31