Android事件分發(fā)|事件沖突處理

android的事件分發(fā)在面試時(shí)算是高頻問題陌兑,工作中也能用到租谈,這里將事件分發(fā)、事件沖突展氓,和NestedScrolling中的事件傳遞整理哈穆趴。

Android事件分發(fā)

android 事件分發(fā).jpg

方法說明
dispatchTouchEvent:事件分發(fā),Activity遇汞, ViewGroup未妹, View都有該方法,Activity和ViewGroup分發(fā)給子View空入, View分發(fā)給自己
onInterceptTouchEvent:攔截事件络它,只有ViewGroup有該方法,用于事件歪赢,ViewGroup想要處理某個(gè)事件時(shí)化戳,可以隨時(shí)對子View, say no埋凯!我這個(gè)我要處理
onTouchEvent:事件消費(fèi)点楼,Activity扫尖,ViewGroup,View都有該方法

對于開發(fā)者來說掠廓,第一個(gè)接收到事件的地方就在dispatchTouchEvent中换怖,如果想全局不允許點(diǎn)擊是,事件可以在這里直接返回蟀瞧,不進(jìn)行下一步的分發(fā)沉颂。上段源碼

Activity#dispatchTouchEvent

  public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
  1. 調(diào)用Window的superDispatchTouchEvent
  2. 沒有view消費(fèi),我自己調(diào)用onTouchEvent黄橘,返回消費(fèi)結(jié)果

PhoneWindow#superDispatchTouchEvent

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

這里就熟悉了兆览,調(diào)用了mDecor的superDispatchTouchEvent
DecorView#superDispatchTouchEvent

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

DecorView繼承了FrameLayout,F(xiàn)rameLayout又繼承了ViewGroup塞关,F(xiàn)rameLayout中并沒有重寫dispatchTouchEvent抬探, 所以就調(diào)用到了ViewGroup的dispatchTouchEvent

ViewGroup#dispatchTouchEvent

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
         ...//此處省略數(shù)行
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                //重寫設(shè)置狀態(tài)
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            //mFirstTouchTarget 不為空和Down事件,所以有子view消費(fèi)的情況下帆赢,此處一直為真
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //是否禁止攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //父view是否攔截
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
          //很重要的標(biāo)識小压,當(dāng)前是否已經(jīng)分配給target,不至于被down事件被消費(fèi)兩次
           boolean alreadyDispatchedToNewTouchTarget = false;
            //如果取消和攔截都不查找子view
            if (!canceled && !intercepted) {
                      ...此處省略數(shù)行
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                           ...此處省略數(shù)行
                            //這里調(diào)dispatchTransformedTouchEvent椰于, 最終調(diào)用了子View的onTouchEvent去確定該事件是否消費(fèi)
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                               ...此處省略數(shù)行
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }              
                        }
                    }
                }
            }
            // 沒有找到消費(fèi)的子view怠益,那去看看自己消費(fèi)不,最終調(diào)用到了ViewGroup的onTouchEvent
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            }else {
                //mFirstTouchTarget的后續(xù)事件瘾婿,move/up都會走這里去下發(fā)
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    //down事件是 alreadyDispatchedToNewTouchTarget 已經(jīng)為true蜻牢,所以down事件不會被消費(fèi)兩次
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                          target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
        ...此處省略數(shù)行
        return handled;
    }

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        ...此處省略數(shù)行
        //父view調(diào)用是child==null,調(diào)用super.dispatchTouchEvent
        // Perform any necessary transformations and dispatch.
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }
        return handled;
    }

這里調(diào)用了子view的dispatchTouchEvent, 為了讓咋們的布局文件接收到分發(fā)事件偏陪,其實(shí)是頂層ViewGroup(DecorView)調(diào)用布局文件的dispatchTouchEvent抢呆,各個(gè)ViewGroup逐層分發(fā),直到有一個(gè)子View或者ViewGroup消費(fèi)了事件笛谦。對于上層ViewGroup而言抱虐,View或者ViewGroup,對于他們都是一樣處理饥脑。調(diào)用dispatchTouchEvent恳邀,ViewGroup調(diào)用dispatchTouchEvent就再次分發(fā),然后咋們看哈View的dispatchTouchEvent

View#dispatchTouchEvent

    public boolean dispatchTouchEvent(MotionEvent event) {
        ...此處省略數(shù)行
            if (!result && onTouchEvent(event)) {
                result = true;
            }
           ...此處省略數(shù)行
        return result;
    }

子view調(diào)用了onTouchEvent灶轰,如果消費(fèi)了就會返回true

總結(jié)

  • 事件從ViewGroup逐級往下分發(fā)谣沸,直到找到消費(fèi)的view或者viewgroup
  • 子view一但消費(fèi)了down后續(xù)的move和up都會分發(fā)給它(一個(gè)前提,未被父view攔截)笋颤,即使onTouchEvent返回了flase
  • 父view一但做了攔截乳附,不管子view是否還想消費(fèi)事件,都會被父view消費(fèi)掉
  • 如果沒有子view消費(fèi),父view就會調(diào)用自己的onTouchEvent

關(guān)于第二點(diǎn)還要補(bǔ)充哈许溅,為什么子view一但在down事件中返回了true,后續(xù)的事件都會分發(fā)給它秉版,因?yàn)楦竀iew的mFirstTouchTarget 已經(jīng)不為空贤重,父View的父級View中的mFirstTouchTarget 也不為,一層層的下來清焕。事件每次都會分發(fā)給down時(shí)返回true的view并蝗。這就是為什么,有時(shí)候我們明明已經(jīng)移出控件外了秸妥,但是還是會接收到move和up事件滚停。如果move和up事件返回false,事件最終就會調(diào)用activity的onTouchEvent

事件沖突處理

從上面的事件分發(fā)可知粥惧,ViewGroup擁有子view的絕對分配權(quán)键畴,父view攔截事件,就沒得子view啥事了突雪。
在我們開發(fā)過程中可能遇到起惕,在一個(gè)垂直滾動(dòng)的scrollview中前提一個(gè)橫向的列表,如果橫向滾動(dòng)列表咏删,手指不會一直是一條直線惹想,導(dǎo)致scrollview上下滾動(dòng),這樣體驗(yàn)就不好督函。這個(gè)就是需要解決的事件沖突嘀粱,解決這種沖突有兩個(gè)方案。

Plan1:重寫父onTouchEvent辰狡,監(jiān)聽當(dāng)前頁面的列表锋叨,如果列表是當(dāng)前消費(fèi)事件,onTouchEvent就不消費(fèi)了

Plan2:在子View的down或者move事件中調(diào)用parent.requestDisallowInterceptTouchEvent()

為什么需要在子View的down和move中去調(diào)用搓译?在父View的dispatchTouchEvent中悲柱,down事件是會去重置禁止攔截的標(biāo)識,詳細(xì)查看ViewGroup.resetTouchState()

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末些己,一起剝皮案震驚了整個(gè)濱河市豌鸡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌段标,老刑警劉巖涯冠,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異逼庞,居然都是意外死亡蛇更,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來派任,“玉大人砸逊,你說我怎么就攤上這事≌乒洌” “怎么了师逸?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長豆混。 經(jīng)常有香客問我篓像,道長,這世上最難降的妖魔是什么皿伺? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任员辩,我火速辦了婚禮,結(jié)果婚禮上鸵鸥,老公的妹妹穿的比我還像新娘奠滑。我一直安慰自己,他們只是感情好脂男,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布养叛。 她就那樣靜靜地躺著,像睡著了一般宰翅。 火紅的嫁衣襯著肌膚如雪弃甥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天汁讼,我揣著相機(jī)與錄音淆攻,去河邊找鬼。 笑死嘿架,一個(gè)胖子當(dāng)著我的面吹牛瓶珊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耸彪,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼伞芹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蝉娜?” 一聲冷哼從身側(cè)響起唱较,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎召川,沒想到半個(gè)月后南缓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荧呐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年汉形,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纸镊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡概疆,死狀恐怖逗威,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岔冀,我是刑警寧澤庵楷,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站楣颠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏咐蚯。R本人自食惡果不足惜童漩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望春锋。 院中可真熱鬧矫膨,春花似錦、人聲如沸期奔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呐萌。三九已至馁痴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肺孤,已是汗流浹背罗晕。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赠堵,地道東北人小渊。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像茫叭,于是被迫代替她去往敵國和親酬屉。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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