Android應用開發之自訂View觸摸相關工具類全解

來源:互聯網
上載者:User

Android應用開發之自訂View觸摸相關工具類全解
背景

最近有些亂,各種事情,各種交叉。好在還有一點上進心,於是繼續將自訂這個系列的核心知識再梳理一下吧。關於自訂控制項前面博文說過了,這裡不會教你拿來主義,只授之以漁,如果你喜歡拿來主義,不好意思,請繞行,如果你喜歡得漁,那請繼續。

前面我們已經敘述過了幾篇關於自訂View涉及的東西,大家可以自己回過頭去看我之前的部落格,譬如事件處理、座標系、工具類等。下面我們還是繼續補充一些常用的自訂控制項工具類。

ViewConfiguration基礎參數工具類

ViewConfiguration這個類主要提供了一些自訂控制項用到的標準常量,譬如尺寸、滑動距離、敏感度等,當我們自訂控制項時就可以直接使用他來避免自己做一些測試。下面是該類的源碼注視,使用時直接可以參考,沒啥特殊的邏輯東西,所以不再進行源碼分析。如下:

public class ViewConfiguration {    ......    //不推薦使用,推薦ViewConfiguration.get(Context)擷取執行個體    public ViewConfiguration() {}    public static ViewConfiguration get(Context context) {}    //不推薦使用,推薦getScaledScrollBarSize()代替;擷取水平捲軸的寬或垂直捲軸的高    public static int getScrollBarSize() {}    public int getScaledScrollBarSize() {}    //捲軸褪去消失的期間    public static int getScrollBarFadeDuration() {}    //捲軸消失的延遲時間    public static int getScrollDefaultDelay() {}    //不推薦使用,推薦getScaledFadingEdgeLength()代替;褪去邊緣的長度    public static int getFadingEdgeLength() {}    public int getScaledFadingEdgeLength() {}    //按下的期間長度    public static int getPressedStateDuration() {}    //按住狀態轉變為長按狀態需要的時間    public static int getLongPressTimeout() {}    //重新按鍵判斷時間    public static int getKeyRepeatTimeout() {}    //重複按鍵延遲的時間    public static int getKeyRepeatDelay() {}    //判斷是單擊還是滾動的時間,在這個時間內沒有移動則是單擊,否則是滾動    public static int getTapTimeout() {}    //在這個時間內沒有完成這個點擊,那麼就認為是一個點擊事件    public static int getJumpTapTimeout() {}    //得到雙擊間隔時間,在這個時間內是雙擊,否則是單擊    public static int getDoubleTapTimeout() {}    //不推薦使用,推薦getScaledEdgeSlop()代替;判斷是否滑動事件    public static int getEdgeSlop() {}    public int getScaledEdgeSlop() {}    //不推薦使用,推薦getScaledTouchSlop()代替;滑動的時候,手的移動要大於這個距離才算移動    public static int getTouchSlop() {}    public int getScaledTouchSlop() {}    //觸摸邊沿padding地區的判斷    public int getScaledPagingTouchSlop() {}    //不推薦使用,推薦getScaledDoubleTapSlop()代替;判斷是否雙擊的閾值    public static int getDoubleTapSlop() {}    public int getScaledDoubleTapSlop() {}    //不推薦使用,推薦getScaledWindowTouchSlop()代替;觸摸表單邊沿地區判斷    public static int getWindowTouchSlop() {}    public int getScaledWindowTouchSlop() {}    //不推薦使用,推薦getScaledMinimumFlingVelocity()代替;得到滑動的最小速度, 以像素/每秒來進行計算    public static int getMinimumFlingVelocity() {}    public int getScaledMinimumFlingVelocity() {}    //不推薦使用,推薦getScaledMaximumFlingVelocity()代替;得到滑動的最大速度, 以像素/每秒來進行計算    public static int getMaximumFlingVelocity() {}    public int getScaledMaximumFlingVelocity() {}    //不推薦使用,推薦getScaledMaximumDrawingCacheSize()代替;擷取最大的圖形可緩衝大小,單位bytes    public static int getMaximumDrawingCacheSize() {}    public int getScaledMaximumDrawingCacheSize() {}    ......}

有了上面這個工具類,我們在自訂控制項處理滑動手勢等判斷時就可以很方便的判斷出臨界值等問題,不用我們再去自己測試定義一個近似的值來代替。

特別注意: ViewConfiguration還有一個在support包中的相容類ViewConfigurationCompat,使用時請注意一下。

Scroller加強版OverScroller回彈工具類

之前有篇部落格說到了Scroller的源碼淺析,其實Scroller在API 1就出現了,而這裡要說的Scroller加強版OverScroller在API 9才出現,所以功能指定比之前的Scroller強大,支援了回彈效果(關於不同的回彈效果我們可以自訂不同的動畫插值器即可),不過原理基本和之前分析的Scroller源碼一樣,所以這裡我們不會再對OverScroller源碼分析,只對他和Scroller的差異進行說明,下面我們來看看。

OverScroller在Scroller類基礎上多出來的方法:

方法 含義
isOverScrolled() 返回當前的位置是否有效或者是否超出捲動界限。
springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) 當你想復原的時候調用這個方法,復原的範圍在有效座標範圍內。
fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY) 同Scroller的,只是最後兩個參數含義為fling滾動超過有效值的範圍。
notifyHorizontalEdgeReached(int startX, int finalX, int overX) 通知水平滾動是否到達邊界。
notifyVerticalEdgeReached(int startY, int finalY, int overY) 同上。

關於Scroller的基本使用流程可以參見我之前部落格Scroller源碼淺析和ViewDragHelper源碼淺析兩篇文章,如果需要深入理解可以看看官方ScrollView的實現,其就完全使用了OverScroller。

特別注意: Scroller(OverScroller)這貨也有一個在support相容包的相容類ScrollerCompat,使用時請留意一下。

VelocityTracker手勢速率工具類

VelocityTracker主要用跟蹤觸控螢幕事件(Flinging及其他Gestures手勢事件等)的速率。我們在拿到執行個體後可以通過computeCurrentVelocity(int)來初始化速率的單位,然後接著通過addMovement(MotionEvent)方法將MotionEvent加入VelocityTracker執行個體中,然後在需要的地方通過getXVelocity() 或getXVelocity()獲得橫向和豎向的速率即可。

下面給出相關的API說明(VelocityTracker許多方法都是native實現的):

public final class VelocityTracker {    //擷取VelocityTracker執行個體    static public VelocityTracker obtain() {}    public static VelocityTracker obtain(String strategy) {}    //回收後代表你不需要使用了,系統將此對象在此分配到其他要求者    public void recycle() {}    //清空回到初始狀態,computeCurrentVelocity都被reset了    public void clear() {}        //將事件加入到VelocityTracker類執行個體中    public void addMovement(MotionEvent event) {}    //unitis表示速率的基本時間單位,1表示一毫秒時間單位內運動了多少個像素    public void computeCurrentVelocity(int units) {}    //同上,floatVelocity表示速率的最大值,超過最大值的都返回最大值    public void computeCurrentVelocity(int units, float maxVelocity) {}        //擷取xy方向速率    public float getXVelocity() {}    public float getYVelocity() {}        //擷取xy速率,id為event的pointid    public float getXVelocity(int id) {}        public float getYVelocity(int id) {}}

有了上面這些手勢速率的偵查工具類,下面我們來看下他的一些通用模板:

VelocityTracker mVelocityTracker = null;  @Override    public boolean onTouchEvent(MotionEvent event){        int action = event.getAction();        switch(action){        case MotionEvent.ACTION_DOWN:            if(mVelocityTracker == null){            mVelocityTracker = VelocityTracker.obtain();            }else{            mVelocityTracker.clear();            }            mVelocityTracker.addMovement(event);            break;        case MotionEvent.ACTION_MOVE:            mVelocityTracker.addMovement(event);            mVelocityTracker.computeCurrentVelocity(1000);         Log.i("X = "+mVelocityTracker.getXVelocity());            Log.i("Y = "+mVelocityTracker.getYVelocity());            break;        case MotionEvent.ACTION_UP:        case MotionEvent.ACTION_CANCEL:            mVelocityTracker.recycle();            break;        }        return true;    } 

關於速率檢測類的知識就介紹到這裡,沒啥新鮮的。

GestureDetector手勢工具類

除了我們通過onTouchEvent()自己處理一堆複雜的手勢以外,其實Android給我們提供了現成的便捷方式,那就是GestureDetector手勢監聽類,如下:

public class GestureDetector {    public interface OnGestureListener {    //ACTION_DOWN時觸發         boolean onDown(MotionEvent e);    //ACTION_DOWN了過一會還沒有滑動時觸發,onDown->onShowPress->onLongPress        void onShowPress(MotionEvent e);    //ACTION_DOWN後沒有滑動(onScroll)且沒有長按(onLongPress)接著ACTION_UP時觸發        boolean onSingleTapUp(MotionEvent e);    //滑動時即時觸發        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);    //ACTION_DOWN長按時觸發        void onLongPress(MotionEvent e);    //觸摸滑動一定距離後鬆手ACTION_UP時觸發,後參數為速率        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);    }    public interface OnDoubleTapListener {    //ACTION_DOWN後沒有滑動(onScroll)且沒有長按(onLongPress)接著ACTION_UP時觸發        boolean onSingleTapConfirmed(MotionEvent e);    //雙擊的第二下ACTION_DOWN時觸發         boolean onDoubleTap(MotionEvent e);    //雙擊的第二下ACTION_DOWN和ACTION_UP都會觸發,e.getAction()區別        boolean onDoubleTapEvent(MotionEvent e);    }    public interface OnContextClickListener {    //context點擊觸發,與View#onGenericMotionEvent(MotionEvent)相關,不常用        boolean onContextClick(MotionEvent e);    }    public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,            OnContextClickListener {    ......    //OnGestureListener、OnDoubleTapListener、OnContextClickListener所有介面的預設實現    ......    }    //各種推薦的不推薦的構造方法    @Deprecated    public GestureDetector(OnGestureListener listener, Handler handler) {}    @Deprecated    public GestureDetector(OnGestureListener listener) {}    public GestureDetector(Context context, OnGestureListener listener) {}    public GestureDetector(Context context, OnGestureListener listener, Handler handler) {}    public GestureDetector(Context context, OnGestureListener listener, Handler handler,            boolean unused) {}    //其他兩類回調介面的設定,OnGestureListener必須在構造中就處理掉    public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {}    public void setContextClickListener(OnContextClickListener onContextClickListener) {}    //一些處理判斷方法    public void setIsLongpressEnabled(boolean isLongpressEnabled) {}    public boolean isLongpressEnabled() {}    public boolean onTouchEvent(MotionEvent ev) {}    public boolean onGenericMotionEvent(MotionEvent ev) {}}

有了上面這些GestureDetector手勢工具類的基本API介紹之後我們就可以各種使用了,沒啥特殊的介紹。

特別注意: 其實手勢相關的東西還有Gesture類等GestureOverlayView手勢建立識別類的,這裡不作介紹,作為拓展。

View及ViewGroup觸摸事件總結

關於View觸控螢幕事件的傳遞機制源碼分析其實可以參考我前面寫的部落格,這篇文章既然是總結,那就是只給出結論,相關分析請看前面的博文。

觸摸事件傳遞源Activity:

Activity的dispatchTouchEvent()方法將事件傳遞給它的根布局ViewGroup(即調用根布局ViewGroup的dispatchTouchEvent()方法,該方法會對事件進行如下情況處理:

如果根布局ViewGroup及其內部子布局控制項均沒處理(此時根布局ViewGroup的dispatchTouchEvent()方法返回false)則調用Activity自己的onTouchEvent()方法;如果Activity自己的onTouchEvent()方法仍然沒有處理(返回false)則該事件處理宣告結束。

如果根布局ViewGroup的dispatchTouchEvent()方法返回true則表明根布局中處理了這一次事件,此時就不會再調用Activity的onTouchEvent()方法了(因為Activity沒有父控制項且不能設定觸摸監聽OnTouchListener,所以沒有onInterceptTouchEvent()方法)。

觸摸事件傳遞View層級處理:

這裡所謂的View層級泛指其內部不包含子控制項(已經為最小控制項單位)的View,當該View的父級ViewGroup觸發該View的dispatchTouchEvent()方法時,由於該View沒有子控制項可以被繼續派發,所以事件只能自己調度自己相關方法。測試的調度如下:

如果該View註冊了OnTouchListener,則優先調用OnTouchListener的onTouch()方法,如果onTouch()方法返回false則繼續調運該View的onTouchEvent()方法,如果onTouch()方法返回true則該View的dispatchTouchEvent()方法直接返回true。

如果該View沒有註冊OnTouchListener則直接調用該View的onTouchEvent()方法,該方法返回true、false決定了該View的dispatchTouchEvent()方法傳回值。

觸摸事件傳遞ViewGroup層級處理:

ViewGroup的dispatchTouchEvent()方法被其父布局 (父ViewGroup或者Activity)調用,當前ViewGroup的dispatchTouchEvent()方法主要任務就是為子控制項派發事件(調運子控制項的dispatchTouchEvent()),同時向父級布局返回事件處理情況。

不過在當前ViewGroup的dispatchTouchEvent()方法向子控制項派發事件之前我們在當前ViewGroup裡是可以通過自己的onInterceptTouchEvent()方法來決定觸摸事件是否攔截(當前ViewGroup的onInterceptTouchEvent()返回true則不再傳遞給自己的子控制項,而是當前ViewGroup自己處理,接著將處理結果告訴父控制項;返回false則不攔截(繼續傳遞給子控制項,如果子控制項的dispatchTouchEvent()方法都返回false則ViewGroup就嘗試自己處理事件,然後告訴父布局自己處理的結果)。

一次完整的事件流程處理:

綜合從Activity到根ViewGroup到中間ViewGroup,再到View的事件處理流程,我們要注意其傳遞過程中的下面幾點:

一次完整的事件觸發可以分為ACTION_DOWN->[ACTION_MOVE]->ACTION_UP。當我們手指按下派發ACTION_DOWN事件時,如果我們當前層級的View或者ViewGroup的onTouch()或onTouchEvent()方法返回false,則當前層級的View或者ViewGroup的onTouch()或onTouchEvent()方法就再也接收不到其他事件了,直到下次新的觸摸事件(ACTION_DOWN)開始。

在一次完整的事件傳遞(ACTION_DOWN->[ACTION_MOVE]->ACTION_UP)過程中只要當前ViewGroup的onInterceptTouchEvent()方法有一次返回true則當前ViewGroup將會攔截這次事件傳遞的全部後續觸發事件,同時這些後續觸發事件都不會再觸發當前ViewGroup的onInterceptTouchEvent()方法(直到下次ACTION_DOWN來臨),同時向之前處理事件的子布局傳遞一個ACTION_CANCEL事件。如果當前ViewGroup的onInterceptTouchEvent()方法返回false,則本次傳遞的每個事件來臨時都會觸發當前ViewGroup的onInterceptTouchEvent()方法。

只有ViewGroup才有onInterceptTouchEvent()方法,因為最小單位的View不具備再往下派發事件的能力,它只會直接調用自己的onTouch()和onTouchEvent()方法。

當父ViewGroup截獲了當前傳遞事件,常理來說其內部的子布局View或者ViewGroup就不能夠再收到派發事件了;但是我們有一種方法可以阻止父ViewGroup截獲傳遞的事件(getParent().requestDisallowInterceptTouchEvent(true);),一旦子布局View或者ViewGroup收到觸摸事件後調用這個方法則父ViewGroup就不會再調用她自己的onInterceptTouchEvent()方法了,直到事件處理完畢再getParent().requestDisallowInterceptTouchEvent(false);即可。

到此View的觸摸事件傳遞也就總結完成了,使用中牢記這些準則即可,當然也推薦查看源碼。

總結

可以看見,關於自訂控制項的基礎觸摸相關的東西其實差不多也就這麼多了,有了上面這些玩意你也基本上就能夠玩轉Android自訂控制項觸摸相關的蛋疼處理了,不用再苦苦思索了。

這一篇博文沒有附帶任何例子,因為是一個總結性的文章,相關總結到的東西在我前面的博文中基本都有深入的分析文章,所以如果你感興趣可以翻翻我之前的博文,謝謝喏。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.