android觸摸事件
『壹』 Android Touch事件分發處理機制詳解
Android應用的開發過程不可能不涉及到Touch事件的處理,簡單地如設置OnClickListener、OnLongClickListener等監聽器處理View的點擊事件,復雜地如在自定義View中通過重寫onTouchEvent來捕獲用戶交互事件以定製出各種效果,在使用的過程中或多或少會遇到一些奇怪的Bug,讓你對Touch事件「從哪來,到哪去」產生迷之疑惑,經過多少次徘徊之後終於決定系統的分析下源碼,本文就給大家分享下我的收獲。
MotionEvent作為Touch事件的載體,採用時間片來管理Touch事件所有相關行為的數據,本文這樣理解時間片這個概念:
通常MotionEvent會將觸發當前事件的Pointer作為主要Pointer,其PointerIndex為0,而MotionEvent通過提供getX()這類不帶index參數的介面以更方便的操作主要Pointer的數據。
了解了MotionEvent的組成結構之後,接下來就可以分析MotionEvent包含的事件類型了,MotionEvent通過getAction介面來獲取事件Action,而Action中低8位地址存儲的是事件類型(對於觸摸事件來說,主要包括Down、Move、Up、Cancel、PointerDown、PointerUp),高8位地址存儲的是PointerId(當事件類型為PointerDown、PointerUp時)。通常來說事件會以Down開始,以Up或Cancel結束,各事件所承擔的角色以及各自的特點在分析事件分發與處理的過程時再詳細說明。
另外,MotionEvent中的Flag需要說明一下:
本文僅分析Touch事件在Framework中Java層的傳遞,因此從事件傳遞到Activity開始分析。當Touch事件傳遞給Activity時,會調用Activity.dispatchTouchEvent(MotionEvent),Activity會將事件傳遞給其Window進行處理,實際會調用PhoneWindow.superDispatchTouchEvent(MotionEvent),PhoneWindow會將該事件傳遞給Android中View層級前埋中的頂層View(即DecorView)進行處理:
在Window未設置Callback的情況下,會調用父類的dispatchTouchEvent,DecorView繼承自FrameLayout,然後FrameLayout並未實現dispatchEvent,因此最終調用ViewGroup.dispatchTouchEvent,也就是Touch事件分發的核心邏輯所在,前文中提到MotionEvent中事件類型主要包括Down、Move、Up、Cancel、PointerDown、PointerUp,而dispatchTouchEvent根據事件的不同類型會做不同處理,因此這里分別進行分析:
Down事件處理
非異常情況下,Touch事件的事件周期總是以Down事件開始的,因此Down事件在整個事件分發邏輯中起關鍵作用,將決定了後續Move、Up及Cancel事件的處理主體,先看一張Down事件分發的流程圖:
從流程圖中可以看到,Down事件的分發邏輯主要目的在於尋找到能處理該Touch事件的View控制項(該View為以當前ViewGroup為Root節點的View層級中的View,利用尋找到的View創建事件處理Target),整個處理邏輯主要包含以下渣悔陸幾步:
Move、Up、Cancel事件處理
完成Down事件的分發邏輯後,就確定了該Down事件後續Move、Up及Cancel事件的處理主體(注意:這里並沒有確定PointerDown事件的處理主體,關於PointerDown事件的分發邏輯稍後分析),先通過一張流程圖來感受下Move、Up、Cancel事件的分發邏輯:
從流程圖可以看出,對於Move、Up、Cancel事件的分發步驟如如頃下:
PointerDown事件處理
PointerDown事件是在支持多Pointer(調用將FLAG_SPLIT_MOTION_EVENTS置位)的環境下,當有新的Pointer按下時產生的,該事件處理的特殊性在於會重新遍歷View層級,尋找可以處理新Pointer事件的Target,具體流程參考Down事件的分發邏輯;遍歷結束若仍沒有找到處理該事件的Target,則會將新Pointer的處理權設置給已有Target中最早被添加的Target。完成Target的尋找之後,會將該事件通過dispatchTransformedTouchEvent傳遞至所有已有Target進行處理,可以通過下面流程圖,對PointerDown事件的處理有一個更全局的認識:
PointerUp事件處理
相對於Up事件來說,對於PointerUp事件的處理區別在於當傳遞至所有已有Target結束之後並不能標記以Down事件起始的整個事件周期結束,僅能標記其關聯Pointer(以PointerDown事件起始)的事件周期結束,因此不會清除所有狀態,而僅會從已有Target中移除掉與該Pointer相關的部分。
onInterceptTouchEvent
在ViewGroup進行事件分發的過程中,會調用該函數來確定是否需要攔截事件,當該函數返回true時該事件將會被攔截,即不會進行正常的View層級傳遞,而是直接由該ViewGroup來處理,而攔截後的操作需要根據攔截事件的類型不同而不同:
dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits)
在將事件傳遞給Target進行處理之前會調用該函數對MotionEvent進行處理:
MotionEvent.split(int idBits)
判斷一個View控制項是否消費一個事件,是由View.dispatchEvent的返回值來決定的,而View.dispatchEvent用於尋找事件的最終消費者,話不多說,還是通過一張流程圖來個直觀感受:
從流程圖中可以看出,View會根據ouch事件對Scroll狀態進行調整,並尋找該事件的最終處理器:
View.dispatchEvent將向其直接ViewGroup返回是否消費掉該事件,返回值將決定上級ViewGroup是否需要繼續詢問其他子View是否需要消費該事件。這就是View中分發事件的邏輯,真是簡單粗暴!
從View.dispatchEvent的分析中可以發現當未對View設置mTouchListener或mTouchListener未消費掉該事件時,Touch事件最終將由View.onTouchEvent來決定是否消費,自定義View可以重寫該方法實現自身的邏輯,此處僅分析View中的通用處理邏輯:
從上述分析可以很開心地發現熟悉的onClick及onLongClick事件的產生邏輯,若是之前沒看過類似的文章,應該會有原來如此的感覺吧,哈哈~~
至此,Touch事件的分發與處理流程算是走通了,個人看完整個源碼之後有種豁然開朗的感覺,能很清晰的分析向「為什麼事件有時候傳到某個View有時候卻不傳?」、「有時候只傳前面幾個事件後面卻不傳了?」等問題,也希望本文的分析能讓你更清晰地感知Android中Touch事件的傳遞流程,如果發現文中有何錯誤,希望不吝賜教!
『貳』 Android 懸浮窗如何能讓它和他的的下層一起響應觸摸事件
/**
* 創建一個小懸浮窗。初始位置為屏幕的右部中間位置。
*
* @param context 必須為應用程序的Context.
*/
public static void createSmallWindow(Context context) {
WindowManager windowManager = getWindowManager(context);
int screenWidth = windowManager.getDefaultDisplay().getWidth();
int screenHeight = windowManager.getDefaultDisplay().getHeight();
if (smallWindow == null) {
smallWindow = new FloatWindowSmallView(context);
if (smallWindowParams == null) {
smallWindowParams = new LayoutParams();
smallWindowParams.type = LayoutParams.TYPE_PHONE;
smallWindowParams.format = PixelFormat.RGBA_8888;
smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE;
smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
smallWindowParams.width = FloatWindowSmallView.viewWidth;
smallWindowParams.height = FloatWindowSmallView.viewHeight;
smallWindowParams.x = screenWidth;
smallWindowParams.y = screenHeight / 2;
}
smallWindow.setParams(smallWindowParams);
windowManager.addView(smallWindow, smallWindowParams);
LogPrinter.i(TAG, "添加了小浮標View");
}
}
smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
『叄』 Android-View的事件分發及攔截-父控制項和子控制項都處理觸摸事件的方式
比如接著上篇 Android-View的事件分發及攔截機制簡單流程先體驗再研究(場景?疑問? 具體?待續...) ,小白現在要實現就是子View和父ViewGroup都響應點擊事件。
1. 單純的都只是響應down事件
這個就很簡單了 - 直接子View的**public boolean **onTouchEvent(MotionEvent event) 裡面直接返回false就行了。也就是子控制項響應了一次down後,接下來就交給父ViewGroup了.(子View就啥幾把也幹不了了);
2. 響應down和up事件,move啥的
我們知道子View如果onTouch裡面返回了true,那麼將會處理後續的move,up事件。而不再交給上層父ViewGroup。那父ViewGroup就沒辦法在Touch裡面處理,所以我們只能放到dispatchTouchEvent或者onInterceptTouchEvent中處理這個down,up等事件:
比如dispatchTouchEvent中:
這樣的情況就是父ViewGroup先執行點擊事件,然後子View再執行。 如果您需要父ViewGroup晚點,可以延時執行啥的。
如果此時,子View的dispatchTouchEvent返回true - 表示攔截,不繼續了
那麼子View的所有的事件都不會響應了。其實也就是我們的一個事件先傳遞,touch再處理的樹形圖:
網路上拔個圖來
簡單記錄下下而已,繼續加深理解...這是上一篇的續,還是上一篇....嘖嘖....後面是官方文檔分析來著...
『肆』 Android可監聽的事件類型(提示:用戶事件和系統事件,用戶事件又分為按鍵事件和觸屏事件)
在android系統中,存在多種界面事件,如點擊事件,觸摸事件,焦點事件,和菜單事件
用戶事件和系統事件等,事件發生時,android界面框架調用界面控制項的事件處理函數對事件進行處理。
如:用戶事件:
按鍵事件:keyevent將傳遞給onkey()函數進行處理
觸屏事件:touchevent將傳遞給ontouch()函數進行處理。
『伍』 Android 雙擊單擊事件監聽手勢檢測GestureDetector原理及實現
app我們常用的手勢有很多的地方,比如右滑關閉界面等。手勢控制分為觸發動作(Touch Mechanics,用戶手指在屏幕上如何動作)和觸發行為(Touch Activities,界面上特定動作在特定情境下引發的結果)。這是因為同樣的觸發動作(如單次觸擊)在不同情境下可能會帶來不同的結果(如輕觸,取消,開啟/關閉指示),同樣單次觸發行為(如放大)可能是由多種觸發動作(如捏放,雙次觸擊,雙次觸擊拖拽等)實現。
一般情況下,我們知道View類有個View.OnTouchListener內部介面,通過重寫他的onTouch(View v, MotionEvent event)方法,我們可以處理一些touch事件,但是這個方法太過簡單,如果需要處理一些復雜的手勢,用這個介面就會很麻煩(因為我們要自己根據用戶觸摸的軌跡去判斷是什麼手勢)。
Android sdk給我們提供了GestureDetector類,通過這個類我們可以識別很多的手勢,主要是通過他的onTouchEvent(event)方法完成了不同手勢的識別。雖然他能識別手勢,但是不同的手勢要怎麼處理,應該是提供給程序員實現的。
一.GestureDetector簡介
1.組成
GestureDetector類用來識別觸摸屏的各種手勢,它包含了兩個介面和一個內部類:
介面:
OnGestureListener:用來監聽手勢事件(6種)。
OnDoubleTapListener:用來監聽雙擊事件。
內部類:
SimpleOnGestureListener:用來監聽所有的手勢。實際上它實現了上述兩個介面,不過方法體是空的,需要我們自己寫。我們可以繼承這個類,重寫裡面的方法進行手勢處理。
2.構造
GestureDetector gestureDetector = new GestureDetector(GestureDetector.OnGestureListener listener);
GestureDetector gestureDetector = new GestureDetector(Context context,GestureDetector.OnGestureListener listener);
GestureDetector gestureDetector = new GestureDetector(Context context,GestureDetector.SimpleOnGestureListener listener);
3.方法
(1)onTouchEvent(MotionEvent ev) 分析捕捉到的觸摸事件觸發相應的回調函數
(2)setIsLongpressEnabled(boolean isLongpressEnabled) 設置「長按」是否可用
(3)setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener) 設置雙擊監聽器
4.使用
流程:
首先,系統捕捉屏幕的觸摸事件(onTouchListener),這時還未涉及具體手勢,只是簡單地捕捉到觸摸。
接著,在onTouch()方法中調用GestureDetector的onTouchEvent()方法,將捕捉到的MotionEvent交給GestureDetector來處理
最後,還需要實現抽象方法。
可根據需要選擇:
重寫OnGestureListener並通過構造函數傳入gestureDetector
重寫OnDoubleTapListener並通過GestureDetector.setOnDoubleTapListener方法傳入gestureDetector
重寫SimpleOnGestureListener並通過構造函數傳入gestureDetector
實現:
註:不要注重我寫的類是什麼類,要注重實現方法自定義view和activity中都可以,根據需要繼承上面三種listener,傳入構造函數即可;
public class TestDemo{
Context context;
public TestDemo(Context context){
this.context = context;
}
private GestureDetectordetector;
private void initView(){
detector =new GestureDetector(context, new MySimple());
detector =new GestureDetector(context, new MyGesture());
detector =new GestureDetector(context, new MyDoubleTap());
setOnTouchListener((v, event) -> {
// 事件監聽交給手勢類來處理
detector.onTouchEvent(event);
return true;
});
}
//與上面二選一
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
// 手勢監聽器類SimpleOnGestureListener
private class MySimple extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {//一次單獨的輕擊抬起操作,也就是輕擊一下屏幕,立刻抬起來,才會有這個觸發;如果除了Down以外還有其它操作,那就不再是Single操作了,所以也就不會觸發這個事件
return super.onSingleTapUp(e);
}
@Override
public void onLongPress(MotionEvent e) {//長按事件;
super.onLongPress(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//在屏幕上拖動事件,只要手指移動就會執行,無論是用手拖動view,或者是以拋的動作滾動,都會多次觸發,這個方法在ACTION_MOVE動作發生時就會觸發他不會執行MotionEvent.ACTION_UP,通常用來實現放大縮小和移動。
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {//滑動屏幕,用戶按下觸摸屏、快速移動後松開,由1個MotionEvent ACTION_DOWN, 多個ACTION_MOVE, 1個ACTION_UP觸發;是個甩的動作,這個甩的動作是在一個MotionEvent.ACTION_UP(手指抬起)發生時執行,通常用來實現翻頁效果
return super.onFling(e1, e2, velocityX, velocityY);
}
@Override
public void onShowPress(MotionEvent e) {//down事件發生而move或則up還沒發生前觸發該事件;
super.onShowPress(e);
}
@Override
public boolean onDown(MotionEvent e) {//down事件用戶按下屏幕
return super.onDown(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {//雙擊事件
return super.onDoubleTap(e);
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {//雙擊間隔中還發生其他的動作。通知DoubleTap手勢中的事件,包含down、up和move事件(這里指的是在雙擊之間發生的事件,例如在同一個地方雙擊會產生DoubleTap手勢,而在DoubleTap手勢裡面還會發生down和up事件,這兩個事件由該函數通知)
return super.onDoubleTapEvent(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {//單擊事件。用來判定該次點擊是SingleTap而不是DoubleTap,如果連續點擊兩次就是DoubleTap手勢,如果只點擊一次,系統等待一段時間後沒有收到第二次點擊則判定該次點擊為SingleTap而不是DoubleTap,然後觸發SingleTapConfirmed事件。
return super.onSingleTapConfirmed(e);
}
}
// 手勢監聽器類GestureListener
private class MyGesture extends GestureDetector.OnGestureListener{
@Override
public boolean onDown(MotionEvent e) {//down事件用戶按下屏幕
return false;
}
@Override
public void onShowPress(MotionEvent e) {//down事件發生瞬間而move或則up還沒發生前觸發該事件;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {//一次單獨的輕擊抬起操作,也就是輕擊一下屏幕,立刻抬起來,才會有這個觸發;如果除了Down以外還有其它操作,那就不再是Single操作了,所以也就不會觸發這個事件;
return super.onSingleTapUp(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//在屏幕上拖動事件,只要手指移動就會執行,無論是用手拖動view,或者是以拋的動作滾動,都會多次觸發,這個方法在ACTION_MOVE動作發生時就會觸發他不會執行MotionEvent.ACTION_UP,通常用來實現放大縮小和移動。
return false;
}
@Override
public void onLongPress(MotionEvent e) {//長按事件,超過一定時長觸發該事件回調;
super.onLongPress(e);
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {//滑動屏幕,用戶按下觸摸屏、快速移動後松開,由1個MotionEvent ACTION_DOWN, 多個ACTION_MOVE, 1個ACTION_UP觸發;是個甩的動作,這個甩的動作是在一個MotionEvent.ACTION_UP(手指抬起)發生時執行,通常用來實現翻頁效果
return super.onFling(e1, e2, velocityX, velocityY);
}
}
// 手勢監聽器類SimpleOnGestureListener
private class MyDoubleTap extends GestureDetector.OnDoubleTapListener {
@Override
public boolean onDoubleTap(MotionEvent e) {//雙擊事件
return super.onDoubleTap(e);
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {//雙擊間隔中還發生其他的動作。通知DoubleTap手勢中的事件,包含down、up和move事件(這里指的是在雙擊之間發生的事件,例如在同一個地方雙擊會產生DoubleTap手勢,而在DoubleTap手勢裡面還會發生down和up事件,這兩個事件由該函數通知)
return super.onDoubleTapEvent(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {//單擊事件。用來判定該次點擊是SingleTap而不是DoubleTap,如果連續點擊兩次就是DoubleTap手勢,如果只點擊一次,系統等待一段時間後沒有收到第二次點擊則判定該次點擊為SingleTap而不是DoubleTap,然後觸發SingleTapConfirmed事件。
return super.onSingleTapConfirmed(e);
}
}
}
關於onFling()和onScroll()的區別:
onFling()是甩,這個甩的動作是在一個MotionEvent.ACTION_UP(手指抬起)發生時執行,而onScroll(),只要手指移動就會執行。他不會執行MotionEvent.ACTION_UP。onFling通常用來實現翻頁效果,而onScroll通常用來實現放大縮小和移動。
關於onSingleTapConfirmed和onSingleTapUp的一點區別: OnGestureListener有這樣的一個方法onSingleTapUp,和onSingleTapConfirmed容易混淆。二者的區別是:onSingleTapUp,只要手抬起就會執行,而對於onSingleTapConfirmed來說,如果雙擊的話,則onSingleTapConfirmed不會執行
SimpleOnGestureListener是GestureDetector類的一個內部類,該類是static class,也就是說它實際上是一個外部類。可以在外部繼承這個類,重寫裡面的手勢處理方法。
1.OnDoubleTapListener是用來檢測滑鼠雙擊事件的
2.SimpleOnGestureListener實際上實現了OnGestureListener 和OnDoubleTapListener,所以它可以完成以上提到的所有手勢識別(9種)