Android的Scroller介紹 滑動


Android的Scroller介紹

 

正文

  一、結構

    public class Scroller extends Object

 

    java.lang.Object

      android.widget.Scroller

 

  二、概述

    這個類封裝了滾動操作。滾動的持續時間可以通過構造函數傳遞,並且可以指定滾動動作的持續的最長時間。經過這段時間,滾動會自動定位到最終位置,並且通過computeScrollOffset()會得到的返回值為false,表明滾動動作已經結束。
 

  三、構造函數

  public Scroller (Context context)

  使用缺省的持續持續時間和動畫插入器創建一個Scroller。(譯者注:interpolator這里翻譯為動畫插入器,見這里。)

 

  public Scroller (Context context, Interpolator interpolator)

  根據指定的動畫插入器創建一個Scroller,如果指定的動畫插入器為空,則會使用缺省的動畫插入器(粘滯viscous)創建。

 

  四、公共方法

  public void abortAnimation ()

  停止動畫。與forceFinished(boolean)相反,Scroller滾動到最終xy位置時中止動畫。

  參見

        forceFinished(boolean)

 

  public boolean computeScrollOffset ()

  當想要知道新的位置時,調用此函數。如果返回true,表示動畫還沒有結束。位置改變以提供一個新的位置。

 

  public void extendDuration (int extend)

  延長滾動動畫時間。此函數允許當使用setFinalX(int) or setFinalY(int) 時,卷動動作持續更長時間並且卷動更長距離。

          參數

              extend 卷動事件延長的時間,以毫秒為單位

          參見

              setFinalX(int)

              setFinalY(int)

 

  public void fling (int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)

  在fling(譯者注:快滑,用戶按下觸摸屏、快速移動后松開)手勢基礎上開始滾動。滾動的距離取決於fling的初速度。

      參數

          startX 滾動起始點X坐標

  startY 滾動起始點Y坐標

  velocityX   當滑動屏幕時X方向初速度,以每秒像素數計算

  velocityY   當滑動屏幕時Y方向初速度,以每秒像素數計算

  minX    X方向的最小值,scroller不會滾過此點。

  maxX    X方向的最大值,scroller不會滾過此點。

  minY    Y方向的最小值,scroller不會滾過此點。

  maxY    Y方向的最大值,scroller不會滾過此點。

 

  public final void forceFinished (boolean finished)

  強制終止的字段到特定值。(譯者注:立即停止滾動?)

      參數

          finished    新的結束值

 

  public final int getCurrX ()

  返回當前滾動X方向的偏移

      返回值

          距離原點X方向的絕對值

 

  public final int getCurrY ()

  返回當前滾動Y方向的偏移

      返回值

          距離原點Y方向的絕對值

 

  public final int getDuration ()

  返回滾動事件的持續時間,以毫秒計算。

      返回值

          滾動持續的毫秒數

 

  public final int getFinalX ()

  返回滾動結束位置。僅針對“fling”手勢有效

      返回值

          最終位置X方向距離原點的絕對距離

 

  public final int getFinalY ()

  返回滾動結束位置。僅針對“fling”操作有效

      返回值

          最終位置Y方向距離原點的絕對距離

 

  public final int getStartX ()

  返回滾動起始點的X方向的偏移

      返回值

          起始點在X方向距離原點的絕對距離

 

  public final int getStartY ()

  返回滾動起始點的Y方向的偏移

      返回值

          起始點在Y方向距離原點的絕對距離

 

  public final boolean isFinished ()

  返回scroller是否已完成滾動。

      返回值

          停止滾動返回true,否則返回false

 

  public void setFinalX (int newX)

  設置scroller的X方向終止位置

      參數

          newX    新位置在X方向距離原點的絕對偏移。

      參見

          extendDuration(int)

          setFinalY(int)

 

  public void setFinalY (int newY)

  設置scroller的Y方向終止位置

      參數

          newY    新位置在Y方向距離原點的絕對偏移。

      參見

          extendDuration(int)

          setFinalY(int)

 

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

  以提供的起始點和將要滑動的距離開始滾動。滾動會使用缺省值250ms作為持續時間。

      參數

          startX 水平方向滾動的偏移值,以像素為單位。負值表明滾動將向左滾動

  startY 垂直方向滾動的偏移值,以像素為單位。負值表明滾動將向上滾動

  dx 水平方向滑動的距離,負值會使滾動向左滾動

  dy 垂直方向滑動的距離,負值會使滾動向上滾動

 

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

  以提供的起始點和將要滑動的距離開始滾動。

      參數

          startX 水平方向滾動的偏移值,以像素為單位。負值表明滾動將向左滾動

  startY 垂直方向滾動的偏移值,以像素為單位。負值表明滾動將向上滾動

  dx 水平方向滑動的距離,負值會使滾動向左滾動

  dy 垂直方向滑動的距離,負值會使滾動向上滾動

        duration    滾動持續時間,以毫秒計。
 

  public int timePassed ()

  返回自滾動開始經過的時間

      返回值

            經過時間以毫秒為單位
 

  五、補充

    文章精選

      Scroller 粗淺理解

      ScrollTextView - scrolling TextView for Android

 

  Android里Scroller類是為了實現View平滑滾動的一個Helper類。通常在自定義的View時使用,在View中定義一個私有成員mScroller = new Scroller(context)。設置mScroller滾動的位置時,並不會導致View的滾動,通常是用mScroller記錄/計算View滾動的位置,再重寫View的computeScroll(),完成實際的滾動。 

      相關API介紹如下
 
Java代碼  收藏代碼
  1. mScroller.getCurrX() //獲取mScroller當前水平滾動的位置  
  2. mScroller.getCurrY() //獲取mScroller當前豎直滾動的位置  
  3. mScroller.getFinalX() //獲取mScroller最終停止的水平位置  
  4. mScroller.getFinalY() //獲取mScroller最終停止的豎直位置  
  5. mScroller.setFinalX(int newX) //設置mScroller最終停留的水平位置,沒有動畫效果,直接跳到目標位置  
  6. mScroller.setFinalY(int newY) //設置mScroller最終停留的豎直位置,沒有動畫效果,直接跳到目標位置  
  7.   
  8. //滾動,startX, startY為開始滾動的位置,dx,dy為滾動的偏移量, duration為完成滾動的時間  
  9. mScroller.startScroll(int startX, int startY, int dx, int dy) //使用默認完成時間250ms  
  10. mScroller.startScroll(int startX, int startY, int dx, int dy, int duration)  
  11.   
  12. mScroller.computeScrollOffset() //返回值為boolean,true說明滾動尚未完成,false說明滾動已經完成。這是一個很重要的方法,通常放在View.computeScroll()中,用來判斷是否滾動是否結束。  



      舉例說明,自定義一個CustomView,使用Scroller實現滾動: 

Java代碼  收藏代碼
  1. import android.content.Context;  
  2. import android.util.AttributeSet;  
  3. import android.util.Log;  
  4. import android.view.View;  
  5. import android.widget.LinearLayout;  
  6. import android.widget.Scroller;  
  7.   
  8. public class CustomView extends LinearLayout {  
  9.   
  10.     private static final String TAG = "Scroller";  
  11.   
  12.     private Scroller mScroller;  
  13.   
  14.     public CustomView(Context context, AttributeSet attrs) {  
  15.         super(context, attrs);  
  16.         mScroller = new Scroller(context);  
  17.     }  
  18.   
  19.     //調用此方法滾動到目標位置  
  20.     public void smoothScrollTo(int fx, int fy) {  
  21.         int dx = fx - mScroller.getFinalX();  
  22.         int dy = fy - mScroller.getFinalY();  
  23.         smoothScrollBy(dx, dy);  
  24.     }  
  25.   
  26.     //調用此方法設置滾動的相對偏移  
  27.     public void smoothScrollBy(int dx, int dy) {  
  28.   
  29.         //設置mScroller的滾動偏移量  
  30.         mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);  
  31.         invalidate();//這里必須調用invalidate()才能保證computeScroll()會被調用,否則不一定會刷新界面,看不到滾動效果  
  32.     }  
  33.       
  34.     @Override  
  35.     public void computeScroll() {  
  36.       
  37.         //先判斷mScroller滾動是否完成  
  38.         if (mScroller.computeScrollOffset()) {  
  39.           
  40.             //這里調用View的scrollTo()完成實際的滾動  
  41.             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
  42.               
  43.             //必須調用該方法,否則不一定能看到滾動效果  
  44.             postInvalidate();  
  45.         }  
  46.         super.computeScroll();  
  47.     }  
  48. }  

 

下面更深一點介紹Scrooler:

 

   友情提示:

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

 

             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軸方向的偏移坐標

 

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

 

[java]  view plain copy print ?
 
  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

 

[java]  view plain copy print ?
 
  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類中      

 

[java]  view plain copy print ?
 
  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()過程中,如下:

 

[java]  view plain copy print ?
 
  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()方法去緩慢移動至該坐標處。

 

  截圖如下:

 

 

                                                         

 

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

 

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

 

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

 

[java]  view plain copy print ?
 
  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處,很顯然我們應該到達第二屏  
  48.                 //即(500 + 320/2)/320 = 2  
  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類),

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

 

[java]  view plain copy print ?
 
  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/linghu_java/5573479

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

轉自http://m.blog.csdn.net/blog/linghu_java/9087841


免責聲明!

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



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