scroller滑屏實現

       


        前言:  雖然本文標題的有點標題黨的感覺,但無論如何,通過這篇文章的學習以及你自己的實踐認知,寫個簡單的滑屏小

   Demo還是just so so的。

 

        友情提示:

            在繼續往下面讀之前,希望您對以下知識點有一定程度掌握,否則,繼續看下去對您意義也不大。


             1、掌握View(視圖)的"視圖座標"以及"佈局座標",以及scrollTo()和scrollBy()方法的作用 ----- 必須理解

                      如果對這方面知識不太清楚的話,建議先看看我的這篇博客

                         <Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用說明>, 

                   不誇張地說,這篇博客理論上來說是我們這篇博文的基礎。


             2、知道onInterceptTouchEvent()以及onTouchEvent()對觸摸事件的分發流程         ---- 不是必須

             3、知道怎麼繪製自定義ViewGroup即可                                        ---- 不是必須

 

 

     OK。 繼續往下看,請一定有所準備 。大家跟着我一步一步來咯。

 

 知識點一:  關於scrollTo()和scrollBy()以及偏移座標的設置/取值問題


        在前面一篇博文中《Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用說明》,我們掌握了scrollTo()和

scrollBy()方法的作用,這兩個方法的主要作用是將View/ViewGroup移至指定的座標中,並且將偏移量保存起來。另外:

                  mScrollX 代表X軸方向的偏移座標

                  mScrollY 代表Y軸方向的偏移座標


          關於偏移量的設置我們可以參看下源碼:

  1. package com.qin.customviewgroup;  
  2.   
  3. public class View {  
  4.     ....  
  5.     protected int mScrollX;   //該視圖內容相當於視圖起始座標的偏移量   , X軸 方向      
  6.     protected int mScrollY;   //該視圖內容相當於視圖起始座標的偏移量   , Y軸方向  
  7.     //返回值  
  8.     public final int getScrollX() {  
  9.         return mScrollX;  
  10.     }  
  11.     public final int getScrollY() {  
  12.         return mScrollY;  
  13.     }  
  14.     public void scrollTo(int x, int y) {  
  15.         //偏移位置發生了改變  
  16.         if (mScrollX != x || mScrollY != y) {  
  17.             int oldX = mScrollX;  
  18.             int oldY = mScrollY;  
  19.             mScrollX = x;  //賦新值,保存當前便宜量  
  20.             mScrollY = y;  
  21.             //回調onScrollChanged方法  
  22.             onScrollChanged(mScrollX, mScrollY, oldX, oldY);  
  23.             if (!awakenScrollBars()) {  
  24.                 invalidate();  //一般都引起重繪  
  25.             }  
  26.         }  
  27.     }  
  28.     // 看出原因了吧 。。 mScrollX 與 mScrollY 代表我們當前偏移的位置 , 在當前位置繼續偏移(x ,y)個單位  
  29.     public void scrollBy(int x, int y) {  
  30.         scrollTo(mScrollX + x, mScrollY + y);  
  31.     }  
  32.     //...  
  33. }  

     於是,在任何時刻我們都可以獲取該View/ViewGroup的偏移位置了,即調用getScrollX()方法和getScrollY()方法

 

 知識點二: Scroller類的介紹


         在初次看Launcher滑屏的時候,我就對Scroller類的學習感到非常蛋疼,完全失去了繼續研究的慾望。如今,沒得辦法,

  得重新看Launcher模塊,基本上將Launcher大部分類以及功能給掌握了。當然,也花了一天時間來學習Launcher裏的滑屏實現

 ,基本上業是撥開雲霧見真知了。

     

       我們知道想把一個View偏移至指定座標(x,y)處,利用scrollTo()方法直接調用就OK了,但我們不能忽視的是,該方法本身

   來的的副作用:非常迅速的將View/ViewGroup偏移至目標點,而沒有對這個偏移過程有任何控制,對用戶而言可能是不太

   友好的。於是,基於這種偏移控制,Scroller類被設計出來了,該類的主要作用是爲偏移過程制定一定的控制流程(後面我們會

   知道的更多),從而使偏移更流暢,更完美。

   

     可能上面說的比較懸乎,道理也沒有講透。下面我就根據特定情景幫助大家分析下:


        情景: 從上海如何到武漢?

            普通的人可能會想,so easy : 飛機、輪船、11路公交車...

            文藝的人可能會想,  小 case : 時空忍術(火影的招數)、翻個筋斗(孫大聖的招數)...


     不管怎麼樣,我們想出來的套路可能有兩種:

               1、有個時間控制過程才能抵達(緩慢的前進)                              -----     對應於Scroller的作用

                      假設做火車,這個過程可能包括: 火車速率,花費週期等;

               2、瞬間抵達(超神太快了,都眩暈了,用戶體驗不太好)                     ------   對應於scrollTo()的作用

 

    模擬Scroller類的實現功能:


        假設從上海做動車到武漢需要10個小時,行進距離爲1000km ,火車速率200/h 。採用第一種時間控制方法到達武漢的

   整個配合過程可能如下:

        我們每隔一段時間(例如1小時),計算火車應該行進的距離,然後調用scrollTo()方法,行進至該處。10小時過完後,

    我們也就達到了目的地了。

 

    相信大家心裏應該有個感覺了。我們就分析下源碼裏去看看Scroller類的相關方法.

 

     其源代碼(部分)如下: 路徑位於 \frameworks\base\core\java\android\widget\Scroller.java

  1. public class Scroller  {  
  2.   
  3.     private int mStartX;    //起始座標點 ,  X軸方向  
  4.     private int mStartY;    //起始座標點 ,  Y軸方向  
  5.     private int mCurrX;     //當前座標點  X軸, 即調用startScroll函數後,經過一定時間所達到的值  
  6.     private int mCurrY;     //當前座標點  Y軸, 即調用startScroll函數後,經過一定時間所達到的值  
  7.      
  8.     private float mDeltaX;  //應該繼續滑動的距離, X軸方向  
  9.     private float mDeltaY;  //應該繼續滑動的距離, Y軸方向  
  10.     private boolean mFinished;  //是否已經完成本次滑動操作, 如果完成則爲 true  
  11.   
  12.     //構造函數  
  13.     public Scroller(Context context) {  
  14.         this(context, null);  
  15.     }  
  16.     public final boolean isFinished() {  
  17.         return mFinished;  
  18.     }  
  19.     //強制結束本次滑屏操作  
  20.     public final void forceFinished(boolean finished) {  
  21.         mFinished = finished;  
  22.     }  
  23.     public final int getCurrX() {  
  24.         return mCurrX;  
  25.     }  
  26.      /* Call this when you want to know the new location.  If it returns true, 
  27.      * the animation is not yet finished.  loc will be altered to provide the 
  28.      * new location. */    
  29.     //根據當前已經消逝的時間計算當前的座標點,保存在mCurrX和mCurrY值中  
  30.     public boolean computeScrollOffset() {  
  31.         if (mFinished) {  //已經完成了本次動畫控制,直接返回爲false  
  32.             return false;  
  33.         }  
  34.         int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);  
  35.         if (timePassed < mDuration) {  
  36.             switch (mMode) {  
  37.             case SCROLL_MODE:  
  38.                 float x = (float)timePassed * mDurationReciprocal;  
  39.                 ...  
  40.                 mCurrX = mStartX + Math.round(x * mDeltaX);  
  41.                 mCurrY = mStartY + Math.round(x * mDeltaY);  
  42.                 break;  
  43.             ...  
  44.         }  
  45.         else {  
  46.             mCurrX = mFinalX;  
  47.             mCurrY = mFinalY;  
  48.             mFinished = true;  
  49.         }  
  50.         return true;  
  51.     }  
  52.     //開始一個動畫控制,由(startX , startY)在duration時間內前進(dx,dy)個單位,即到達座標爲(startX+dx , startY+dy)出  
  53.     public void startScroll(int startX, int startY, int dx, int dy, int duration) {  
  54.         mFinished = false;  
  55.         mDuration = duration;  
  56.         mStartTime = AnimationUtils.currentAnimationTimeMillis();  
  57.         mStartX = startX;       mStartY = startY;  
  58.         mFinalX = startX + dx;  mFinalY = startY + dy;  
  59.         mDeltaX = dx;            mDeltaY = dy;  
  60.         ...  
  61.     }  
  62. }  

     其中比較重要的兩個方法爲:


            public void startScroll(int startX, int startY, int dx, int dy, int duration)

                   函數功能說明:根據當前已經消逝的時間計算當前的座標點,保存在mCurrX和mCurrY值中

            public void startScroll(int startX, int startY, int dx, int dy, int duration)

                  函數功能說明:開始一個動畫控制,由(startX , startY)在duration時間內前進(dx,dy)個單位,到達座標爲

                      (startX+dx , startY+dy)處。


        PS : 強烈建議大家看看該類的源碼,便於後續理解。


知識點二: computeScroll()方法介紹


       爲了易於控制滑屏控制,Android框架提供了 computeScroll()方法去控制這個流程。在繪製View時,會在draw()過程調用該

  方法。因此, 再配合使用Scroller實例,我們就可以獲得當前應該的偏移座標,手動使View/ViewGroup偏移至該處。

     computeScroll()方法原型如下,該方法位於ViewGroup.java類中      

  1. /** 
  2.      * Called by a parent to request that a child update its values for mScrollX 
  3.      * and mScrollY if necessary. This will typically be done if the child is 
  4.      * animating a scroll using a {@link android.widget.Scroller Scroller} 
  5.      * object. 
  6.      */由父視圖調用用來請求子視圖根據偏移值 mScrollX,mScrollY重新繪製  
  7.     public void computeScroll() { //空方法 ,自定義ViewGroup必須實現方法體  
  8.           
  9.     }  

          爲了實現偏移控制,一般自定義View/ViewGroup都需要重載該方法 。

 

     其調用過程位於View繪製流程draw()過程中,如下:

  1. @Override  
  2. protected void dispatchDraw(Canvas canvas){  
  3.     ...  
  4.       
  5.     for (int i = 0; i < count; i++) {  
  6.         final View child = children[getChildDrawingOrder(count, i)];  
  7.         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  8.             more |= drawChild(canvas, child, drawingTime);  
  9.         }  
  10.     }  
  11. }  
  12. protected boolean drawChild(Canvas canvas, View child, long drawingTime) {  
  13.     ...  
  14.     child.computeScroll();  
  15.     ...  
  16. }  

   Demo說明:

           我們簡單的複用了之前寫的一個自定義ViewGroup,與以前一次有區別的是,我們沒有調用scrollTo()方法去進行瞬間

       偏移。 本次做法如下:

                   第一、調用Scroller實例去產生一個偏移控制(對應於startScroll()方法)

                   第二、手動調用invalid()方法去重新繪製,剩下的就是在 computeScroll()里根據當前已經逝去的時間,獲取當前

                       應該偏移的座標(由Scroller實例對應的computeScrollOffset()計算而得),

                   第三、當前應該偏移的座標,調用scrollBy()方法去緩慢移動至該座標處。

 

  截圖如下:



                                                         


                                         原始界面                                     點擊按鈕或者觸摸屏之後的顯示界面


        附:由於滑動截屏很難,只是簡單的截取了兩個個靜態圖片,觸摸的話可以實現左右滑動切屏了。


           更多知識點,請看代碼註釋。。

 

  1. //自定義ViewGroup , 包含了三個LinearLayout控件,存放在不同的佈局位置,通過scrollBy或者scrollTo方法切換  
  2. public class MultiViewGroup extends ViewGroup {  
  3.     ...  
  4.     //startScroll開始移至下一屏  
  5.     public void startMove(){  
  6.         curScreen ++ ;  
  7.         Log.i(TAG, "----startMove---- curScreen " + curScreen);  
  8.           
  9.         //使用動畫控制偏移過程 , 3s內到位  
  10.         mScroller.startScroll((curScreen-1) * getWidth(), 0, getWidth(), 0,3000);  
  11.         //其實點擊按鈕的時候,系統會自動重新繪製View,我們還是手動加上吧。  
  12.         invalidate();  
  13.         //使用scrollTo一步到位  
  14.         //scrollTo(curScreen * MultiScreenActivity.screenWidth, 0);  
  15.     }  
  16.     // 由父視圖調用用來請求子視圖根據偏移值 mScrollX,mScrollY重新繪製  
  17.     @Override  
  18.     public void computeScroll() {     
  19.         // TODO Auto-generated method stub  
  20.         Log.e(TAG, "computeScroll");  
  21.         // 如果返回true,表示動畫還沒有結束  
  22.         // 因爲前面startScroll,所以只有在startScroll完成時 纔會爲false  
  23.         if (mScroller.computeScrollOffset()) {  
  24.             Log.e(TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());  
  25.             // 產生了動畫效果,根據當前值 每次滾動一點  
  26.             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
  27.               
  28.             Log.e(TAG, "### getleft is " + getLeft() + " ### getRight is " + getRight());  
  29.             //此時同樣也需要刷新View ,否則效果可能有誤差  
  30.             postInvalidate();  
  31.         }  
  32.         else  
  33.             Log.i(TAG, "have done the scoller -----");  
  34.     }  
  35.     //馬上停止移動,如果已經超過了下一屏的一半,我們強制滑到下一個屏幕  
  36.     public void stopMove(){  
  37.           
  38.         Log.v(TAG, "----stopMove ----");  
  39.           
  40.         if(mScroller != null){  
  41.             //如果動畫還沒結束,我們就按下了結束的按鈕,那我們就結束該動畫,即馬上滑動指定位置  
  42.             if(!mScroller.isFinished()){  
  43.                   
  44.                 int scrollCurX= mScroller.getCurrX() ;  
  45.                 //判斷是否超過下一屏的中間位置,如果達到就抵達下一屏,否則保持在原屏幕  
  46.                 // 這樣的一個簡單公式意思是:假設當前滑屏偏移值即 scrollCurX 加上每個屏幕一半的寬度,除以每個屏幕的寬度就是  
  47.                 //  我們目標屏所在位置了。 假如每個屏幕寬度爲320dip, 我們滑到了500dip處,很顯然我們應該到達第二屏,索引值爲1  
  48.                 //即(500 + 320/2)/320 = 1  
  49.                 int descScreen = ( scrollCurX + getWidth() / 2) / getWidth() ;  
  50.                   
  51.                 Log.i(TAG, "-mScroller.is not finished scrollCurX +" + scrollCurX);  
  52.                 Log.i(TAG, "-mScroller.is not finished descScreen +" + descScreen);  
  53.                 mScroller.abortAnimation();  
  54.   
  55.                 //停止了動畫,我們馬上滑倒目標位置  
  56.                 scrollTo(descScreen *getWidth() , 0);  
  57.                 curScreen = descScreen ; //糾正目標屏位置  
  58.             }  
  59.             else  
  60.                 Log.i(TAG, "----OK mScroller.is  finished ---- ");  
  61.         }     
  62.     }  
  63.     ...  
  64. }  

 如何實現觸摸滑屏? 


      其實網上有很多關於Launcher實現滑屏的博文,基本上也把道理闡釋的比較明白了 。我這兒也是基於自己的理解,將一些

 重要方面的知識點給補充下,希望能幫助大家理解。


      想要實現滑屏操作,值得考慮的事情包括如下幾個方面:


        其中:onInterceptTouchEvent()主要功能是控制觸摸事件的分發,例如是子視圖的點擊事件還是滑動事件。

        其他所有處理過程均在onTouchEvent()方法裏實現了。

            1、屏幕的滑動要根據手指的移動而移動  ---- 主要實現在onTouchEvent()方法中

            2、當手指鬆開時,可能我們並沒有完全滑動至某個屏幕上,這是我們需要手動判斷當前偏移至去計算目標屏(當前屏或者

               前後屏),並且優雅的偏移到目標屏(當然是用Scroller實例咯)。

           3、調用computeScroll ()去實現緩慢移動過程。

 

  知識點介紹:              

    VelocityTracker類

           功能:  根據觸摸位置計算每像素的移動速率。

           常用方法有:     

                     public void addMovement (MotionEvent ev)

                   功能:添加觸摸對象MotionEvent , 用於計算觸摸速率。   

            public void computeCurrentVelocity (int units)

                   功能:以每像素units單位考覈移動速率。額,其實我也不太懂,賦予值1000即可。
                   參照源碼 該units的意思如下:
                           參數 units : The units you would like the velocity in.  A value of 1
                             provides pixels per millisecond, 1000 provides pixels per second, etc.

            public float getXVelocity ()

                           功能:獲得X軸方向的移動速率。

    ViewConfiguration類

           功能: 獲得一些關於timeouts(時間)、sizes(大小)、distances(距離)的標準常量值 。

           常用方法:

                  public int getScaledEdgeSlop()

                      說明:獲得一個觸摸移動的最小像素值。也就是說,只有超過了這個值,才代表我們該滑屏處理了。

                 public static int getLongPressTimeout()

                     說明:獲得一個執行長按事件監聽(onLongClickListener)的值。也就是說,對某個View按下觸摸時,只有超過了

         這個時間值在,才表示我們該對該View回調長按事件了;否則,小於這個時間點鬆開手指,只執行onClick監聽

 

 

        我能寫下來的也就這麼多了,更多的東西參考代碼註釋吧。 在掌握了上面我羅列的知識後(重點scrollTo、Scroller類),

    其他方面的知識都是關於點與點之間的計算了以及觸摸事件的分發了。這方面感覺也沒啥可寫的。

  1. //自定義ViewGroup , 包含了三個LinearLayout控件,存放在不同的佈局位置,通過scrollBy或者scrollTo方法切換  
  2. public class MultiViewGroup extends ViewGroup {  
  3.   
  4.     private static String TAG = "MultiViewGroup";  
  5.       
  6.     private int curScreen = 0 ;  //當前屏幕  
  7.     private Scroller mScroller = null ; //Scroller對象實例  
  8.       
  9.     public MultiViewGroup(Context context) {  
  10.         super(context);  
  11.         mContext = context;  
  12.         init();  
  13.     }  
  14.     public MultiViewGroup(Context context, AttributeSet attrs) {  
  15.         super(context, attrs);  
  16.         mContext = context;  
  17.         init();  
  18.     }  
  19.     //初始化  
  20.     private void init() {     
  21.         ...  
  22.         //初始化Scroller實例  
  23.         mScroller = new Scroller(mContext);  
  24.         // 初始化3個 LinearLayout控件  
  25.         ...  
  26.         //初始化一個最小滑動距離  
  27.         mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();  
  28.     }  
  29.     // 由父視圖調用用來請求子視圖根據偏移值 mScrollX,mScrollY重新繪製  
  30.     @Override  
  31.     public void computeScroll() {     
  32.         // TODO Auto-generated method stub  
  33.         Log.e(TAG, "computeScroll");  
  34.         // 如果返回true,表示動畫還沒有結束  
  35.         // 因爲前面startScroll,所以只有在startScroll完成時 纔會爲false  
  36.         if (mScroller.computeScrollOffset()) {  
  37.             Log.e(TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());  
  38.             // 產生了動畫效果,根據當前值 每次滾動一點  
  39.             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
  40.               
  41.             Log.e(TAG, "### getleft is " + getLeft() + " ### getRight is " + getRight());  
  42.             //此時同樣也需要刷新View ,否則效果可能有誤差  
  43.             postInvalidate();  
  44.         }  
  45.         else  
  46.             Log.i(TAG, "have done the scoller -----");  
  47.     }  
  48.     //兩種狀態: 是否處於滑屏狀態  
  49.     private static final int TOUCH_STATE_REST = 0;  //什麼都沒做的狀態  
  50.     private static final int TOUCH_STATE_SCROLLING = 1;  //開始滑屏的狀態  
  51.     private int mTouchState = TOUCH_STATE_REST; //默認是什麼都沒做的狀態  
  52.     //--------------------------   
  53.     //處理觸摸事件 ~  
  54.     public static int  SNAP_VELOCITY = 600 ;  //最小的滑動速率  
  55.     private int mTouchSlop = 0 ;              //最小滑動距離,超過了,才認爲開始滑動  
  56.     private float mLastionMotionX = 0 ;       //記住上次觸摸屏的位置  
  57.     //處理觸摸的速率  
  58.     private VelocityTracker mVelocityTracker = null ;  
  59.       
  60.     // 這個感覺沒什麼作用 不管true還是false 都是會執行onTouchEvent的 因爲子view裏面onTouchEvent返回false了  
  61.     @Override  
  62.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  63.         // TODO Auto-generated method stub  
  64.         Log.e(TAG, "onInterceptTouchEvent-slop:" + mTouchSlop);  
  65.   
  66.         final int action = ev.getAction();  
  67.         //表示已經開始滑動了,不需要走該Action_MOVE方法了(第一次時可能調用)。  
  68.         //該方法主要用於用戶快速鬆開手指,又快速按下的行爲。此時認爲是出於滑屏狀態的。  
  69.         if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {  
  70.             return true;  
  71.         }  
  72.           
  73.         final float x = ev.getX();  
  74.         final float y = ev.getY();  
  75.   
  76.         switch (action) {  
  77.         case MotionEvent.ACTION_MOVE:  
  78.             Log.e(TAG, "onInterceptTouchEvent move");  
  79.             final int xDiff = (int) Math.abs(mLastionMotionX - x);  
  80.             //超過了最小滑動距離,就可以認爲開始滑動了  
  81.             if (xDiff > mTouchSlop) {  
  82.                 mTouchState = TOUCH_STATE_SCROLLING;  
  83.             }  
  84.             break;  
  85.   
  86.         case MotionEvent.ACTION_DOWN:  
  87.             Log.e(TAG, "onInterceptTouchEvent down");  
  88.             mLastionMotionX = x;  
  89.             mLastMotionY = y;  
  90.             Log.e(TAG, mScroller.isFinished() + "");  
  91.             mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;  
  92.   
  93.             break;  
  94.   
  95.         case MotionEvent.ACTION_CANCEL:  
  96.         case MotionEvent.ACTION_UP:  
  97.             Log.e(TAG, "onInterceptTouchEvent up or cancel");  
  98.             mTouchState = TOUCH_STATE_REST;  
  99.             break;  
  100.         }  
  101.         Log.e(TAG, mTouchState + "====" + TOUCH_STATE_REST);  
  102.         return mTouchState != TOUCH_STATE_REST;  
  103.     }  
  104.     public boolean onTouchEvent(MotionEvent event){  
  105.   
  106.         super.onTouchEvent(event);  
  107.           
  108.         Log.i(TAG, "--- onTouchEvent--> " );  
  109.   
  110.         // TODO Auto-generated method stub  
  111.         Log.e(TAG, "onTouchEvent start");  
  112.         //獲得VelocityTracker對象,並且添加滑動對象  
  113.         if (mVelocityTracker == null) {  
  114.             mVelocityTracker = VelocityTracker.obtain();  
  115.         }  
  116.         mVelocityTracker.addMovement(event);  
  117.           
  118.         //觸摸點  
  119.         float x = event.getX();  
  120.         float y = event.getY();  
  121.         switch(event.getAction()){  
  122.         case MotionEvent.ACTION_DOWN:  
  123.             //如果屏幕的動畫還沒結束,你就按下了,我們就結束上一次動畫,即開始這次新ACTION_DOWN的動畫  
  124.             if(mScroller != null){  
  125.                 if(!mScroller.isFinished()){  
  126.                     mScroller.abortAnimation();   
  127.                 }  
  128.             }  
  129.             mLastionMotionX = x ; //記住開始落下的屏幕點  
  130.             break ;  
  131.         case MotionEvent.ACTION_MOVE:  
  132.             int detaX = (int)(mLastionMotionX - x ); //每次滑動屏幕,屏幕應該移動的距離  
  133.             scrollBy(detaX, 0);//開始緩慢滑屏咯。 detaX > 0 向右滑動 , detaX < 0 向左滑動 ,  
  134.               
  135.             Log.e(TAG, "--- MotionEvent.ACTION_MOVE--> detaX is " + detaX );  
  136.             mLastionMotionX = x ;  
  137.             break ;  
  138.         case MotionEvent.ACTION_UP:  
  139.               
  140.             final VelocityTracker velocityTracker = mVelocityTracker  ;  
  141.             velocityTracker.computeCurrentVelocity(1000);  
  142.             //計算速率  
  143.             int velocityX = (int) velocityTracker.getXVelocity() ;    
  144.             Log.e(TAG , "---velocityX---" + velocityX);  
  145.               
  146.             //滑動速率達到了一個標準(快速向右滑屏,返回上一個屏幕) 馬上進行切屏處理  
  147.             if (velocityX > SNAP_VELOCITY && curScreen > 0) {  
  148.                 // Fling enough to move left  
  149.                 Log.e(TAG, "snap left");  
  150.                 snapToScreen(curScreen - 1);  
  151.             }  
  152.             //快速向左滑屏,返回下一個屏幕)  
  153.             else if(velocityX < -SNAP_VELOCITY && curScreen < (getChildCount()-1)){  
  154.                 Log.e(TAG, "snap right");  
  155.                 snapToScreen(curScreen + 1);  
  156.             }  
  157.             //以上爲快速移動的 ,強制切換屏幕  
  158.             else{  
  159.                 //我們是緩慢移動的,因此先判斷是保留在本屏幕還是到下一屏幕  
  160.                 snapToDestination();  
  161.             }  
  162.             //回收VelocityTracker對象  
  163.             if (mVelocityTracker != null) {  
  164.                 mVelocityTracker.recycle();  
  165.                 mVelocityTracker = null;  
  166.             }  
  167.             //修正mTouchState值  
  168.             mTouchState = TOUCH_STATE_REST ;  
  169.               
  170.             break;  
  171.         case MotionEvent.ACTION_CANCEL:  
  172.             mTouchState = TOUCH_STATE_REST ;  
  173.             break;  
  174.         }  
  175.           
  176.         return true ;  
  177.     }  
  178.     ////我們是緩慢移動的,因此需要根據偏移值判斷目標屏是哪個?  
  179.     private void snapToDestination(){  
  180.         //當前的偏移位置  
  181.         int scrollX = getScrollX() ;  
  182.         int scrollY = getScrollY() ;  
  183.           
  184.         Log.e(TAG, "### onTouchEvent snapToDestination ### scrollX is " + scrollX);  
  185.         //判斷是否超過下一屏的中間位置,如果達到就抵達下一屏,否則保持在原屏幕      
  186.         //直接使用這個公式判斷是哪一個屏幕 前後或者自己  
  187.         //判斷是否超過下一屏的中間位置,如果達到就抵達下一屏,否則保持在原屏幕  
  188.         // 這樣的一個簡單公式意思是:假設當前滑屏偏移值即 scrollCurX 加上每個屏幕一半的寬度,除以每個屏幕的寬度就是  
  189.         //  我們目標屏所在位置了。 假如每個屏幕寬度爲320dip, 我們滑到了500dip處,很顯然我們應該到達第二屏  
  190.         int destScreen = (getScrollX() + MultiScreenActivity.screenWidth / 2 ) / MultiScreenActivity.screenWidth ;  
  191.           
  192.         Log.e(TAG, "### onTouchEvent  ACTION_UP### dx destScreen " + destScreen);  
  193.           
  194.         snapToScreen(destScreen);  
  195.     }  
  196.     //真正的實現跳轉屏幕的方法  
  197.     private void snapToScreen(int whichScreen){   
  198.         //簡單的移到目標屏幕,可能是當前屏或者下一屏幕  
  199.         //直接跳轉過去,不太友好  
  200.         //scrollTo(mLastScreen * MultiScreenActivity.screenWidth, 0);  
  201.         //爲了友好性,我們在增加一個動畫效果  
  202.         //需要再次滑動的距離 屏或者下一屏幕的繼續滑動距離  
  203.           
  204.         curScreen = whichScreen ;  
  205.         //防止屏幕越界,即超過屏幕數  
  206.         if(curScreen > getChildCount() - 1)  
  207.             curScreen = getChildCount() - 1 ;  
  208.         //爲了達到下一屏幕或者當前屏幕,我們需要繼續滑動的距離.根據dx值,可能想左滑動,也可能像又滑動  
  209.         int dx = curScreen * getWidth() - getScrollX() ;  
  210.           
  211.         Log.e(TAG, "### onTouchEvent  ACTION_UP### dx is " + dx);  
  212.           
  213.         mScroller.startScroll(getScrollX(), 0, dx, 0,Math.abs(dx) * 2);  
  214.           
  215.         //由於觸摸事件不會重新繪製View,所以此時需要手動刷新View 否則沒效果  
  216.         invalidate();  
  217.     }  
  218.     //開始滑動至下一屏  
  219.     public void startMove(){  
  220.         ...       
  221.     }  
  222.     //理解停止滑動  
  223.     public void stopMove(){  
  224.         ...  
  225.     }  
  226.     // measure過程  
  227.     @Override  
  228.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  229.        ...  
  230.     }  
  231.     // layout過程  
  232.     @Override  
  233.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  234.         ...  
  235.     }  
  236. }  

   最後,希望大家能多多實踐,最好能寫個小Demo出來。那樣才正在的掌握了所學所得。

 


   本文代碼示例下載地址:


                         http://download.csdn.net/detail/qinjuning/4194324 (二合一的Demo)

 

     最後, 請一定要自己實踐理解。否則,效果也不會很好。


發佈了34 篇原創文章 · 獲贊 115 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章