淺談Android事件分發(fā)機制

Android事件分發(fā)分為View和ViewGroup的分發(fā)悯恍,由dispatchTouchEvent,onInterceptTouchEvent钙畔,onTouchEvent三個方法共同完成分發(fā)過程柿祈。

View的事件分發(fā)

View的事件分發(fā)由dispatchTouchEvent,onTouchEvent兩個方法完成蔓姚。
我們先來看一下一個完整事件的執(zhí)行流程。
在頁面上有一個TestTextView(繼承自TextView)慨丐。為了方便查看結(jié)果坡脐,在TestTextView中的dispatchTouchEvent,onTouchEvent方法中打了Log日志

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e("tag","=====  onTouchEvent "+ event.getAction());
                break;
            case MotionEvent.ACTION_UP:
                Log.e("tag","=====  onTouchEvent " + event.getAction());
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("tag","=====  onTouchEvent " + event.getAction());
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Log.e("tag","=====  dispatchTouchEvent " + event.getAction());
                break;
            case MotionEvent.ACTION_UP:
                Log.e("tag","=====  dispatchTouchEvent " + event.getAction());
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e("tag","=====  dispatchTouchEvent " + event.getAction());
                break;
        }
        return super.dispatchTouchEvent(event);
    }

設(shè)置TestTextView的點擊事件和touch監(jiān)聽

      tv_touch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.e("tag","========   onClick");
            }
        });

        tv_touch.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        Log.e("tag","=====  onTouch "+ event.getAction());
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.e("tag","=====  onTouch " + event.getAction());
                        break;
                    case MotionEvent.ACTION_MOVE:
                        Log.e("tag","=====  onTouch " + event.getAction());
                        break;
                }
                return false;
            }
        });

點擊TextView查看Log日志:

11-24 16:20:46.870 10998-10998/com.zy.touchevent E/tag: =====  dispatchTouchEvent 0
11-24 16:20:46.871 10998-10998/com.zy.touchevent E/tag: =====  onTouch 0
11-24 16:20:46.871 10998-10998/com.zy.touchevent E/tag: =====  onTouchEvent 0
11-24 16:20:46.897 10998-10998/com.zy.touchevent E/tag: =====  dispatchTouchEvent 1
11-24 16:20:46.897 10998-10998/com.zy.touchevent E/tag: =====  onTouch 1
11-24 16:20:46.897 10998-10998/com.zy.touchevent E/tag: =====  onTouchEvent 1
11-24 16:20:46.898 10998-10998/com.zy.touchevent E/tag: =====  onClick
從Log日志上可以看出房揭,點擊頁面上的一個View备闲,事件傳遞的方法順序是 dispatchTouchEvent ->onTouch ->onTouchEvent->onClick 晌端;

先看dispatchTouchEvent 方法

     /**
     * 分發(fā)事件,因為是view的事件分發(fā)此時分發(fā)的對象是自己
     * 返回值是boolean類型,表示是否消費當(dāng)前事件
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

看一下dispatchTouchEvent的源碼:

boolean result = false;//返回結(jié)果浅役,默認(rèn)false不處理
...
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
   result = true;
 }      
//前面如果沒有處理斩松,result為false,此時result結(jié)果受onTouchEvent
if (!result && onTouchEvent(event)) {
      result = true; 
}    

從上面代碼可以看出觉既,result默認(rèn)為false不處理惧盹,且result的結(jié)果在兩個if中確定。
第一個if判斷 li != null在if上面已經(jīng)初始化瞪讼,li!=null成立钧椰;
mOnTouchListener 在我們設(shè)置setOnTouchListener監(jiān)聽的時候已經(jīng)賦值,也不為null符欠;
(mViewFlags & ENABLED_MASK) == ENABLED判斷當(dāng)前點擊的控件是否是enable的嫡霞,按鈕默認(rèn)都是enable的,成立希柿;
最后一個條件li.mOnTouchListener.onTouch(this, event)的返回值受OnTouchListener事件中的onTouch()方法影響诊沪,如果在onTouch()中返回ture則result=true成立,就不會執(zhí)行第二個if語句曾撤。
第二個if判斷當(dāng)?shù)谝粋€if判斷不成立時端姚,此時result的結(jié)果受自身onTouchEvent影響;

既然dispatchTouchEvent的返回值受onTouch()方法影響挤悉,onTouch()的默認(rèn)返回值為false渐裸,我們修改為true看看此時的打印日志:

11-24 17:32:13.263 18052-18052/com.zy.touchevent E/tag: =====  dispatchTouchEvent 0
11-24 17:32:13.264 18052-18052/com.zy.touchevent E/tag: =====  onTouch 0
11-24 17:32:13.316 18052-18052/com.zy.touchevent E/tag: =====  dispatchTouchEvent 1
11-24 17:32:13.316 18052-18052/com.zy.touchevent E/tag: =====  onTouch 1

從Log日志我們可以發(fā)現(xiàn)onTouchEvent,onclick方法沒了,這很好理解装悲。因為onTouch 方法返回true表示將此次事件消費掉昏鹃,而onTouchEvent方法在onTouch 后面執(zhí)行,事件自然也就執(zhí)行不到onTouchEvent和onclick诀诊。

既然onTouch 方法能夠影響onTouchEvent的執(zhí)行洞渤,那么onTouchEvent又和onclick有沒有一腿呢?
來看看onTouchEvent方法的源碼(源碼有點多属瓣,省略和onclick不相關(guān)部分)

public boolean onTouchEvent(MotionEvent event) {
       switch (action) {
                case MotionEvent.ACTION_UP:
                        if (!post(mPerformClick)) {
                                    performClick();
                                }
                      break;
        }
}

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

當(dāng)手指抬起時您宪,performClick();方法會被調(diào)用,點擊查看方法奠涌,可以知道當(dāng)li.mOnClickListener != null不為空時,li.mOnClickListener.onClick(this);view的點擊方法就會被調(diào)用。

View的事件分發(fā)就說到這里了磷杏,下面來說說ViewGroup的事件分發(fā):

ViewGroup的事件分發(fā)
gv.jpg

我覺得這張圖是最能簡單說明ViewGroup的事件分發(fā)順序了溜畅,所以我就借鑒一下 ?
當(dāng)一個事件(點擊或者touch)到達activity的根節(jié)點也就是ViewGroup時,ViewGroup會把事件遍歷分發(fā)給它包含的每個子View,而當(dāng)子View為ViewGroup時极祸,又會通過調(diào)用ViwGroup的dispatchTouchEvent方法繼續(xù)調(diào)用其內(nèi)部的View的dispatchTouchEvent方法慈格。所以上圖的分發(fā)順序是① -- ② -- ⑤ -- ⑥ -- ⑦ -- ③ -- ④怠晴。如果在分發(fā)過程中任何一個子View的dispatchTouchEvent的返回值為true(消費事件),后面的分發(fā)都會中止。
看一下ViewGroup的源碼吧浴捆,很長蒜田,會省略很多,我開始看不懂也是參考別人的講解看的选泻,文章末尾會給鏈接的冲粤。

1、dispatchTouchEvent方法
   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ...
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  ①
            ...
            for (int i = count - 1; i >= 0; i--) {
                final View child = children[i];
                if (child.dispatchTouchEvent(ev))  {    ②
                    mMotionTarget = child;
                    return true;
                }
            }
        }
        return target.dispatchTouchEvent(ev);
    }

這里只給了幾處影響dispatchTouchEvent方法返回值的代碼页眯,第一個判斷里面兩個條件滿足其中一個就會進入后面代碼梯捕,disallowIntercept是指是否禁用掉事件攔截的功能,默認(rèn)是false窝撵,也可以通過調(diào)用requestDisallowInterceptTouchEvent方法對這個值進行修改傀顾。第二個判斷是對onInterceptTouchEvent(攔截事件)返回值取反。如果一個控件是可點擊的碌奉,那么點擊該控件時短曾,dispatchTouchEvent的返回值必定是true,第二個判斷條件child.dispatchTouchEvent(ev)為true成立赐劣。
ViewGroup也是View的子類嫉拐,因此View的事件分發(fā)在ViewGroup中同樣適用。當(dāng)ViewGroup中沒有子View時隆豹,走的就是View的分發(fā)過程了

    final View target = mMotionTarget;
    if (target == null) {  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }  
        return super.dispatchTouchEvent(ev);  
    }  
2椭岩、onInterceptTouchEvent方法
public boolean onInterceptTouchEvent(MotionEvent ev) {  
    return false;  
}

上面就是onInterceptTouchEvent方法的源碼,默認(rèn)返回false不攔截璃赡,我們可以在需要的時候進行攔截判哥。

3、onTouchEvent方法

剛剛也已經(jīng)說了碉考,ViewGroup也是View的一個子類塌计。因此當(dāng)ViewGroup沒有子View時onTouchEvent方法會被調(diào)用,還有就是子View的onTouchEvent方法返回值為false侯谁,父容器的onTouchEvent方法會被調(diào)用锌仅。
我在開發(fā)藝術(shù)探索里看見了一個很好理解的比喻:假如點擊事件是一個難題,這個難題被上級領(lǐng)導(dǎo)分配給一個程序員處理(類似事件分發(fā)過程)墙贱,結(jié)果這個程序員搞不定(onTouchEvent返回了false)热芹,但是難題又必須要解決,那就只能交給水平更高的上級解決了(上級的onTouchEvent方法被調(diào)用)惨撇,如果上級在搞不定伊脓,那只能交給上級的上級去解決,就這樣將難題一層層上拋魁衙。

最后關(guān)于事件分發(fā)的總結(jié):
1报腔、Touch事件分發(fā)中只有兩個主角:ViewGroup和View株搔。包含onInterceptTouchEvent、
dispatchTouchEvent纯蛾、onTouchEvent三個相關(guān)事件纤房,其中ViewGroup又繼承于View。
2翻诉、一個點擊事件產(chǎn)生后炮姨,他的傳遞過程遵循如下順序: Activity -> Window -> View
3、觸摸事件由Action_Down米丘、Action_Move剑令、Aciton_UP組成,其中一次完整的觸摸事件中拄查,Down和Up都只
有一個吁津,Move有若干個,可以為0個堕扶。

總結(jié):第一次寫這種總結(jié)分析類的文章碍脏,還是很困難的,首先我沒有一個貫穿全文的思路(想了幾個稍算,被寫死了)典尾,然后就是不知道該怎么寫(雖然我心里明白是啥回事,但說不出來啊/(ㄒoㄒ)/)糊探,所以這篇文章里面我借鑒了好多別人的東西钾埂,包括思路,代碼片段(希望不會被瞧不起)科平。哈哈褥紫,雖然寫的不好,但還是有收獲的瞪慧,繼續(xù)加油~

參考:
《安卓開發(fā)藝術(shù)探索》
Android事件分發(fā)機制完全解析髓考,帶你從源碼的角度徹底理解(下)
Android:30分鐘弄明白Touch事件分發(fā)機制

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市弃酌,隨后出現(xiàn)的幾起案子氨菇,更是在濱河造成了極大的恐慌,老刑警劉巖妓湘,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件查蓉,死亡現(xiàn)場離奇詭異,居然都是意外死亡榜贴,警方通過查閱死者的電腦和手機奶是,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人聂沙,你說我怎么就攤上這事〕踵冢” “怎么了及汉?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長屯烦。 經(jīng)常有香客問我坷随,道長,這世上最難降的妖魔是什么驻龟? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任温眉,我火速辦了婚禮,結(jié)果婚禮上翁狐,老公的妹妹穿的比我還像新娘类溢。我一直安慰自己,他們只是感情好露懒,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布闯冷。 她就那樣靜靜地躺著,像睡著了一般懈词。 火紅的嫁衣襯著肌膚如雪蛇耀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天坎弯,我揣著相機與錄音纺涤,去河邊找鬼。 笑死抠忘,一個胖子當(dāng)著我的面吹牛撩炊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播褐桌,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼衰抑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了荧嵌?” 一聲冷哼從身側(cè)響起呛踊,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎啦撮,沒想到半個月后谭网,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡赃春,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年愉择,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡锥涕,死狀恐怖衷戈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情层坠,我是刑警寧澤殖妇,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站破花,受9級特大地震影響谦趣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜座每,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一前鹅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧峭梳,春花似錦舰绘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挫以,卻和暖如春者蠕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掐松。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工踱侣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人大磺。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓抡句,卻偏偏與公主長得像,于是被迫代替她去往敵國和親杠愧。 傳聞我的和親對象是個殘疾皇子待榔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容