View事件沖突和處理

事件處理和沖突

一陆错、View

1. disPatchTouchEvent

//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
        && (mViewFlags & ENABLED_MASK) == ENABLED
        && li.mOnTouchListener.onTouch(this, event)) {
    //先處理onTouch
    result = true;
}

dispatchTouchEvent方法中先調(diào)用這個(gè)ViewonTouchListener.onTouch方法灯抛。如果onTouch返回false則執(zhí)行下面的流程,否則就返回true表示此事件已經(jīng)被消費(fèi)音瓷。

//從這里調(diào)用onclick
if (!result && onTouchEvent(event)) {
    result = true;
}

onTouchEvent中方法判斷如果當(dāng)前事件為ACTION_UP時(shí)就會(huì)調(diào)用onClick的回調(diào)

//如果沒能將這個(gè)調(diào)用放到Handler中对嚼,Handler退出了
if (!post(mPerformClick)) {
    performClickInternal();
}
public boolean performClick() {
    // We still need to call this method to handle the cases where performClick() was called
    // externally, instead of through performClickInternal()
    notifyAutofillManagerOnClick();

    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;
    }

在這里就會(huì)調(diào)用到OnClickListener.onClick方法。

總結(jié):在View.dispatchTouchEvent方法中會(huì)先執(zhí)行OnTouchLitern.onTouch方法绳慎,并且onTouch可以響應(yīng)所有的事件纵竖,如果返回false才會(huì)執(zhí)行OnClickListener.onClick方法漠烧,onClick方法只能響應(yīng)ACTION_UP。如果既要執(zhí)行onTouch又要執(zhí)行onClick可以直接調(diào)用performClick方法靡砌,來(lái)執(zhí)行onClick方法已脓。

二 . ViewGroup

1. 形成消費(fèi)鏈

final View[] children = mChildren;
//遍歷所有的子View
for (int i = childrenCount - 1; i >= 0; i--) {
    final int childIndex = getAndVerifyPreorderedIndex(
            childrenCount, i, customOrder);
    final View child = getAndVerifyPreorderedView(
            preorderedList, children, childIndex);

    if (!child.canReceivePointerEvents()
            || !isTransformedTouchPointInView(x, y, child, null)) {
        ev.setTargetAccessibilityFocus(false);
        continue;
    }

    //判斷當(dāng)前 的處理鏈中是否包含了這個(gè)View
    newTouchTarget = getTouchTarget(child);
    //當(dāng)前的點(diǎn)擊鏈中已經(jīng)包含了這個(gè)View
    if (newTouchTarget != null) {
        // Child is already receiving touch within its bounds.
        // Give it the new pointer in addition to the ones it is handling.
        newTouchTarget.pointerIdBits |= idBitsToAssign;
        //如果包含直接退出循環(huán)
        break;
    }

這里遍歷 所有的子View如果當(dāng)前處理鏈中已經(jīng)包含了某個(gè)子View,就直接退出循環(huán)通殃,這個(gè)子View之后的View也就不會(huì)再收到通知度液。如果當(dāng)前的處理鏈中不包含所有的子View,就調(diào)用dispatchTransformedTouchEvent這個(gè)方法画舌。

// Perform any necessary transformations and dispatch.
if (child == null) {
    //父布局的View來(lái)處理
    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());
    }

    //子View來(lái)處理
    handled = child.dispatchTouchEvent(transformedEvent);
}

如果傳入的子View為Null則會(huì)調(diào)用當(dāng)前這個(gè)ViewGroup的父類(View.dispatchToucEvent)方法堕担,來(lái)處理onTouch和onClick事件,如果傳入的不為null這個(gè)方法又會(huì)遞歸的調(diào)用傳入的子ViewdispatchTouchEvent方法骗炉。如果這個(gè)方法最終返回true即照宝,有View消費(fèi)了這個(gè)事件,就把這個(gè)View加入到處理鏈中句葵。

newTouchTarget = addTouchTarget(child, idBitsToAssign);
//標(biāo)記是否有新的子View加入到處理的鏈中
alreadyDispatchedToNewTouchTarget = true;

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

addTouchTraget方法中會(huì)創(chuàng)建一個(gè)TouchTarget節(jié)點(diǎn)將,并把剛加入的View作為鏈頭兢仰,并設(shè)置mFirstTouchTraget乍丈。

當(dāng)遍歷所有的子View之并把處理當(dāng)前事件的View加入處理鏈之后就會(huì)跳出循環(huán)(不再通知后面的View)。到這里其實(shí)這個(gè)事件已經(jīng)被下面的View消費(fèi)了把将,接下來(lái)就要標(biāo)記這個(gè)處理的狀態(tài)轻专,用來(lái)告訴這個(gè)ViewGroup的父View,它是否處消費(fèi)過這個(gè)事件。

// Dispatch to touch targets.
//沒有處理鏈
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    //沒有子View處理這個(gè)事件察蹲,就調(diào)用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.
    TouchTarget predecessor = null;
    TouchTarget target = mFirstTouchTarget;
    while (target != null) {
        final TouchTarget next = target.next;
        //有新加入的節(jié)點(diǎn)
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
             //沒有新加入的節(jié)點(diǎn)就遍歷所有的處理鏈请垛,看他們處不處理
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                handled = true;
            }
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}

這個(gè)方法整體上分為兩個(gè)部分:

  • 沒有處理鏈

    ViewGroup自己來(lái)處理這個(gè)事件

  • 有處理鏈

    這種情況又分為兩種

    • 有新加入的節(jié)點(diǎn),直接將handle設(shè)為true(因?yàn)樵谇懊孢@個(gè)節(jié)點(diǎn)已經(jīng)處理過了洽议,確認(rèn)要消費(fèi)才加入的)
    • 沒有新的節(jié)點(diǎn)就遍歷整條消費(fèi)鏈宗收,看是否有節(jié)點(diǎn)要消費(fèi),如果整條鏈上由節(jié)點(diǎn)消費(fèi)了就返回true,否則就返回false亚兄。這種情況下每一個(gè)處理鏈上的節(jié)點(diǎn)處理到收到這個(gè)事件混稽,而不像之前遍歷子View的。

2. ViewGroup的二次選擇機(jī)會(huì)

在整個(gè)的處理流程中當(dāng)前的ViewGroup有兩次機(jī)會(huì)選擇要不要處理這個(gè)事件审胚。

  • 開始的時(shí)候判斷是否攔截這個(gè)事件
  • 沒有子View處理這個(gè)事件

第一種情況 即調(diào)用onInterceptTouchEvent這個(gè)方法判斷要不要攔截匈勋。

if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    //down事件由于重置一定為false
    //子View通過調(diào)用requestDisallowInterceptTouchEvent,來(lái)告訴父View某個(gè)事件用不用通知子View
    //如果為參數(shù)為true就告訴父View不要攔截膳叨,如果參數(shù)為false就讓父View自己決定要不要攔截
    //調(diào)用這個(gè)方法最主要的原因就是為了決定要不要走父View的onInterceptTouchEvent方法洽洁。不讓它執(zhí)行就默認(rèn)不攔截
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    //可以攔截
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
}

如果ViewGroup確定要攔截這個(gè)事件,就不會(huì)再詢問所有的子View菲嘴,而是判斷當(dāng)前處理鏈?zhǔn)欠駷榭斩鲎裕绻麨榭站?code>ViewGroup自己處理碎浇,否則就給處理鏈上的節(jié)點(diǎn)依次處理。

final boolean cancelChild = resetCancelNextUpFlag(target.child)
        || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
        target.child, target.pointerIdBits)) {
    handled = true;
}


if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    //被攔截的時(shí)候回 把這個(gè)事件設(shè)置為Cancle
    event.setAction(MotionEvent.ACTION_CANCEL);
    if (child == null) {
        //詢問當(dāng)前View時(shí)
        //調(diào)用onTouchEvent
        handled = super.dispatchTouchEvent(event);
    } else {
        //詢問子View時(shí)
        handled = child.dispatchTouchEvent(event);
    }
    event.setAction(oldAction);
    return handled;
}

ViewGroup在確認(rèn)攔截這個(gè)事件后會(huì)依次通知處理鏈上的每一個(gè)節(jié)點(diǎn)璃俗,并把一個(gè)CANCLE事件傳遞給這些節(jié)點(diǎn)奴璃。

所以在被攔截之后子節(jié)點(diǎn)會(huì)收到一個(gè)Cancle類型的事件。

//被攔截之后城豁,會(huì)把鏈表置空苟穆,把表頭置空
if (cancelChild) {
    if (predecessor == null) {
        mFirstTouchTarget = next;
    } else {
        predecessor.next = next;
    }
    target.recycle();
    target = next;
    continue;
}

如果ViewGroup攔截之后就會(huì)把mFirstTouchTarget設(shè)置為null,這樣所有的子View再收到Cancle事件之后就不能再 收到其他事件的通知了唱星。

onInterceptTouchEvent實(shí)現(xiàn)攔截是ViewGroup的第一次選擇機(jī)會(huì)

如果所有的子View都不處理這個(gè)Down事件那么mFirstTouchTarget就為null雳旅,就會(huì)調(diào)用這個(gè)ViewdisPatchTouchEvent方法。

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    //沒有子View處理這個(gè)事件间聊,就調(diào)用onTouchEvent方法
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);

如果所有的子View都不處理那么這個(gè)ViewGroup自己處理就是第二次選擇機(jī)會(huì)

3.子View對(duì)父View的反向制約

從上面的分析中可以看出父View如果攔截之后子View就 無(wú)法再消費(fèi)到事件攒盈。

[圖片上傳失敗...(image-247a99-1593159300388)]

造成這種情況的原因是走進(jìn)了if條件中,如果子View能夠讓if條件不滿足就能不調(diào)用父View的的攔截方法哎榴。通過reqestDisallowInterceptTouchEvent()方法能夠設(shè)置mGroupFlag的值型豁,從而調(diào)整if條件是否滿足。

if (disallowIntercept) {
    mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}

FLAG_DISALLOW_INTERCEPT是一個(gè)非0的數(shù)尚蝌,如果傳入的參數(shù)為true那么mGroupFlags&FLAG_DISALLOW_INTERCEPT就不為0迎变,if條件就不滿足,就默認(rèn)不攔截飘言;如果參數(shù)為false時(shí)mGroupFlags&FLAG_DISALLOW_INTERCEPT就一定 為0衣形,if條件就滿足就會(huì)執(zhí)行父ViewinterceptTouchEvent方法。

整型值的非運(yùn)算

~7

  • 先化成2進(jìn)制
    0000 0111

  • 取反

    1111 1000

  • 計(jì)算補(bǔ)碼

    1000 0111 + 1 = 1000 1000

    -8,首位為1表示負(fù)數(shù)姿鸿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谆吴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子苛预,更是在濱河造成了極大的恐慌句狼,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碟渺,死亡現(xiàn)場(chǎng)離奇詭異鲜锚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)苫拍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門芜繁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人绒极,你說我怎么就攤上這事骏令。” “怎么了垄提?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵榔袋,是天一觀的道長(zhǎng)周拐。 經(jīng)常有香客問我,道長(zhǎng)凰兑,這世上最難降的妖魔是什么妥粟? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮吏够,結(jié)果婚禮上勾给,老公的妹妹穿的比我還像新娘。我一直安慰自己锅知,他們只是感情好播急,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著售睹,像睡著了一般桩警。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上昌妹,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天捶枢,我揣著相機(jī)與錄音,去河邊找鬼捺宗。 笑死柱蟀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蚜厉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼畜眨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼昼牛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起康聂,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贰健,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后恬汁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伶椿,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年氓侧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了脊另。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡约巷,死狀恐怖偎痛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情独郎,我是刑警寧澤踩麦,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布枚赡,位于F島的核電站,受9級(jí)特大地震影響谓谦,放射性物質(zhì)發(fā)生泄漏贫橙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一反粥、第九天 我趴在偏房一處隱蔽的房頂上張望卢肃。 院中可真熱鬧,春花似錦星压、人聲如沸践剂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)逊脯。三九已至,卻和暖如春竣贪,著一層夾襖步出監(jiān)牢的瞬間军洼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工演怎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匕争,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓爷耀,卻偏偏與公主長(zhǎng)得像甘桑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子歹叮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348