Android事件分發(fā)機制從0開始

1.?事件基礎

1.?MotionEvent

  1. 手指在屏幕的動作被封裝成了MotionEvent洞翩。
  2. 常用事件類型分為如下幾種:
  • MotionEvent.ACTION_DOWN---->手指剛剛接觸屏幕
  • MotionEvent.ACTION_MOVE---->手指在屏幕上移動
  • MotionEvent.ACTION_UP------>手指從屏幕上松開的瞬間
  • MotionEvent.ACTION_CANCEL-->這個比較復雜遍希,下文詳細分析
  1. 每個手勢操作都是以ACTION_DOWN開始绽媒,以ACTION_UP結束愉择,中間夾雜著多個或者零個ACTION_MOVE籍凝。

2. 所謂的事件分發(fā)機制屠缭,就是對MotionEvent對象的分發(fā)過程。當我們的手勢操作產生了一個MotionEvent對象時,系統(tǒng)需要把它傳遞給一個具體的View相寇。

3. 涉及到的三個主要方法:

1. public boolean dispatchTouchEvent(MotionEvent ev)

如果一個事件可以傳遞到當前View慰于,那么此View的這個方法一定可以被調用。此方法用來對事件進行分發(fā)唤衫,它的返回結果表示當前事件是否被處理婆赠,也就是此事件是否被消耗。

2. public boolean onInterceptTouchEvent(MotionEvent event)

這個方法表示當前View是或否攔截此事件佳励,在dispatchTouchEvent(MotionEvent ev)中被調用休里。這個方法只在ViewGroup中存在,但是默認返回false植兰,也就是View默認攔截傳遞到它的事件份帐,而ViewGroup默認不攔截任何事件。

3. public boolean onTouchEvent(MotionEvent event)

用來處理點擊事件楣导,我們設置的點擊事件最終是在這個方法中被調用。它同樣在dispatchTouchEvent(MotionEvent ev)中被調用畜挨。

2.?事件分發(fā)的源碼解析

  1. 一個事件最先傳遞給Activity然后向下進行事件分發(fā)筒繁。我們從Activity的dispatchTouchEvent()開始分析
/***Activity.dispatchTouchEvent(MotionEvent ev)***/
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
public Window getWindow() {
        return mWindow;
    }

通過在Activity中查找我們可以發(fā)現mWindow = new PhoneWindow(this, window, activityConfigCallback);

/****PhoneWindow***/
 @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

這個mDecor是DecorView的一個對象

/***DecorView***/
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
}

我們知道,DecorView就是通過setContentView()這個方法設置的View的父View巴元。而它繼承自FrameLayout顯然是一個View毡咏,更進一步說是一個ViewGroup,這樣我們的事件就從Activity傳遞到了View逮刨。</br>
所以我們說一個事件總是從ViewGruop開始分發(fā)呕缭,期間經歷多個或零個ViewGroup,最終以ViewGroup或者View終結修己。

  1. ViewGroup的事件分發(fā)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
        } else {
            intercepted = false;
        }
    } else {
        intercepted = true;
    }
    if (!canceled && !intercepted) {
        final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
            final View[] children = mChildren;
            for (int i = childrenCount - 1; i >= 0; i--) {
                //如果子元素成功處理了事件恢总,則mFirstTouchTarget就會被賦值
                dispatchTransformedTouchEvent(ev, false, children[i], idBitsToAssign)
            }
        }
    }
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        // 調用的這個方法會去調用view.dispatchTouchEvent(),這樣onTouchEvent()就會被調用了
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                                TouchTarget.ALL_POINTER_IDS);
    } else {
        // Dispatch to touch targets, excluding the new touch target if we already
        // dispatched to it.  Cancel touch targets if necessary.
        dispatchTransformedTouchEvent(ev, cancelChild,
                                      target.child, target.pointerIdBits);
    }
    return handled;
}

public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    return handled;
}

以上源碼經過了精簡,只能表示分發(fā)流程睬愤,幫助理解片仿。詳細分析過程就不寫了。ViewGroup是個抽象類尤辱,我們不能直接用砂豌,干活的都是它的子類,但是我看了一圈貌似常用的幾種布局LinearLayout光督、FrameLayout等等都沒有重寫上述方法阳距,也就是說它們的分發(fā)過程都是上面這個過程。

  1. View的事件分發(fā)
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }
    if (!result && onTouchEvent(event)) {
        result = true;
    }
    return result;
}

public boolean onTouchEvent(MotionEvent event) {
    final int action = event.getAction();
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                               || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                              || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return clickable;
    }
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        performClickInternal()-->performClick()-->onClick();
        return true;
    }
    return false;
}

3.?一些結論

1.某個View一旦決定攔截结借,那么這個事件序列都只能由它來處理筐摘。如果它是ViewGroup,那么它的onInterceptTouchEvent()不會被調用。因為條件actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null為false蓄拣。

2.正常情況下扬虚,一個事件序列只能被一個View攔截消耗。因為一旦一個元素攔截了此事件球恤,那么同一個事件序列中的所有事件都會被直接交給它處理辜昵,因此同一個事件序列中的事件不能分別由兩個View同時處理蚕涤,但是通過特殊手段可以做到蹋笼,比如一個View將本該自己處理的事件通過onTouchEvent()強行傳遞給其它View處理。

3.某個View一旦開始處理事件茄唐,如果它不能消耗ACTION_DOWN事件张惹,那么同一事件序列中的其它事件都不會再交給它來處理舀锨,并且事件將重新交給它的父元素去處理,即父元素的的onTouchEvent()會被調用宛逗。意思是事件一旦交給了一個View處理坎匿,那么它就必須消耗掉,否則同一事件序列中剩下的事件就不會再交給它來處理了雷激。

4.如果View不消耗ACTION_DOWN以外的其它事件替蔬,那么這個點擊事件會消失,此時父元素的onTouchEvent()并不會被調用屎暇,并且當前View可以持續(xù)收到后續(xù)的事件承桥,最終這些消失的事件會傳遞給Activity處理。

5.View的onTouchEvent()默認消耗事件(返回true)根悼,除非它是不可點擊的(clickable和longClickable都是false)凶异。View的longClickable默認都為false,clickable分情況。

6.onClick發(fā)生的前提是當前View可點擊并且接收到了Down和up事件挤巡。

7.事件分發(fā)總是從父元素傳遞給子元素剩彬,但是在子元素中可以通過requestDisallowInterceptTouchEvent()來干預父元素中除了ACTION_DOWN之外的事件。

4.?ACTION_CANCEL

當控件收到前驅事件(什么叫前驅事件玄柏?一個從DOWN一直到UP的所有事件組合稱為完整的手勢襟衰,中間的任意一次事件對于下一個事件而言就是它的前驅事件)之后,后面的事件如果被父控件攔截粪摘,那么當前控件就會收到一個CANCEL事件瀑晒,并且把這個事件會傳遞給它的子事件。(注意:這里如果在控件的onInterceptTouchEvent中攔截掉CANCEL事件是無效的徘意,它仍然會把這個事件傳給它的子控件)之后這個手勢所有的事件將全部攔截苔悦,也就是說這個事件對于當前控件和它的子控件而言已經結束了。
在設計設置頁面的滑動開關時椎咧,如果不監(jiān)聽ACTION_CANCEL玖详,在滑動到中間時把介,如果你手指上下移動,就是移動到開關控件之外蟋座,則此時會觸發(fā)ACTION_CANCEL拗踢,而不是ACTION_UP,造成開關的按鈕停頓在中間位置向臀。意思就是巢墅,當用戶保持按下操作,并從你的控件轉移到外層控件時券膀,會觸發(fā)ACTION_CANCEL當前的手勢被中斷君纫,不會再接收到關于它的記錄。推薦將這個事件作為 ACTION_UP 來看待芹彬,但是要區(qū)別于普通的 ACTION_UP

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蓄髓,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子舒帮,更是在濱河造成了極大的恐慌会喝,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件会前,死亡現場離奇詭異好乐,居然都是意外死亡,警方通過查閱死者的電腦和手機瓦宜,發(fā)現死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岭妖,“玉大人临庇,你說我怎么就攤上這事£腔牛” “怎么了假夺?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斋攀。 經常有香客問我已卷,道長,這世上最難降的妖魔是什么淳蔼? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任侧蘸,我火速辦了婚禮,結果婚禮上鹉梨,老公的妹妹穿的比我還像新娘讳癌。我一直安慰自己,他們只是感情好存皂,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布晌坤。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪骤菠。 梳的紋絲不亂的頭發(fā)上它改,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音商乎,去河邊找鬼央拖。 笑死,一個胖子當著我的面吹牛截亦,可吹牛的內容都是我干的爬泥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼崩瓤,長吁一口氣:“原來是場噩夢啊……” “哼袍啡!你這毒婦竟也來了?” 一聲冷哼從身側響起却桶,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤境输,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后颖系,有當地人在樹林里發(fā)現了一具尸體嗅剖,經...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年嘁扼,在試婚紗的時候發(fā)現自己被綠了信粮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡趁啸,死狀恐怖强缘,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情不傅,我是刑警寧澤旅掂,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站访娶,受9級特大地震影響商虐,放射性物質發(fā)生泄漏。R本人自食惡果不足惜崖疤,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一秘车、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧戳晌,春花似錦鲫尊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咳蔚。三九已至,卻和暖如春搔驼,著一層夾襖步出監(jiān)牢的瞬間谈火,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工舌涨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留糯耍,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓囊嘉,卻偏偏與公主長得像温技,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扭粱,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內容