Android development blog
Tutorials about Android dev topics

Android SwipeRefreshLayout Tutorial

Topics covered

Android SwipeRefreshLayout

Refresh ListView

Reresh UI

In this post, we will describe SwipeRefreshLayout component. This component should be used whenever the user can refresh the UI using swipe gesture. In a previous post, we talked about another method to refresh the UI that we called shake to refresh, where the user shakes his smartphone and using accelerometer sensor the app refresh the UI. We talked about a custom implementation of this refresh pattern in this post where we implemented some like it.
The SwipeRefreshLayout component is a standard implementation provided by the SDK and it is already used in some App provided by Android (i.e Gmail).

Introduction

This component accepts only one child: the one we want to refresh. It uses a listener mechanism to inform the listener that holds this component that a refresh event occurred, so in other word our Activity, for example, has to implement an interface to be notified. The Activity is responsible to handle the refresh event and refreshing the corresponding View. If the listener, once it receives the event, determines that the refresh process should take place and wants to show a “refresh animation”, it has to call setRefrshing(true), otherwise it can cancel the animation calling setRefreshing(false).

How to use SwipeRefreshLayout

Now we know how this component works, we will create a simple example to show how to use it. Let us suppose we want to generate a random number as the user uses a vertical swipe gesture:
android_swipetorefreshlayout



Usually this component is the root component:
<android.support.v4.widget.SwipeRefreshLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:id="@+id/swipe">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Random number:"
                android:id="@+id/lbl"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/rndNum"
                android:layout_toRightOf="@id/lbl"/>


            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/lbl"
                android:layout_centerHorizontal="true"
                android:layout_marginTop="20dp"
                android:text="Swipe to Refresh"
                style="@android:style/TextAppearance.Medium"/>



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

As you can see from the layout above, SwipeRefreshLayout has only one child. Now we can code our Activity:
...    
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final SwipeRefreshLayout swipeView = (SwipeRefreshLayout) findViewById(R.id.swipe);
        final TextView rndNum = (TextView) findViewById(R.id.rndNum);
        swipeView.setColorScheme(android.R.color.holo_blue_dark, android.R.color.holo_blue_light, android.R.color.holo_green_light, android.R.color.holo_green_light);
        swipeView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                swipeView.setRefreshing(true);
                Log.d("Swipe", "Refreshing Number");
                ( new Handler()).postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        swipeView.setRefreshing(false);
                        double f = Math.random();
                        rndNum.setText(String.valueOf(f));
                    }
                }, 3000);
            }
        });
    }
....

As you can see everything in our example happens in onCreate method. At line 6 we get the reference to the SwipeRefreshLayout so that we can set the listener (line 10,11,12). In the listener we simply can setRefreshing(true) to enable to refreshing animation and then we generate our random number. At the end (we emulate a quite long process) we stop the animation.

SwipeRefreshLayout with ListView

Another interesting example is how to use SwipeRefreshLayout with a ListView. This is an interesting example because in real app we have often this situation where we have a ListView holding some items and we want to refresh them. If the ListView is the only one child of the SwipeRefreshLayout we wouldn’t have any kind of problems, because everything works smoothly. In some cases we have not only the ListView but we have other elements, let us suppose we have an UI like this:

android_swipetorefreshlayout_listview

This case is a little bit more complex, because if we scroll up the items in the ListView everything works as expected, but if the scroll down the refresh process starts and the list items doesn’t scroll as we want. In this case we can use a trick, we can disable the refresh notification using setEnabled(false) and enable it again as soon as the first item in the ListView is visible:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final SwipeRefreshLayout swipeView = (SwipeRefreshLayout) findViewById(R.id.swipe);

    swipeView.setEnabled(false);
    ListView lView = (ListView) findViewById(R.id.list);
    ArrayAdapter<String> adp = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, createItems(40,0 ));
    lView.setAdapter(adp);

    swipeView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            swipeView.setRefreshing(true);
            ( new Handler()).postDelayed(new Runnable() {
                @Override
                public void run() {
                    swipeView.setRefreshing(false);

                }
            }, 3000);
        }
    });

    lView.setOnScrollListener(new AbsListView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(AbsListView absListView, int i) {

        }

        @Override
        public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                if (firstVisibleItem == 0)
                    swipeView.setEnabled(true);
                else
                    swipeView.setEnabled(false);
        }
    });
}

As you can see at line 33, we override the onScrollListener of the ListView to handle the enable/disable mechanism.

5 comments:

  1. Hello hope you are having a good day, I know this is a bit off topic but i was wondering if you remake the upload to server app and put all of the source code in one github directory even the server side and explain how to optimize the app for a localhost server.

    ReplyDelete
  2. survivingwithandroidMay 20, 2014 at 4:30 PM

    It could be a nice idea. I will provide a post about it. Thx for your suggestion

    ReplyDelete
  3. Instead of adding an onScrollListener, you should subclass the SwipeRefreshLayout class and Override the canChildScrollUp method.

    ReplyDelete
  4. Guillaume BourderyeJuly 29, 2014 at 12:58 PM

    Hi !

    I think you should add these 2 conditions in your if:

    @Override

    public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

    if (firstVisibleItem == 0 && visibleItemCount > 0 && getListAdapter().getView(0, null, null).getTop() >= 0)
    swipeView.setEnabled(true);

    else
    swipeView.setEnabled(false);

    }

    It allows to call the refresh method only when you are at the top of your listView.

    ReplyDelete

Related Posts with Thumbnails