Android View的點擊事件傳遞
View的定義
view是Android中所有組件的父類,不管是Button或者TextView這樣的初學者所瞭解的簡單的View,還是ViewGroup,LinearLayout等,都是View的子類。
點擊事件
點擊事件就是MotionEvent,即手指接觸屏幕之後產生的一系列事件,一般需要關注的事件類型是:
ACTION_DOWN
就是手指剛剛接觸屏幕的瞬間產生的動作,但是很多時候這個動作一般不會讓某一個View來處理。
ACTION_MOVE
手指在屏幕上滑動,並且滑動的距離超過某個最小值(TouchSlop),才被判定爲滑動,其中TouchSlop在不同設備上的值往往是不一樣的,我們可以通過ViewConfiguration.get(mContext).getScaledTouchSlop()獲取這個常量。
ACTION_UP
手指離開屏幕的產生的動作 。
通過以上我們可以得出,手指在屏幕上的一次操作,會產生出一系列的點擊事件,比如簡單的點擊就會產生一個ACTION_DOWN和一個ACTION_UP,如果滑動的話,還需要在其中加入多個ACTION_MOVE 。
View的事件分發機制
View中點擊事件的傳遞規則
View針對MotionEvent有三個處理函數:
public boolean dispatchTouchEvent(MotionEvent ev)
用來對點擊事件進行分發,是View最早接收到MotionEvent 的函數,返回值代表是否消耗當前事件。
public boolean onInterceptTouchEvent(MotionEvent ev)
在dispatchTouchEvent(ev)中調用,表示是否截斷當前接受到的事件。
public boolean onTouchEvent(MotionEvent ev)
在dispatchTouchEvent(ev)中調用,用來處理點擊事件,結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,該View無法再次接收到事件。其中TRUE表示消耗當前事件。
上面三個方法的關係可以用以下代碼表示:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = childView.dispatchTouchEvent(ev);
}
return consume;
}
從上面的代碼我們可以看出,View的點擊事件的傳遞規則:當上級View將點擊事件傳遞搭配當前View的時候,View的dispatchTouchEvent就會被首先調用,然後如果當前View的onInterceptTouchEvent()返回值爲TRUE,那麼就會該事件傳遞給onTouchEvent處理,不然就將其交給下一級View的dispatchTouchEvent處理。同時,值得注意的是,每一個MotionEvent只表示一個ACTION_MOVE ,ACTION_UP或者ACTION_DOWN,我們可以通過以下方法得到該MotionEvent所代表的事件:
//ev爲MotionEvent對象
ev.getAction()
同時這裏有一個疑問,加入當前View的onInterceptTouchEvent()返回值爲TRUE,但是onTouchEvent()的返回值爲FALSE(當前沒有消耗這個事件),那麼這個MotionEvent到底去哪裏了呢? 這種情況下這個MotionEvent會傳遞給上級View處理。
上述所說的只是大致的流程,其實還有很多細節的地方,有興趣的人可以去看看Android源碼。
其他總結
(一)同一序列事件是指從手指觸摸屏幕開始,直到手指離開屏幕的時候,所產生的點擊事件。
(二)在整個事件傳遞的過程中,Activity拿到事件對象,Activity把事件對象傳遞給PhoneWindow,PhoneWindow再傳遞給DecorView,DecorView通過遍歷再傳遞到我們的ViewGroup。
(三)如果在當前View中設置了onTouchlistener,或者onClickListener,那麼在整個View的點擊事件的傳遞規則優先級爲:onTouchlistener.onTouch()>onTouchEvent()>onClickListener.onClick()。如果想要截斷這個事件可以在某一處將其返回值設置爲TRUE。
(四)正常情況下,一個事件序列只能被一個Visw攔截且消耗。這一條的原因可以參考(五),因爲一旦一個元素攔截了某此事件,那麼同一個事件序列內的所有事件都會直接交給它處理,因此同一個事件序列中的事件不能分別由兩個View同時處理,但是通過特殊手段可以做到,比如一個Vew將本該自己處理的事件通過onTouchEvent強行傳遞給其他View處理。
(五)某個View一旦決定攔截,那麼這一個事件序列都只能由它來處理(如果事件序列能夠傳遞給它的話),並且它的onInterceprTouchEvent不會再被調用。這條也很好理解,就是說當一個View決定攔截一個事件後,那麼系統會把同一個事件序列內的其他方法都直接交給它來處理,因此就不用再調用這個View的onInterceptTouchEvent去詢問它是否要攔截了。
(六)某個View一旦開始處理事件,如果它不消耗ACTON_DOWN事件(onTouchEvent返回了false),那麼同一事件序列中的其他事件都不會再交給它來處理,並且事件將重新交由它的父元素去處理,即父元素的onTouchEvent會被調用。意思就是事件一旦交給一個View處理,那麼它就必須消耗掉,否則同一事件序列中剩下的事件就不再交給它來處理了,這就好比上級交給程序員一件事,如果這件事沒有處理好,短期內上級就不敢再把事情交給這個程序員做了,二者是類似的道理。
(七)如果View不消耗除ACTION_DOWN以外的其他事件,那麼這個點擊事件會消失,此時父元素的onTouchEvent並不會被調用,並且當前View可以持續收到後續的事件,最終這些消失的點擊事件會傳遞給Activity處理。
(八)ViewGroup默認不攔截任何事件。Android源碼中ViewGroup的onInterceptTouchEvent方法默認返回false
(九)View沒有onInterceptTouchEvent方法,一旦有點擊事件傳遞給它,那麼它的onTouchEvent方法就會被調用。所以如果我們要對某個事件進行截斷的時候,我們可以在它的onTouchEvent中進行處理,爲了實現很多自定義的效果,我們必須得常常去對一些控件或者佈局進行繼承,並且重寫其中的方法。
(十)view的onTouchEvent默認都會消耗事件(返回true),除非它是不可點擊的(clickable和longClickable同時爲false),View的longClickable屬性默認都爲false,clickable屬性要分情況,比如Button的clickable屬性默認爲true,而TextView 的clickable屬性默認爲false
(十一)view 的enable.屬性不影響onTouchEvent的默認返回值。哪怕一個View是disable狀態的,只要它的clickable或者longclickable有一個爲true,那麼它的onTouchEvent就返會true。
(十二)onclick會發生的前提實際當前的View是可點擊的,並且他收到了down和up的事件。
如果有任何疑問可以去看看
https://blog.csdn.net/huachao1001/article/details/51766225
寫的真的很容易理解。