In Android, the 100-line code is used to implement a custom ListView that can be pulled up or down. androidlistview
Reprinted please indicate the source: http://blog.csdn.net/bettarwang/article/details/41634729
I have seen some so-called pull-down refresh examples on the Internet before, but I always feel that it is complicated to do simple things. It is easy to use more than 300 lines or even more than 600 lines of code, in fact, it is mainly to respond to touch events, and there is no need for such trouble. The following code first implements a ListView that can be pulled up or down, and then a ListView with a Header that can be pulled down and refreshed:
The source code of the up and down-pulled ListView is as follows:
/*** Top/bottom-up Pull ListView * @ author Bettar **/public class RefreshableListView extends ListView {private static final String TAG = "RefreshableListView"; private int touchSlop; private int initTopMargin; // private int initTopOfFirstChild; private boolean hasRecord = false; private float startY; private boolean isPulling = false; // private ViewGroup. layoutParams params; private LinearLayout. layoutParams params; public RefreshableListView (Context context, AttributeSet attrs) {super (context, attrs); // you can read the setting parameters without conflict with the settings in the layout file. Params = new LinearLayout. layoutParams (context, attrs); initTopMargin = params. topMargin; this. setLayoutParams (params); touchSlop = ViewConfiguration. get (context ). getTouchSlop () ;}@ Overridepublic boolean onTouchEvent (MotionEvent event) {switch (event. getAction () {case MotionEvent. ACTION_DOWN: if (! HasRecord) {hasRecord = true; startY = event. getY (); Log. I (TAG, "ACTION_DOWN");} break; case MotionEvent. ACTION_MOVE: float distance = event. getY ()-startY; if (! IsPulling) {if (! CouldPull (distance) {Log. I (TAG, "cocould not pull in ACTION_MOVE"); return false ;}} isPulling = true; Log. I (TAG, "pull in ACTION_MOVE"); params. topMargin + = distance; this. setLayoutParams (params); this. setPressed (false); this. setFocusable (false); this. setFocusableInTouchMode (false); return true; case MotionEvent. ACTION_UP: Log. I (TAG, "ACTION_UP"); params. topMargin = initTopMargin; this. setLayoutParams (params); HasRecord = false; this. setFocusable (true); this. setFocusableInTouchMode (true); if (isPulling) {isPulling = false; // Note: After stretching, true must be returned; otherwise, the event will be read by other event processors, this affects the external operations of this class, such as the operations in setOnItemClickListener. Return true;} isPulling = false; break;} return super. onTouchEvent (event);} private boolean couldPull (float distance) {if (Math. abs (distance) <touchSlop) {return false;} if (distance> 0) {Log. I (TAG, "getTop ()" + this. getTop (); if (this. getFirstVisiblePosition () = 0 & this. getChildAt (0 ). getTop () = 0) {return true ;}} else {if (this. getLastVisiblePosition () = this. getCount ()-1) {return true ;}} return false ;}}
One of the details should be noted is the processing of ACTION_UP. If the finger's ACTION_UP is opened after stretching, true instead of false should be returned; otherwise, the normal use of the custom ListView will be affected, if false is returned, the entire process will be treated as a Click because of ACTION_DOWN and ACTION_UP, which will affect the operation.
If you want to add a certain amount of animation, it is also very simple to use the animation or asynchronous task to implement, the following code uses two implementation methods:
Package com. android. customview; import android. content. context; import android. OS. asyncTask; import android. util. attributeSet; import android. util. log; import android. view. motionEvent; import android. view. viewConfiguration; import android. view. viewGroup; import android. view. animation. translateAnimation; import android. widget. absListView; import android. widget. linearLayout; import android. widget. listView ;/*** ListView * @ author Bettar **/public class RefreshableListView extends ListView {private static final String TAG = "RefreshableListView"; // 0.5 will feel stuck, 1.0 is too slippery, and 0.8 is a good parameter. Private static final float RATIO = 0.8f; private static final int ANIM_DURATION = 1000; private int touchSlop; private int initTopMargin; private int [] initLocation = new int [2]; private boolean hasRecord = false; private float startY; private boolean isPulling = false; // private ViewGroup. layoutParams params; private LinearLayout. layoutParams params; public RefreshableListView (Context context, AttributeSet attrs) {sup Er (context, attrs); // params = this. getLayoutParams (); // you can read the setting parameters without conflict with the settings of the layout file. Params = new LinearLayout. layoutParams (context, attrs); initTopMargin = params. topMargin; this. getLocationOnScreen (initLocation); // initTopOfFirstChild = this. getChildAt (0 ). getTop (); this. setLayoutParams (params); touchSlop = ViewConfiguration. get (context ). getTouchSlop () ;}@ Overridepublic boolean onTouchEvent (MotionEvent event) {switch (event. getAction () {case MotionEvent. ACTION_DOWN: if (! HasRecord) {hasRecord = true; startY = event. getY (); Log. I (TAG, "ACTION_DOWN");} break; case MotionEvent. ACTION_MOVE: float distance = event. getY ()-startY; if (! IsPulling) {if (! CouldPull (distance) {Log. I (TAG, "cocould not pull in ACTION_MOVE"); return false ;}} isPulling = true; Log. I (TAG, "pull in ACTION_MOVE"); params. topMargin = initTopMargin + (int) (distance * RATIO); this. setLayoutParams (params); this. setPressed (false); this. setFocusable (false); this. setFocusableInTouchMode (false); return true; case MotionEvent. ACTION_UP: Log. I (TAG, "ACTION_UP"); if (isPulling) {startTranslateAnima Tion (); // executeTranslateAnimation ();} // reset the parameter. Note that if you use a Custom Animation, You need to annotate the reset () Here, wait until the asynchronous task is completed and then execute reset (); otherwise, the parameters will interfere with each other. Reset (); if (isPulling) {isPulling = false; // Note: true must be returned after being stretched. Otherwise, the event will be read by other event processors, this affects the external operations of this class, such as the operations in setOnItemClickListener. Return true;} isPulling = false; break;} return super. onTouchEvent (event);} private void reset () {params. topMargin = initTopMargin; this. setLayoutParams (params); hasRecord = false; this. setFocusable (true); this. setFocusableInTouchMode (true);} private void startTranslateAnimation () {int [] location = new int [2]; RefreshableListView. this. getLocationOnScreen (location); // The test shows that location [0] = 0, and location [1] is the distance from the top of the first Item to the top. Log. I (TAG, "location [0] =" + location [0] + "location [1] =" + location [1]); translateAnimation anim = new TranslateAnimation (location [0], initLocation [0], location [1], initLocation [1]); anim. setDuration (ANIM_DURATION); RefreshableListView. this. startAnimation (anim);}/*** this is actually equivalent to implementing animation on your own. */Private void executeTranslateAnimation () {new translatetask(2020..exe cute ();}/*** if you use it, you need to put the params parameters after the asynchronous task is executed, otherwise, mutual interference may occur. * @ Author Bettar **/private class TranslateTask extends AsyncTask <Void, Integer, Integer> {// The sleep time of each thread. private int deltaSleepTime; private int deltaScrollY; public TranslateTask (int deltaSleepTime) {this. deltaSleepTime = deltaSleepTime; if (deltaSleepTime> 0) {deltaScrollY = 0-(params. topMargin-initTopMargin)/(ANIM_DURATION/deltaSleepTime);} else {deltaScrollY = params. topMargin> initTopMargin? -20:20;} Log. I (TAG, "deltaScrollY =" + deltaScrollY) ;}@ Overrideprotected Integer doInBackground (Void... voidParams) {int topMargin = params. topMargin; while (true) {topMargin + = deltaScrollY; Log. I (TAG, "topMargin =" + topMargin); if (deltaScrollY <0) {if (topMargin <0) {topMargin = 0; break ;}} else {if (topMargin> 0) {topMargin = 0; break ;}} publishProgress (topMargin); try {Thread. sleep (deltaSleepTime);} catch (InterruptedException ex) {e X. printStackTrace () ;}} publishProgress (0); return topMargin ;}@ Overrideprotected void onProgressUpdate (Integer... values) {// values [0] corresponds to topMarginLog in publisProgress above. I (TAG, "values [0] I. e topMargin = "+ values [0]); params. topMargin = values [0]; RefreshableListView. this. setLayoutParams (params) ;}@ Overrideprotected void onPostExecute (Integer result) {// After the asynchronous task is executed, You can reset the reset ();}} /*** determine whether the pull can start. If the pull is downward Find that the first Item is completely visible; if it is pulled up, the last Item must be completely visible. * @ Param distance * @ return */private boolean couldPull (float distance) {if (Math. abs (distance) <touchSlop) {return false;} if (distance> 0) {Log. I (TAG, "getTop ()" + this. getTop (); if (this. getFirstVisiblePosition () = 0 & this. getChildAt (0 ). getTop () = 0) // if (this. getFirstVisiblePosition () = 0 & this. getChildAt (0 ). getTop () = initTopOfFirstChild) {return true ;}} else {if (this. getLastVisiblePosition () = this. getCount ()-1) {return true ;}} return false ;}}
Believe this, it is very easy to create a ListView with a pull-down refresh header. Let's go here today and add the code after the pull-down refresh.