Android View系統那些事

本篇文章打算介紹下View的座標、自定義View的手勢檢測以及實現View內容滾動的幾種方式,但願對有須要的同窗有所幫助。java

View的座標

在自定義View中,常常須要處理各類座標之間的轉換,下圖展現了View中的各類座標: View座標系 簡單解釋下上圖的含義:spring

針對一個普通View:ide

  • getTop方法表示view自身的頂邊到其父佈局頂邊的距離
  • getLeft方法表示view自身的左邊到其父佈局左邊的距離
  • getRight方法表示view自身的右邊到其父佈局左邊的距離
  • getBottom方法表示view自身的底邊到其父佈局頂邊的距離。

在處理一個普通View的Touch事件時:函數

  • getX方法表示Touch事件在當前View座標系中X軸上的座標,即相對於view左邊緣的距離
  • getY方法表示Touch事件在當前View座標系中Y軸上的座標,即相對於View上邊緣的距離
  • getRawX方法表示Touch事件在屏幕座標系中X軸上的座標,即相對於整個屏幕左邊緣的距離
  • getRawY方法表示Touch事件在屏幕座標系中Y軸上的座標,即相對於整個屏幕上邊緣的距離。

GestureDetector手勢檢測

咱們在自定義View時,常常須要處理Touch事件,而GestureDetector就是一個輔助咱們檢測用戶單擊、雙擊、長按和滾動等行爲的工具類。工具

該工具類的使用也很簡單。首先建立一個GestureDetector對象,並把實現事件監聽接口的類做爲參數傳進到對象中。其中,能夠實現的事件監聽接口有兩個,分別是OnGestureListener接口負責監聽單擊、長按和滾動等事件,和OnDoubleTapListener接口負責監聽雙擊事件。佈局

GestureDetector mGestureDetector = new GestureDetector(實現上述事件監聽接口的類對象);
複製代碼

而後,在自定義View的onTouchEvent方法中,把事件處理權委託給GestureDetectorpost

mGestureDetector.onTouchEvent(event);
複製代碼

最後,根據具體狀況,在不一樣的事件回調方法中處理業務邏輯就OK了。動畫

下面重點介紹一下兩個事件監聽接口中的回調方法:spa

onDown

方法簽名:日誌

boolean onDown(MotionEvent e);
複製代碼

說明:每次ACTION_DOWN事件發生時,都會調用該方法,事件e表示該ACTION_DOWN事件。熟悉Android事件分發機制的同窗應該知道ACTION_DOWN事件的重要性。若在該方法中返回了false(表示不消費該事件),那麼後續的ACTION事件都再也不會分發到當前View。所以,通常狀況下,該方法要返回true。

onShowPress

方法簽名:

void onShowPress(MotionEvent e);
複製代碼

說明:若發生ACTION_DOWN事件後,在ViewConfiguration.TAP_TIMEOUT時間內,既沒有發生ACTION_UP事件,也沒有觸發onScroll滾動方法,那麼就會觸發該方法,表示對用戶行爲的反饋,事件e表示ACTION_DOWN事件。

這裏要特別注意和onDown方法的區別。onShowPress方法強調在一段時間內沒有鬆開或者滾動的行爲,纔會觸發。

onLongPress

方法簽名:

void onLongPress(MotionEvent e);
複製代碼

說明:若發生ACTION_DOWN事件後,在ViewConfiguration.TAP_TIMEOUT + ViewConfiguration.LONGPRESS_TIMEOUT時間內,既沒有發生ACTION_UP事件,也沒有觸發onScroll滾動方法,那麼就會觸發該方法,表示用戶的長按行爲,事件e表示ACTION_DOWN事件。

這裏結合前面兩個方法看一個例子:下面是一次長按事件的日誌:

GestureDetector長按事件

從日誌能夠看出,在個人Nexus 6p上,ACTION_DOWN事件後,100ms後觸發了onShowPress方法,600ms後觸發了onLongPress方法。這個時間對應於ViewConfiguration類中相應變量,即取決於Android系統,每一個手機均可能不一樣。

onSingleTapUp

方法簽名:

boolean onSingleTapUp(MotionEvent e);
複製代碼

說明:當用戶手指離開屏幕,生成UP事件時會觸發該方法,事件e表示ACTION_UP事件。

關於該方法有幾點須要注意:

  1. ACTION_DOWNACTION_UP事件之間,若觸發了onScroll滾動方法,則不會觸發該方法。
  2. 該方法在長按事件發生時(即上面的onLongPress方法),不會被觸發。
  3. 該方法在雙擊事件的第一次UP事件時會被觸發,但第二次UP事件時不會被觸發了。也就是說觸發該方法的緣由可能並非一個嚴格的單擊事件,也有多是雙擊事件中的第一個UP事件。

onDoubleTap

方法簽名:

boolean onDoubleTap(MotionEvent e);
複製代碼

說明:在必定時間內兩次單擊行爲構成一次雙擊操做。嚴格來講,若是第二次單擊行爲的DOWN事件時間和第一次單擊行爲的UP事件時間之差小於ViewConfiguration.DOUBLE_TAP_TIMEOUT,那麼則構成一次雙擊事件,事件e表示第一次單擊行爲的ACTION_DOWN事件。

有一點須要注意:觸發該方法的確切時機是第二次單擊行爲的ACTION_DOWN事件,而不是ACTION_UP事件。也就是說雙擊事件是第二次按下屏幕的時候觸發,而不是第二次離開屏幕的時候觸發。關於雙擊事件下面還會繼續介紹。

onSingleTapConfirmed

方法簽名:

boolean onSingleTapConfirmed(MotionEvent e);
複製代碼

說明:嚴格的單擊操做,若該方法被觸發,則說明這是一個嚴格的單擊行爲,那麼後面不可能再緊跟着另外一個單擊行爲,即這隻多是單擊,而不多是雙擊事件中的一次單擊(這點是和onSingleTapUp方法的主要區別)。

這裏要說下雙擊操做和嚴格的單擊操做是怎麼實現的,首先看一個示意圖:

雙擊操做和嚴格的單擊操做示意圖

如圖所示,A一、A2和A3分別表示三個時間點,時間線之上的兩個變量分別表示A2和A3時間點的系統值,時間線之下的數值則是在Nexus 6P上的具體取值。

實際上在ACTION_DOWN事件發生後,系統會發出兩個延時Message(假設爲M1和M2),分別在A2和A3時間點觸發。 其中,A2時間點的M1用於實現雙擊操做:若第二次單擊操做發生時,M1尚未執行,那麼說明在A1 ~ A2之間發生了兩次單擊操做,則構成了一次雙擊事件。 A3時間點的M2用於實現長按事件:若在A1 ~ A3時間內,沒有觸發onScroll滾動方法,也沒有發生ACTION_UP事件,那麼在A3時間點就會觸發onLongPress方法。

而觸發onSingleTapConfirmed方法的時機有兩個:

  1. 若在A1 ~ A2之間只有一次單擊行爲(即只有一次ACTION_UP事件,且沒有觸發onScroll滾動方法),那麼就會在A2時間點會觸發onSingleTapConfirmed方法,表示這是一次嚴格的單擊操做。之因此要等到A2時間點,而不是UP事件發生時就觸發該方法,是由於在該次UP事件有多是雙擊事件中的第一次單擊操做,只有等到A2時間點,才能肯定這是一次嚴格的單擊操做,而不是雙擊操做的一部分。此時,方法簽名中的事件e表示ACTION_DOWN事件。

  2. 若惟一的一次單擊行爲發生在A2 ~ A3之間(即只有一次ACTION_UP事件,且沒有觸發onScroll滾動方法),那麼在ACTION_UP事件發生時,就會觸發onSingleTapConfirmed方法,表示這是一次嚴格的單擊操做。此時,方法簽名中的事件e表示ACTION_UP事件。

總結一下,只有在[A2,A3)時間區間內,纔會觸發onSingleTapConfirmed方法,以代表這是一次嚴格的單擊操做。並且雙擊操做和嚴格的單擊操做是不可能同時發生的。

onDoubleTapEvent

方法簽名:

boolean onDoubleTapEvent(MotionEvent e);
複製代碼

說明:在觸發onDoubleTap方法以後,就能夠在onDoubleTapEvent方法中監聽到雙擊事件發生後,從按下到彈起的全部觸屏事件。也就是說雙擊事件中的第二次單擊行爲的ACTION_DOWN事件以後的全部ACTION事件都會觸發該方法

onScroll

方法簽名:

/** 該方法在滾動期間,會屢次被調用 e1表示初始的ACTION_DOWN事件 e2表示滾動過程當中的ACTION_MOVE事件 distanceX爲本次在X軸上的滾動距離 distanceY爲本次在Y軸上的滾動距離 這裏的滾動距離是相對於上一次onScroll事件的距離,而不是e1 down事件和e2 move事件的距離 */
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
複製代碼

說明:手指按下屏幕並滾動會觸發該方法,即由初始的DOWN事件和一些列的MOVE事件驅動該方法。

onFling

方法簽名:

/** e1表示初始的ACTION_DOWN事件 e2表示手指離開View時的ACTION_UP事件 veloctiyX,velocitY爲手指離開當前View時的滾動速度,以這兩個速度爲初始速度作勻減速運動,就是如今快速拖動列表後的延遲滾動效果。 */
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
複製代碼

說明:在快速滾動屏幕,擡起手指後,滾動效果並不會當即中止。而是會以當前的滾動速度,作勻減速運動,直接速度爲0,纔會中止滾動。onFling方法就是在手指離開屏幕時觸發的。

View的內容滾動

這裏討論的View滾動指的是View內容的滾動,而不是View位置的移動。當View的內容過長時,經過Scroll的方式,能夠一點點的查看。View的內容滾動實際是修改View的mScrollXmScrollY屬性。

要實現對View的滾動,能夠經過View的scrollTo或者scrollBy方法來實現(scrollBy最終也是經過scrollBy來實現的)。同時,View滾動後,會回調onScrollChanged方法。以下所示:

//這裏是滾動到具體的位置
 public void scrollTo(int x, int y) {
    if (mScrollX != x || mScrollY != y) {
        int oldX = mScrollX;
        int oldY = mScrollY;
        mScrollX = x;
        mScrollY = y;
        invalidateParentCaches();
        //回調方法
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
        if (!awakenScrollBars()) { //喚醒滾動條
            postInvalidateOnAnimation();
        }
    }
}
複製代碼

這裏要明確下mScrollX和mScrollY屬性的具體含義:

  1. mScrollX:View左邊緣和View內容左邊緣在水平方向上的距離,而且當View內容左邊緣在View左邊緣的左邊時,mScrollX爲正值,不然爲負值。
  2. mScrollY:View上邊緣和View內容上邊緣在垂直方向上的距離,而且當View內容上邊緣在View上邊緣的上邊時,mScrollY爲正值,不然爲負值。

下面來看個幾個例子,以下所示:

ScrollX
上圖是分別在X軸上滾動100和-100的示意圖。

ScrollY
上圖是分別在Y軸上滾動100和-100的示意圖。

ScrollXY
上圖是分別在X軸和Y軸上滾動100和-100的示意圖。

經過上面幾個示意圖,應該對mScrollXmScrollY屬性有必定認識了。

OverScroller類三個滾動相關的方法

下面看下如何經過OverScroller類實現View內容的彈性滑動。

上面介紹的scrollTo方法會瞬間把View內容滑動到指定位置,沒有任何動畫效果,顯得比較突兀。可是咱們能夠經過OverScroller(OverScroller是Scroller的超集,在Scroller的基礎上添加了對OverScroll的支持)實現彈性滑動。典型的實現以下所示:

OverScroller mScroller = new OverScroller(mContext);

public void smoothScrollTo(int destX,int destY,int duration){
    //計算出須要滾動的距離
    int deltaX = destX - getScrollX();
    int deltaY = destY - getScrollY();
    //開始滾動(表示在指定時間內,滾動指定的偏移量。)
    mScroller.startScroll(getScrollX(),getScrollY(),deltaX,deltaY,duration);
    invalidate();
}

//須要在自定義View中實現該方法
@Override
public void computeScroll(){
    if(mScroller.computeScrollOffset()){ //判斷滾動是否結束
        //真正的滾動操做
        scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
        postInvalidate();
    }
}
複製代碼

整個彈性滾動是靠着View繪製來驅動的,當調用OverScroller.startScroll()方法後,會觸發View重繪,而在View.draw方法中就會調用上面的computeScroll方法。正如上述代碼所示,在computeScroll方法中,獲取當前的scrollX和scrollY,而後經過scrollTo來實現滾動,接着又經過postInvalidate方法觸發下一次重繪,如此反覆,直到整個滾動過程結束。


仔細看下OverScroller的API,就會發現除了能夠經過startScroll方法實現彈性滾動外,還有兩個特殊的API:flingspringBack,它們能夠分別實現Fling效果和回彈效果。

所謂Fling效果,就是當咱們快速滑動列表時,手指離開屏幕後,列表會以當前的滾動速度,以一個摩擦係數,作勻減速運動,直到滾動速度爲0爲止。 看下fling的方法簽名:

/** startX和startY表示初始的滾動值,通常設置爲當前的mScrollX和mScrollY便可 velocityX和velocityY分別表示X軸和Y軸上的速度,單位:像素/秒。 minX和maxX表示在X軸上最小和最大的滾動值,即最終的mScrollX值要在這個區間以內。 minY和maxY表示在Y軸上最小和最大的滾動值,即最終的mScrollY值要在這個區間以內。 overX表示在X軸上能夠過分滾動的長度,也就是說,若mScrollX達到了[minX,maxX]的邊界值,可是滾動速度還不爲0,那麼能夠在邊界值的基礎上再滾動overX距離,可是最終仍是會回彈到[minX,maxX]區間,即會有一個回彈的效果。 overY表示在Y軸上能夠過分滾動的長度,也就是說,若mScrollY達到了[minY,maxY]的邊界值,可是滾動速度還不爲0,那麼能夠在邊界值的基礎上再滾動overY距離,可是最終仍是會回彈到[minY,maxY]區間,即會有一個回彈的效果。 */
public void fling(int startX, int startY, int velocityX, int velocityY,int minX, int maxX, int minY, int maxY, int overX, int overY);
複製代碼

fling方法的參數含義如上,下面看一個具體的案例:

以5000/S的速度在Y軸上進行fling,最終中止在[0,300]這個mScrollY區間,過分滾動的距離overY爲300。 核心代碼以下所示

mScroller.fling(getScrollX(), getScrollY(), 0, 5000, 0, 0, 0, 300, 0, 300);
myView.invalidate();
複製代碼

具體的效果以下所示: fling效果圖

從上圖能夠看到,由於速度過大,當mScrollY達到邊界300時,又滾動了一段距離(overY),可是最終又回彈到了邊界值。

所以,咱們能夠經過OverScroller.fling方法模擬這種帶有回彈效果的fling過程。


最後一個是用於實現回彈效果的springBack方法。 首先看下方法簽名:

/** startX和startY表示初始的滾動值,通常設置爲當前的mScrollX和mScrollY便可 minX和maxX表示在X軸上最小和最大的滾動值,即最終的mScrollX值要在這個區間以內。 minY和maxY表示在Y軸上最小和最大的滾動值,即最終的mScrollY值要在這個區間以內。 minX,maxX,minY,maxY這4個滾動值構成了一個矩形區域。表示回彈後的(mScrollX,mScrollY)要在這個矩形區域內。 返回值若爲true,則表示初始的startX和startY還不在最終的矩形區間內,須要進行回彈;若爲false,則表示初始的startX和startY已經在最終的矩形區間內,不須要進行回彈了。 */
public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY);
複製代碼

springBack方法的參數含義如上,下面看一個具體的案例: 首先把View滾動到(0,-500)位置,而後把mScrollY回彈到[0,100]區間,核心代碼以下所示:

//首先在2S內把mScrollX和mScrollY滾動到(0,-500)
myView.smoothScrollTo(0,-500,2000)

//在當前的位置上進行回彈,目標是mScrollX爲0,mScrollY落在[0,100]區間內
if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, 100)) {
    myView.invalidate();
}
複製代碼

具體的效果以下所示: springBack效果圖

從上圖能夠看出,mScrollX回彈到0就結束了(只要落在[minY,maxY]區間內就能夠了)。

所以,經過OverScroller.springBack方法,咱們能夠垂手可得的把View的內容回彈到咱們指望的位置。

OK,至此``OverScroller類三個滾動相關的方法都介紹完了。它們有一個共同點:這三個函數都僅是初始化函數,都是藉助View的不斷重繪,經過View.computeScroll方法和OverScroller.computeScrollOffset`方法,把每一幀的scrollX和scrollY值,設置到View上,達到滾動View內容的目的。

相關文章
相關標籤/搜索