listview反彈實現詳解


本文轉自 http://jianwang0412.iteye.com/blog/1267885

重寫listview,通過監聽滑動事件,根據滑動時所處的位置,以及滑動的方向,使用view的內置scrollTo或scrollBy函數來移動view到你手勢互動的距離(此處為一半),然后當確定消費了給事件后,又回滾到(0,0)點。當然只有在超出了邊界時才回滾。而且回滾的過程由TranslateAnimation來控制,這樣的好處在代碼的解釋中。我是基於網絡上的listviewpress改了一些(有幾處好像是被篡改了,我又按我的理解將它改正過來,運行后沒問題)。一下是關鍵的代碼,整個代碼見附件中。有不懂的可以問問,大家互相學習。

 

 

 

Java代碼    收藏代碼
  1. package com.listview.test;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Rect;  
  5. import android.util.AttributeSet;  
  6. import android.util.Log;  
  7. import android.view.GestureDetector;  
  8. import android.view.MotionEvent;  
  9. import android.view.View;  
  10. import android.view.GestureDetector.OnGestureListener;  
  11. import android.view.animation.TranslateAnimation;  
  12. import android.widget.ListView;  
  13.   
  14. public class CustomerListView extends ListView {  
  15.   
  16.     private Context mContext;  
  17.     private boolean outBound = false;  
  18.     private int distance;  
  19.     private int firstOut;  
  20.   
  21.     public CustomerListView(Context c) {  
  22.         super(c);  
  23.         this.mContext = c;  
  24.     }  
  25.   
  26.     public CustomerListView(Context c, AttributeSet attrs) {  
  27.         super(c, attrs);  
  28.         this.mContext = c;  
  29.     }  
  30.   
  31.     public CustomerListView(Context c, AttributeSet attrs, int defStyle) {  
  32.         super(c, attrs, defStyle);  
  33.         this.mContext = c;  
  34.     }  
  35.   
  36.     GestureDetector gestureDetector = new GestureDetector(  
  37.             new OnGestureListener() {  
  38.   
  39.                 public boolean onDown(MotionEvent e) {  
  40.                     // TODO Auto-generated method stub  
  41.                     return false;  
  42.                 }  
  43.   
  44.                 public boolean onFling(MotionEvent e1, MotionEvent e2,  
  45.                         float velocityX, float velocityY) {  
  46.                     // TODO Auto-generated method stub  
  47.                     return false;  
  48.                 }  
  49.   
  50.                 public void onLongPress(MotionEvent e) {  
  51.                     // TODO Auto-generated method stub  
  52.   
  53.                 }  
  54. /**捕捉滑動事件  e1為此處為的ACTION_DOWN事件(無論什么動作,起始都是該動作),而e2是觸發調用onScroll的事件。而在此期間,可能已經 
  55.  * 觸發了多次的onScroll,因為我們滑動過程可能比較長,一旦長於某個值,就會觸發一次(即一個Move應該是由多個 
  56.  * move事件組成的,開頭當然是個ACTION_DOWN事件),也就會發出一個移動的MotionEvent。但是期間開始此次 
  57.  * scroll的e1是唯一的。而distance是最近一次調用onScroll以來的距離(前一個e2和現在e2的距離:比如上次是-30,這次是-60(比如向下拉), 
  58.  * 那么 distanceY=-60-(-30)=-30)。 
  59.  */  
  60.   
  61.                 public boolean onScroll(MotionEvent e1, MotionEvent e2,  
  62.                         float distanceX, float distanceY) {  
  63.                     /** 
  64.                      * firstPos和lastPos是adapter中元素的Id 
  65.                      */  
  66.                     int firstPos = getFirstVisiblePosition();  
  67.                     int lastPos = getLastVisiblePosition();  
  68.                     int itemCount = getCount();  
  69.                       
  70.                     /** 
  71.                      * 滑出邊界,而且是一個極點,即可視部分已經已經不存在了,那么直接回到原點 
  72.                      */  
  73.                     if (outBound && firstPos != 0 && lastPos != (itemCount - 1)) {  
  74.                         scrollTo(00);  
  75.                         return false;  
  76.                     }  
  77.                     /** 
  78.                      * getChildAt是屏幕上可見的元素的id,比如現在屏幕上可見的是adapter中的 
  79.                      * 4號到10號,那么你調用getChildAt應該是0~6號  
  80.                      * listView.getChildAt(i) works where 0 is the very first visible row and 
  81.                      *  (n-1) is the last visible row (where n is the number of visible views you see). 
  82.                      *  進入該onScroll有4種可能,第一種是剛開始的時候,此時firstPos==0,而且可視的item在getChildAt的 
  83.                      *  返回也是第一個元素,即adapter元素的index和可視的view的編號一致,所以firstview不為空(lastview也一樣)。 
  84.                      *  當你向上 滑動時,distanceY是大於0的。此時將不消費此次事件,那么將正常地在沒有超出邊際出滾動。 
  85.                      *  第二種是,若以上是向下拉,那么應該屬於超出范圍的情況,則要消費此時事件。 
  86.                      *  第三種和第一種類似,只是到了當剛好顯示最后一個item時,顯然firstView和lastView都將是null,因為 
  87.                      *  此時的adapter的index和getChildAt的index不是相等的,而是成對應關系, 
  88.                      *  即index_adp-firstPos=index_getChild,此時你若使用getChildAt(firstpos-firstpos),那返回的 
  89.                      *  將是非null。同理在lastView。第四種是當在第三情況下,向上拉,那么屬於超出邊界。那么lastView是null這個特征 
  90.                      *  將可以判斷是否進入了下臨界區。 
  91.                      *  總結以上四種情況,每當觸發臨界區時(dispatchTouchEvent時getFirstVisiblePosition()==0 
  92.                      *  和getLastVisiblePosition()==getCount()-1),就可以通過distanceY的方向性判斷是正常的滑動 
  93.                      *  還是將要滑出臨界區。若是滑出臨界區,說明此次將消費該事件,所以返回true,那么在dispatchTouchEvent 
  94.                      *  將設置outBand為true,那么第二次再進入時,將可以通過outBand來確定是否出了臨界區。 
  95.                      *   
  96.                      *  帶方向的函數:onScrollBy/To和onScroll 
  97.                      */  
  98.                     View firstView = getChildAt(firstPos);  
  99.                     View lastView = getChildAt(lastPos-1);  
  100.                 /** 
  101.                  * 記錄下第一次的e2的y軸距離,此次過后outBound就變為了true。這樣distance就是跟蹤最近的一次e2 
  102.                  * 和最開始一次的e2的距離。 
  103.                  */  
  104.                     if (!outBound) {  
  105.                         firstOut = (int) e2.getRawY();  
  106.                     }  
  107.                     if (firstView != null  
  108.                             && (outBound || (firstPos == 0  
  109.                                     && firstView.getTop() == 0 && distanceY < 0))) {  
  110.                         distance = (int) (firstOut - e2.getRawY());//此處應為負值,即view向下滑動  
  111.                 /**  
  112.                  * scrollBy中的值帶有方向,x若為正,則應該以view中該x點顯示在新的原點上,即拿新的點去  
Java代碼    收藏代碼
  1. <span style="white-space: pre;">                </span>*重合y軸,就好像整個布局被往左拉動。  
  2.                  * y為正,則向上滑動|y|距離。負則相反。  
  3.                  */  
  4.                         scrollBy(0, distance / 2);  
  5.                         Log.v("onScroll""e2.getRawY():"+e2.getRawY());  
  6.                         Log.v("onScroll""distance:"+distance);  
  7.                         Log.v("onScroll""distanceY:"+distanceY);  
  8.                         return true;  
  9.                     }  
  10.                     if (lastView == null&&(outBound || (lastPos == itemCount - 1 && distanceY > 0))) {  
  11.                         Log.d("bottom""bottom");  
  12.                         distance = (int) (firstOut - e2.getRawY());//此處應為正直,因為view向上滑動  
  13.                         scrollBy(0, distance/2);  
  14.                         return true;  
  15.                     }  
  16.                     return false;  
  17.                 }  
  18.   
  19.                 public void onShowPress(MotionEvent e) {  
  20.                     // TODO Auto-generated method stub  
  21.   
  22.                 }  
  23.   
  24.                 public boolean onSingleTapUp(MotionEvent e) {  
  25.                     // TODO Auto-generated method stub  
  26.                     return false;  
  27.                 }  
  28.             });  
  29.   
  30.     /** 
  31.      * 最早響應觸屏事件,按下和釋放響應兩次 
  32.      */  
  33.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  34.         if(getFirstVisiblePosition()==0){  
  35.             int act = ev.getAction();  
  36.             if ((act == MotionEvent.ACTION_UP || act == MotionEvent.ACTION_CANCEL)  
  37.                     && outBound) {  
  38.                 outBound = false;  
  39.             }  
  40.             if (!gestureDetector.onTouchEvent(ev)) {  
  41.                 outBound = false;  
  42.             } else {  
  43.                 outBound = true;  
  44.             }  
  45.             Rect rect = new Rect();  
  46.             getLocalVisibleRect(rect);  
  47.             /** 
  48.              * rect.top是個正的距離值,而TanslateAnimation填的是坐標值(有方向的); 
  49.              */  
  50.             TranslateAnimation am = new TranslateAnimation(00, -rect.top, 0);  
  51.             /** 
  52.              * 若此處時間設為0,將導致一陣的抖動,因為完成回滾的速度不是分步,而是直接到終點 
  53.              * 因為每次觸發onScroll時都會做一次回滾,而當傳進又一次move時,上一次的move還沒作完 
  54.              * 就將被新的一次覆蓋,所以不用擔心產生抖動。所以此處給它設時間就是抓住它需要時間來完成回滾的目標,相當 
  55.              * 於給它一個時間的緩沖來實現移動,因為當你在移動時,實際是不需要回滾的,只有你釋放了手指還才需要回滾。 
  56.              * 注意,此時調用scrollTo已經將位置返回了0(可以把animation當成是模型,只有使用scrollTo才 
  57.              * 能真正觸發該移動,結果是已經知道了的,即移動到原點,而過程是TranslateAnimation參謀的,即 
  58.              * scrollTo在移動時會調用onScrollChange來實際移動,而onScrollChange則根據傳入的參數來移動 
  59.              * 而TranslateAnimation則可以控制該參數。可以把scrollTo先去掉,就可以發現new top 和 
  60.              * after scrollBy是一樣的值)。也就是new Top=0。所以每次迭代相減都是現在的e2減去最初的e2在y軸上的值, 
  61.              * 這樣通過scrollBy就可以將view移動到新的位置,而此時top也就又被寫成了新的滑動的位置(是滑動距離的一半位置)。 
  62.              * 11-19 23:51:11.101: V/onScroll(18396): after scrollBy top:0 
  63.                 11-19 23:51:11.101: V/onScroll(18396): new top:0 
  64.                 11-19 23:51:11.249: V/onScroll(18396): after scrollBy top:0 
  65.                 11-19 23:51:11.249: V/onScroll(18396): new top:0 
  66.                 11-19 23:51:11.288: V/onScroll(18396): after scrollBy top:-6 
  67.                 11-19 23:51:11.288: V/onScroll(18396): new top:0 
  68.                 11-19 23:51:11.319: V/onScroll(18396): after scrollBy top:-16 
  69.                 11-19 23:51:11.319: V/onScroll(18396): new top:0 
  70.                 11-19 23:51:11.358: V/onScroll(18396): after scrollBy top:-20 
  71.                 11-19 23:51:11.358: V/onScroll(18396): new top:0 
  72.                 11-19 23:51:11.374: V/onScroll(18396): after scrollBy top:-27 
  73.                 11-19 23:51:11.374: V/onScroll(18396): new top:0 
  74.              */  
  75.             am.setDuration(300);  
  76.             startAnimation(am);  
  77.             Log.v("onScroll","after scrollBy top:"+rect.top);  
  78.             scrollTo(00);  
  79.             getLocalVisibleRect(rect);  
  80.             Log.v("onScroll""new top:"+rect.top);  
  81.         }  
  82.         Log.d("getLastVisiblePosition()", getLastVisiblePosition()+"");  
  83.         Log.d("getCount()", getCount()+"");  
  84.         if(getLastVisiblePosition()==getCount()-1){  
  85.             int act = ev.getAction();  
  86.             if ((act == MotionEvent.ACTION_DOWN || act == MotionEvent.ACTION_CANCEL)  
  87.                     && outBound) {  
  88.                 outBound = false;  
  89.             }  
  90.             if (!gestureDetector.onTouchEvent(ev)) {  
  91.                 outBound = false;  
  92.             } else {  
  93.                 outBound = true;  
  94.             }  
  95.             if(outBound){  
  96.                 Rect rect1 = new Rect();  
  97.                 getLocalVisibleRect(rect1);  
  98.                 TranslateAnimation am1 = new TranslateAnimation(00, rect1.top, 0);  
  99.                 am1.setDuration(300);  
  100.                 startAnimation(am1);  
  101.                 scrollTo(00);  
  102.             }  
  103.         }  
  104.         return super.dispatchTouchEvent(ev);  
  105.     };  
  106. }  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM