一 點擊事件的傳遞規(guī)則
??當一個 MothinEvent 產(chǎn)生了以后,系統(tǒng)需要把這個事件傳遞給一個具體的 View,而這個傳遞過程就是分發(fā)過程,由三個方法共同完成:
dispatchTouchEvent(MotionEvent ev)
?用來進行事件的分發(fā)。如果事件能夠傳遞給當前 View ,那么此方法一定被調(diào)用,返回結(jié)果受當前 View 的 onTouchEvent 和下級的 dispatchTouchEvent 方法影響,表示是否消耗該事件。onInterceptTouchEvent(MotionEvent event)
?在dispatchTouchEvent方法中調(diào)用,判斷是否攔截某個事件,如果 View 攔截了某個事件,那么在同一個事件序列中,此方法不會被再次調(diào)用,返回結(jié)果表示是否攔截該事件。onTouchEvent(MotionEvent event)
?在 dispatchTouchEvent 方法中調(diào)用,用來處理點擊事件,返回結(jié)果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前 View 無法再次接收到該事件。上述三者關系,如下偽代碼:
public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if (onInterceptTouchEvent(ev)){ //是否攔截 consume = onTouchEvent(ev); //攔截調(diào)用 onTouchEvent }else { consume = child.dispatchTouchEvent(ev); //不攔截分發(fā)給子 View } return consume; }事件分發(fā)優(yōu)先級:
? onTouch > onTouchEvent > onClick;
事件分發(fā)過程:
??點擊事件產(chǎn)生后,它的傳遞有如下順序:Activity -> Window -> View , 即事件總是先傳遞給 Activity,Activity 再傳遞給 Window ,最后 Window 再傳遞給頂級 View,頂級的 View 接收到事件后,就會按照事件分發(fā)機制去分發(fā)事件。
??如果一個 View 的 onTouchEvent 返回 false,那么他的父容器的 onTouchEvent 會調(diào)用,如果都不處理這個事件,那么最后會傳遞到Activity的 onTouchEvent 處理。
結(jié)論:
- 1.事件序列:是手指接觸到屏幕那一刻起,到手指離開屏幕的那一刻起,這個過程從 down 事件開始,中間有數(shù)量不定的 move 事件,最終以 up 事件結(jié)束。(down -> move ...move->up)
- 正常情況一個事件序列只能被一個 View 攔截消耗,即同一個事件交給一個 View 處理。但可以通過一個 View 的onTouchEvent 強行傳遞給其他 View 處理。
- 某個 View 一旦決定攔截,這個事件序列只能交給它處理,他的
onInterceptTouchEvent()不再調(diào)用。
- 某個 View 一旦開始處理事件,如果它不消耗 ACTION_DOWN 事件(onTouchEvent 返回 false),他會將這個事件從新交給他的父元素處理,即父容器的 onTouchEvent 會被調(diào)用。一旦交給一個View處理,那么它就必須消耗掉,否則同一事件序列剩下的事件就不再交給它處理了。
- 如果 View 只消耗了 ACTION_DOWN 事件,那么這個點擊事件會消失,父容器的 onTouchEvent 并不會調(diào)用,并且當前的 View 可以持續(xù)收到后續(xù)的事件,最終這些消失的點擊事件會傳遞給 Activity 處理。
- ViewGroup 默認不攔截任何事件,Android 源碼中 ViewGroup 的 onInterceptTouchEvent 方法默認返回 false。
- View 的 onTouchEvent 默認都是消耗事件(返回true),除非它是不可點擊的 >(clickable 和 longClickable 同時為 false)。
View 的 longClickable屬性 :默認都為 false。
clickable 屬性:要分情況討論,比如 Button 的 clickable 為 ture,TextView 的 clickable 屬性>為 false 。
- View 中沒有 onInterceptTouchEvent 方法,一旦有事件傳遞給它,那么它的 onTouchEvent 方法就會被調(diào)用。
- View 的 enable 屬性不影響 onTouchEvent 的默認返回值,假如 View 是 disable 狀態(tài)的,只要它的 clickable 和 longClickable 有一個為 true,那么它的 onTouchEvent 就返回 true。
- onClick 會發(fā)生的前提是 View 是可點擊的,并且它收到了 down 和 up 的事件。
- 事件傳遞過程是由外向內(nèi)的,即事件總先傳遞給父元素,然后再由父元素分給子 View,通過
requestDisallowInterceptTouchEvent方法可以在子元素中干預父元素的事件分發(fā)。(ACTION_DOWN 事件除外)
二 View 的繪制流程
? View 的繪制流程是從 ViewRoot 的 performTraversals 方法開始的,performTraversals 會依次調(diào)用 performMeasure ,performLayout,performDraw 三個方法分別完成頂級 View 的measure,layout,draw 這三大流程,
??其中在 performMeasure 中會調(diào)用 measure 方法,measure 方法會調(diào)用 onMeasure 方法,在 onMeasure 中會完成子元素進行 measure 過程,子元素也會重復父元素的這個過程,performLayout,performDraw 同理。
三 理解 MeasureSpec
?在測量過程中,系統(tǒng)會將 View 的 LayoutParams 根據(jù)父容器所施加的規(guī)則轉(zhuǎn)換成對應的 MeasureSpec ,然后根據(jù)這個 MeasureSpec 測量出 View 的寬/高。
- MeasureSpec 代表一個 32 位 int 值,高 2 位代表 SpecMode ,低 30 位代表 SpecSize。
??SpecMode:測量模式。
??SpecSize:某種測量模式下規(guī)格的大小。- MeasureSpec 將 SpecMode與SpecSize 打包成一個 int 值,避免過多的內(nèi)存分配,而 MeasureSpec 值可以解包成 SpecMode與SpecSize 。(MeasureSpec 這里指的 int 值)
- SpecMode有三類:
??UNSPECIFIED(未指明):父容器不對 View 有任何限制,要多大給多大,這種情況一般用于系統(tǒng)內(nèi)部,表示一種測量狀態(tài)。
??EXACTLY(恰好的):父容器已經(jīng)檢測出 View 所需要的大小,這個時候 View 的最終大小就是 SpecSize 所指定的值,它對應 LayoutParams 中的 match_parent 和具體數(shù)值的兩種模式。
??AT_MOST(至多的):父容器指定了一個大小可用的 SpecSize ,View 的大小不能大于這個值,具體是什么值看 View 的具體實現(xiàn),它對應 LayoutParams 的 wrap_content。
決定因素:值由子View的布局參數(shù)LayoutParams和父容器的MeasureSpec值共同決定。具體規(guī)則見下圖:
普通 View 的 MeasureSpec 的創(chuàng)建規(guī)則.png
