Android應用開發之自定義View觸摸相關工具類全解
背景
最近有些亂,各種事情,各種交叉。好在還有一點上進心,于是繼續將自定義這個系列的核心知識再梳理一下吧。關于自定義控件前面博文說過了,這里不會教你拿來主義,只授之以漁,如果你喜歡拿來主義,不好意思,請繞行,如果你喜歡得漁,那請繼續。
前面我們已經敘述過了幾篇關于自定義View涉及的東西,大家可以自己回過頭去看我之前的博客,譬如事件處理、坐標系、工具類等。下面我們還是繼續補充一些常用的自定義控件工具類。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】
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,使用時請注意一下。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】
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,使用時請留意一下。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】
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; }
關于速率檢測類的知識就介紹到這里,沒啥新鮮的。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】
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手勢創建識別類的,這里不作介紹,作為拓展。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】
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的觸摸事件傳遞也就總結完成了,使用中牢記這些準則即可,當然也推薦查看源碼。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】
總結
可以看見,關于自定義控件的基礎觸摸相關的東西其實差不多也就這么多了,有了上面這些玩意你也基本上就能夠玩轉Android自定義控件觸摸相關的蛋疼處理了,不用再苦苦思索了。
這一篇博文沒有附帶任何例子,因為是一個總結性的文章,相關總結到的東西在我前面的博文中基本都有深入的分析文章,所以如果你感興趣可以翻翻我之前的博文,謝謝喏。
【工匠若水 http://blog.csdn.net/yanbober 未經允許嚴禁轉載,請尊重作者勞動成果。私信聯系我】