Reprinted please indicate the source: http://blog.csdn.net/guolin_blog/article/details/9255575
The listview pull-down refresh function is required in recent projects. To save time, you can find a ready-made one on the Internet, however, I tried to pull and refresh multiple versions on the Internet and found that the results were not very satisfactory. Some of them are incomplete functions or bugs, and some are because they are too complicated to use and are not perfect. Therefore, I gave up the idea of finding out the ready-made code on the Internet. I spent some time writing a very simple implementation solution for pull-down refresh. Now I will share it with you. I believe that after reading this article, you can introduce the pull-down refresh function within one minute in your project.
First, let's talk about the implementation principle. Here we will adopt the combined view method. First, we will define a layout that inherits from linearlayout, and then add the two child elements of the drop-down header and listview to the layout, and vertically arrange the two child elements. During initialization, move the drop-down header to the screen, so that we can only see the listview. Then, listen to the listview touch event. If the current listview has been scrolled to the top and the finger is still pulling down, display the drop-down header and refresh it, hide the drop-down header. The principle is as follows:
Now let's implement it. Create a project named pulltorefreshtest. First, define the layout file pull_to_refresh.xml in the project. The Code is as follows:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/pull_to_refresh_head" android:layout_width="fill_parent" android:layout_height="60dip" > <LinearLayout android:layout_width="200dip" android:layout_height="60dip" android:layout_centerInParent="true" android:orientation="horizontal" > <RelativeLayout android:layout_width="0dip" android:layout_height="60dip" android:layout_weight="3" > <ImageView android:id="@+id/arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/arrow" /> <ProgressBar android:id="@+id/progress_bar" android:layout_width="30dip" android:layout_height="30dip" android:layout_centerInParent="true" android:visibility="gone" /> </RelativeLayout> <LinearLayout android:layout_width="0dip" android:layout_height="60dip" android:layout_weight="12" android:orientation="vertical" > <TextView android:id="@+id/description" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:gravity="center_horizontal|bottom" android:text="@string/pull_to_refresh" /> <TextView android:id="@+id/updated_at" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:gravity="center_horizontal|top" android:text="@string/updated_at" /> </LinearLayout> </LinearLayout></RelativeLayout>
In this layout, we include a drop-down arrow, a drop-down status text prompt, and a time last updated. Of course, there is also a hidden rotation progress bar, which will be displayed only when refreshing.
All referenced strings in the layout are stored in strings. XML, as shown below:
<? XML version = "1.0" encoding = "UTF-8"?> <Resources> <string name = "app_name"> pulltorefreshtest </string> <string name = "pull_to_refresh"> pull-down refresh </string> <string name = "release_to_refresh"> release refresh now </string> <string name = "refreshing"> refreshing... </String> <string name = "not_updated_yet"> not updated yet </string> <string name = "updated_at"> last updated before % 1 $ S </string> <string name = "updated_just_now"> An error occurred while updating </string> <string name = "time_error"> </string> </resources>
Create a refreshableview inherited from linearlayout. The Code is as follows:
Public class refreshableview extends linearlayout implements ontouchlistener {/*** drop-down status */public static final int status_pull_to_refresh = 0; /*** release immediate refresh status */public static final int status_release_to_refresh = 1;/*** refreshing status */public static final int status_refreshing = 2; /*** refresh completed or not refreshed */public static final int status_refresh_finished = 3;/*** speed of pulling-down header rollback */public static final int scroll_speed = -20;/*** millisecond value for one minute, used to determine the last update time */public static final long one_minute = 60*1000;/*** the millisecond value for one hour, used to determine the last update time */public static final long one_hour = 60 * one_minute;/*** the millisecond value of a day, used to determine the last update time */public static final long one_day = 24 * one_hour;/*** the millisecond value of January 1, January, used to determine the last update time */public static final long one_month = 30 * one_day;/*** the millisecond value of one year, used to determine the last update time */public static final long one_year = 12 * one_month ;/*** The String constant of the update time, used as the key value of sharedpreferences */Private Static final string updated_at = "updated_at";/*** pull-down refresh callback interface */private pulltorefreshlistener mlistener; /*** used to store the last update time */private sharedpreferences preferences;/*** drop-down header view */private view header; /* ** the listview to be refreshed from the drop-down list */private listview;/* the progress bar displayed during refresh */private progressbar; /* ** indicates the drop-down and Release Arrows */private imageview arrow;/* ** indicates the drop-down and release Text description */private textview description;/*** text description of the last update time */private textview updateat; /* ** layout parameters of the drop-down header */private marginlayoutparams headerlayoutparams;/* millisecond value of the last update time */private long lastupdatetime; /*** to prevent the pull-down refresh of different interfaces from conflicting with each other during the last update time, use the ID to differentiate */private int mid =-1; /*** height of the drop-down header */private int hideheaderheight;/*** status of the current processing. The optional values include status_pull_to_refresh, status_release_to_refresh, * status_refreshing, and statu. S_refresh_finished */private int currentstatus = status_refresh_finished;/*** record the last status to avoid repeated operations */private int laststatus = currentstatus; /*** screen ordinate when the finger is pressed */private float ydown;/*** the maximum value that the finger can move before being determined to scroll. */Private int touchslop;/*** whether layout has been loaded once. Here, initialization in onlayout only needs to be loaded once */private Boolean loadonce;/*** whether the current pull-down is allowed, the drop-down */private Boolean abletopull is allowed only when the listview is rolled to the header./*** the drop-down refresh control constructor dynamically adds a drop-down layout during running. ** @ Param context * @ Param attrs */Public refreshableview (context, attributeset attrs) {super (context, attrs); preferences = preferencemanager. getdefasharsharedpreferences (context); header = layoutinflater. from (context ). inflate (R. layout. pull_to_refresh, null, true); progressbar = (progressbar) header. findviewbyid (R. id. progress_bar); Arrow = (imageview) header. findviewbyid (R. id. arrow); descripti On = (textview) header. findviewbyid (R. id. description); updateat = (textview) header. findviewbyid (R. id. updated_at); touchslop = viewconfiguration. get (context ). getscaledtouchslop (); refreshupdatedatvalue (); setorientation (vertical); addview (header, 0);}/*** performs some key initialization operations, such: hide the drop-down header from the up offset and register the touch event for listview. * // @ Overrideprotected void onlayout (Boolean changed, int L, int T, int R, int B) {super. onlayout (changed, L, t, R, B); If (changed &&! Loadonce) {hideheaderheight =-header. getheight (); headerlayoutparams = (marginlayoutparams) header. getlayoutparams (); headerlayoutparams. topmargin = hideheaderheight; listview = (listview) getchildat (1); listview. setontouchlistener (this); loadonce = true ;}}/*** called when the listview is touched. The specific logic of various pull-down refreshes is processed. * // @ Overridepublic Boolean ontouch (view V, motionevent event) {setisabletopull (event); If (abletopull) {Switch (event. getaction () {Case motionevent. action_down: ydown = event. getrawy (); break; Case motionevent. action_move: Float ymove = event. getrawy (); int distance = (INT) (ymove-ydown); // If the finger is in a slide state and the drop-down header is completely hidden, the drop-down event if (distance <= 0 & headerlayoutparams. topmargin <= hideheaderheight) {return Fals E;} If (distance <touchslop) {return false;} If (currentstatus! = Status_refreshing) {If (headerlayoutparams. topmargin> 0) {currentstatus = status_release_to_refresh;} else {currentstatus = status_pull_to_refresh;} // implement the drop-down effect headerlayoutparams by shifting the topmargin value of the drop-down header. topmargin = (distance/2) + hideheaderheight; header. setlayoutparams (headerlayoutparams);} break; Case motionevent. action_up: Default: If (currentstatus = status_release_to_refresh) {// if the release is immediate refresh, call The refresh task new refreshingtask(cmd.exe cute ();} else if (currentstatus = status_pull_to_refresh) {// If the refresh task is in the drop-down status, call the new hideheadertask(cmd.exe cute ();} break;} // always remember to update the information in the pull-down header if (currentstatus = status_pull_to_refresh | currentstatus = running) {updateheaderview (); // The task is currently in the drop-down or release state. To make the listview out of focus, otherwise the clicked item will remain in the selected state listview. setpressed (false); listview. setfocusable (false); listview. SETF Ocusableintouchmode (false); laststatus = currentstatus; // The current event is in the drop-down or release status. Return true to block the listview rolling event return true;} return false ;} /*** register a listener for the pull-down refresh control. ** @ Param listener * listener implementation. * @ Param ID * to prevent the pull-down refresh of different interfaces from conflicting with each other during the last update, you must enter different IDs when registering the pull-down refresh listener on different interfaces. */Public void setonrefreshlistener (pulltorefreshlistener listener, int ID) {mlistener = listener; Mid = ID;}/*** when all the refresh logic is complete, record the call, otherwise, your listview will remain in the refreshing status. */Public void finishrefreshing () {currentstatus = status_refresh_finished; preferences. edit (). putlong (updated_at + mid, system. currenttimemillis ()). commit (); New hideheadertask(cmd.exe cute ();}/*** set the value of {@ link # abletopull} * based on the current listview rolling status, every time you need to execute the first one in ontouch, you can determine whether the current should be a rolling listview or a drop-down. ** @ Param event */private void setisabletopull (motionevent event) {view firstchild = listview. getchildat (0); If (firstchild! = NULL) {int firstvisiblepos = listview. getfirstvisibleposition (); If (firstvisiblepos = 0 & firstchild. gettop () = 0) {If (! Abletopull) {ydown = event. getrawy ();} // if the top edge of the first element is 0 from the parent layout value, the listview is rolled to the top. In this case, you should allow the drop-down to refresh abletopull = true ;} else {If (headerlayoutparams. topmargin! = Hideheaderheight) {headerlayoutparams. topmargin = hideheaderheight; header. setlayoutparams (headerlayoutparams);} abletopull = false ;}} else {// if there are no elements in the listview, you should also allow the drop-down refresh abletopull = true ;}} /*** update the information in the drop-down header. */Private void updateheaderview () {If (laststatus! = Currentstatus) {If (currentstatus = status_pull_to_refresh) {description. settext (getresources (). getstring (R. string. pull_to_refresh); arrow. setvisibility (view. visible); progressbar. setvisibility (view. gone); rotatearrow ();} else if (currentstatus = status_release_to_refresh) {description. settext (getresources (). getstring (R. string. release_to_refresh); arrow. setvisibility (view. visible); progressbar. set Visibility (view. gone); rotatearrow ();} else if (currentstatus = status_refreshing) {description. settext (getresources (). getstring (R. string. refreshing); progressbar. setvisibility (view. visible); arrow. clearanimation (); arrow. setvisibility (view. gone) ;}refreshupdatedatvalue () ;}/ *** rotate the arrow based on the current status. */Private void rotatearrow () {float destination Tx = arrow. getwidth ()/2f; float interval ty = arrow. getheight ()/2f; float fromdegrees = 0f; float todegrees = 0f; If (currentstatus = status_pull_to_refresh) {fromdegrees = 180f; todegrees = 360f ;} else if (currentstatus = status_release_to_refresh) {fromdegrees = 0f; todegrees = 180f;} rotateanimation animation = new rotateanimation (fromdegretx, todegrees, begin, Policty); animation. setduration (100); animation. setfillafter (true); arrow. startanimation (animation);}/*** refresh the text description of the last update time in the drop-down header. */Private void refreshupdatedatvalue () {lastupdatetime = preferences. getlong (updated_at + mid,-1); long currenttime = system. currenttimemillis (); long timepassed = currenttime-lastupdatetime; long timestamps format; string updateatvalue; If (lastupdatetime =-1) {updateatvalue = getresources (). getstring (R. string. not_updated_yet);} else if (timepassed <0) {updateatvalue = getresources (). getstring (R. st Ring. time_error);} else if (timepassed <one_minute) {updateatvalue = getresources (). getstring (R. string. updated_just_now);} else if (timepassed <one_hour) {timestamps format = timepassed/one_minute; string value = timestamps format + "Minutes"; updateatvalue = string. format (getresources (). getstring (R. string. updated_at), value);} else if (timepassed <one_day) {timestamps format = timepassed/one_hour; string v Alue = timestamps format + "Hour"; updateatvalue = string. format (getresources (). getstring (R. string. updated_at), value);} else if (timepassed <one_month) {timestamps format = timepassed/one_day; string value = timestamps format + "day"; updateatvalue = string. format (getresources (). getstring (R. string. updated_at), value);} else if (timepassed <one_year) {timestamps format = timepassed/one_month; string value = tim Eintoformat + "months"; updateatvalue = string. format (getresources (). getstring (R. string. updated_at), value);} else {timestamps format = timepassed/one_year; string value = timestamps format + "year"; updateatvalue = string. format (getresources (). getstring (R. string. updated_at), value);} updateat. settext (updateatvalue);}/*** a refresh task. In this task, callback is performed to the registered pull-down refresh listener. ** @ Author Guolin */class refreshingtask extends asynctask <void, integer, void >{@ overrideprotected void doinbackground (void... params) {int topmargin = headerlayoutparams. topmargin; while (true) {topmargin = topmargin + scroll_speed; If (topmargin <= 0) {topmargin = 0; break;} publishprogress (topmargin); sleep (10 );} currentstatus = status_refreshing; publishprogress (0); If (mlistener! = NULL) {mlistener. onrefresh () ;}return null ;}@ overrideprotected void onprogressupdate (integer... topmargin) {updateheaderview (); headerlayoutparams. topmargin = topmargin [0]; header. setlayoutparams (headerlayoutparams) ;}}/*** hide the tasks with the drop-down list. If the drop-down list is not refreshed or the drop-down list is refreshed, this task will re-hide the drop-down list. ** @ Author Guolin */class hideheadertask extends asynctask <void, integer, integer >{@ overrideprotected integer doinbackground (void... params) {int topmargin = headerlayoutparams. topmargin; while (true) {topmargin = topmargin + scroll_speed; If (topmargin <= hideheaderheight) {topmargin = hideheaderheight; break;} publishprogress (topmargin); sleep (10 );} return topmargin;} @ overrideprotected void onprogre Ssupdate (integer... topmargin) {headerlayoutparams. topmargin = topmargin [0]; header. setlayoutparams (headerlayoutparams);} @ overrideprotected void onpostexecute (integer topmargin) {headerlayoutparams. topmargin = topmargin; header. setlayoutparams (headerlayoutparams); currentstatus = status_refresh_finished;}/*** specifies the number of milliseconds for the current thread to sleep. ** @ Param time * specifies the duration of sleep of the current thread, in milliseconds */private void sleep (INT time) {try {thread. sleep (time);} catch (interruptedexception e) {e. printstacktrace () ;}}/*** pull-down refresh listener. You should register this listener for Refresh callback. ** @ Author Guolin */public interface pulltorefreshlistener {/*** calls back this method during refresh and writes the specific refresh logic in the method. Note that this method is called in the Child thread. You do not need to open another thread to perform time-consuming operations. */Void onrefresh ();}}
This class is the most important class in the entire pull-down refresh function. The comments have been written in more detail. I will explain it briefly. First, the newly defined pull_to_refresh layout is dynamically added to the refreshableview constructor as the drop-down header. Then, the drop-down header is removed from the screen in the onlayout method, and a touch event is registered for the listview. The ontouch method is executed whenever the finger slides on the listview. The setisabletopull method is called in the first line of the ontouch method to determine whether the listview is scrolled to the top. The code that follows is executed only when it is scrolled to the top, otherwise, it will be treated as normal listview scrolling without any processing. When the listview scroll to the top, if the finger is still dragging down, it will change the Offset Value of the drop-down header so that the drop-down header is displayed, the drop-down distance is set to 1/2 of the finger movement distance, so that the tension will feel. If the drop-down distance is large enough, a refresh operation will be performed when you release the plug-in. If the distance is not large enough, you just need to hide the drop-down header again.
The specific refresh operation will be performed in refreshingtask. In the doinbackground method, the onrefresh method of the pulltorefreshlistener interface is called back. This is also an interface that must be implemented when you use refreshableview, because the specific refresh logic should be written in the onrefresh method, and the method used will be demonstrated later.
In addition, the updateheaderview method will be called each time you pull down to change the data in the drop-down header, such as the rotation of the arrow direction and the change of the drop-down text description. For more in-depth understanding, please read the code in refreshableview carefully.
Now we have completed all the pull-down refresh functions. Next we will take a look at how to introduce the pull-down refresh in the project. Open or create activity_main.xml as the layout of the main interface of the program, and add the following code:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <com.example.pulltorefreshtest.RefreshableView android:id="@+id/refreshable_view" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@+id/list_view" android:layout_width="fill_parent" android:layout_height="fill_parent" > </ListView> </com.example.pulltorefreshtest.RefreshableView></RelativeLayout>
We can see that a listview is added to the custom refreshableview, which means that the drop-down refresh function is added to this listview, which is so simple!
Then let's take a look at the main activity of the program, open or create a mainactivity, and add the following code:
public class MainActivity extends Activity {RefreshableView refreshableView;ListView listView;ArrayAdapter<String> adapter;String[] items = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L" };@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_main);refreshableView = (RefreshableView) findViewById(R.id.refreshable_view);listView = (ListView) findViewById(R.id.list_view);adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);listView.setAdapter(adapter);refreshableView.setOnRefreshListener(new PullToRefreshListener() {@Overridepublic void onRefresh() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}refreshableView.finishRefreshing();}}, 0);}}
We can see that we have registered a listener by calling the setonrefreshlistener method of refreshableview. When the listview is refreshing, it will call back the onrefresh method of the listener. The specific refresh logic is processed here. In addition, this method has automatically enabled the thread and can perform time-consuming operations directly in the onrefresh method, for example, to request the latest data from the server. Here I will simply let the thread sleep for 3 seconds. In addition, at the end of the onrefresh method, you must call the finishrefreshing method in refreshableview. This method is used to notify the refreshableview that the Refresh has ended. Otherwise, our listview will remain in the refreshing state.
I wonder if you have noticed that the setonrefreshlistener method actually has two parameters, and we just passed in an inconspicuous 0. What is the second parameter used? Because refreshableview is intelligent, it will automatically help us record the time when the last refresh was completed, and then show in the drop-down list how long it has elapsed since the last refresh. This is a very useful function, so we don't have to manually record and calculate the time, but there is a problem. If the pull-down refresh function is used in three of our projects, the refresh is now performed in one place, and the time for the other two will change as well! Because the refresh time is recorded in the configuration file, because the configuration file has been refreshed and changed in one refresh, the time of the configuration file read in the other two has been changed. So what is the solution? It is the place where the pull-down refresh is used to input different IDs to the second parameter of the setonrefreshlistener method. In this way, the last refresh completion time is recorded separately, and there will be no impact between them.
All right, all the code is here. Let's run it and see the effect.
The effect looks very good. Finally, we will summarize the three steps to introduce the listview pull-down refresh function in the project:
1. Add a custom refreshableview to the activity layout file and include the listview in it.
2. Call the setonrefreshlistener method of refreshableview in the activity to register the callback interface.
3. At the end of the onrefresh method, remember to call the finishrefreshing method of refreshableview to notify the refresh end.
From then on, the pull-down refresh function will be introduced in one minute anywhere in the project.
Now, today's explanation is over. If you have any questions, please leave a message below.
Click here to download the source code