Recently, I encountered the problem of introducing the
Pull To Refresh mechanism to the project for updating lists. Due to the specificity of the available lists (lists of different lengths, from 0 to ~ 100; loading of elements on demand; a set of lists is located in a self-written component, ala
ViewPager ), this really turned out to be problematic. Read about all my research in this direction under the cut.
Pull To Refresh - a chip, as far as I know, migrated to Android from the iPhone. A convenient way to update the list.
Consider it on the example of our news application (it actually became necessary to implement this feature): there is a list of news that is updated via the news server. Manual update, so that the “Update” button sticks out below, which takes up some space on the screen. And why waste precious screen space on a button that is not so often used if you can use Pull To Refresh: at the top of the list, pull the list down and then release it to refresh the list. New news (pun) will be loaded and displayed. It looks like this:
The idea is quite successful, and therefore used in many applications, including popular Facebook and Twitter clients. So we decided to introduce such a chip into our news project.
But why write from scratch what is already in the finished form? A quick search on Google, the great and mighty StackOverFlow - and now found the most popular
android-pulltorefresh tool from Johan Nilsson. I took the latest version from GitHub and started using it. Not here it was! The project seems to be developing for almost a year, but ... this is what I see in the case of small lists:

And such a question immediately arises:
WTF? The project is a watch'at of 418 people, as many as 71 people have forged it, and here is such a
flawed incorrect behavior. Why all? Because this one here is “Tap to refresh ...” - this is the header of the ListView. And he hides, in the implementation of Johan, banal
ListView. setSelection (1) . And in the case of short lists, this
setSelection (1) politely sends to FEC does not work.
But then I notice that the project has two more branches:
enhancedpull (which was already with the main branch) and, moreover,
scrollfix_for_short_list :)
I pull out the latest version of the scrollfix_for_short_list branch, fasten it to the project: the short list looks like it looks normal, just why did I start to slow down the UI so much? And the thing is this: my list is not simple, but with loading on demand (on-demand), i.e. Sanacha shows the first 10 elements, and if you flush to the end, then the next portion is loaded to the list. And what is the fix for short lists according to Johan?
“And let's add an empty view to the footer ListView that is exactly the same height so that
setSelection (1) can hide the header again normally,” said Johan and proceeded to detracting the height of footer. To calculate its height, you need to know the total height of all the elements in the list (except for the header, of course). Then we will subtract this height from the height of the ListView and get the height for footer. And in order to find out the height of each element of the list,
from somewhere , a “ingenious” decision was made to sort through all elements using the adapter (using
getView () ) and each
measure () , i.e. essentially draw them (even if they are invisible). As a result, my list thought that everything was being squandered and squandered - and everything was loading and loading new portions until the elements were finished. And I usually have 50 items in the list, and several lists (they scroll like the recently appeared
ViewPager 'a). In general, this is the implementation of counting the total height of the list items:
private int getTotalItemHeight() { ListAdapter adapter = getAdapter(); int listviewElementsheight = 0; for(int i = 0; i < adapter.getCount(); i++) { View mView = adapter.getView(i, null, this);
It is absolutely not suitable for on-demand upload lists. And in general, you should avoid
pulling the adapter's
getView () manually. Who knows what kind of logic is there?
As a result, I went to surf the Internet in search of a more adequate tool. Here is what I found:
Guillermo, in the best OOP tradition, implemented Pull To Refresh using the
State pattern. The result was 13 classes, and the ability to do not only pull-down-to-refresh, but also pull-up-to-refresh. I don’t know why, but the implementation was not very fast: I didn’t have time to catch up with the edge of the list. And the triggering required a rather sharp downward movement to cause the header to appear. And the animation is something not observed ... Let's go further.
Tim Mahoney didn’t fool anyone, but immediately wrote to README:
“Current Status: Buggy.” No, it’s not suitable for a real project. But I looked at the graph of versions - some kind Daniel Wang forked the project and fixed the bugs. We take, fasten. Usability leaves much to be desired. If in previous implementations, it was enough to update the list upon the arrival of the
onRefresh event and then call
onRefreshComplete () :
((PullToRefreshListView) getListView()).setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { new GetDataTask().execute(); } });
private class GetDataTask extends AsyncTask { ... @Override protected void onPostExecute(Void result) { ... ((PullToRefreshListView) getListView()).onRefreshComplete(); } }
here it is necessary to manually create the header, and upon the arrival of events, change the text in it, and of course the list should be updated:
mRefreshHeader = new TextView(this); mRefreshHeader.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); mRefreshHeader.setGravity(Gravity.CENTER); mRefreshHeader.setText("Pull to refresh..."); mContainerView = (PullRefreshContainerView) findViewById(R.id.container); mContainerView.setRefreshHeader(mRefreshHeader); mContainerView.setOnChangeStateListener(new OnChangeStateListener() { @Override public void onChangeState(PullRefreshContainerView container, int state) { switch(state) { case PullRefreshContainerView.STATE_IDLE: case PullRefreshContainerView.STATE_PULL: mRefreshHeader.setText("Pull to refresh..."); break; case PullRefreshContainerView.STATE_RELEASE: mRefreshHeader.setText("Release to refresh..."); break; case PullRefreshContainerView.STATE_LOADING: mRefreshHeader.setText("Loading..."); new GetDataTask().execute();
I do not know, maybe it is more flexible, but ... uncomfortable. In addition, it turned out that this implementation does not work correctly in Pager: it became possible to scroll the list vertically and Pager horizontally at the same time.
As a result, with tears in my eyes, I began to put on crutches the version of Johan from the scrollfix_for_short_list
branch so that it would not force the lists to be loaded indefinitely. Something was done, but it worked, to put it mildly, unstable. On the horizon, the prospect of writing a component loomed itself, and I decided once again to re-search the Internet. And, lo and behold! I stumbled upon
another implementation - from Chris Baines. The project was based on the version of Johan, but has since been significantly improved (as the author writes). The test confirmed: this implementation is validly devoid of all the bugs inherent in the version of Johan, and it looks more pleasant (due to the additional animation).
So, why did I spread here so much? And then all to give
morality :
If you want to use the Pull To Refresh mechanism in your project - use the implementation from Chris Baines . In my opinion, at the moment it is the highest quality implementation of Pull To Refresh.
PS A little later I found a
similar article on Habré, which also criticizes the projects described above and promotes
its version of the Pull To Refresh implementation . But ... for some reason, when using this component, my application just started to fall: some problems with the cursor when trying to upload a new batch to the list. So this option is not for me. But the demo looks good: better than Johan.