AndroidTouch事件

記錄一下Touch事件的分析- -

什么是事件:當(dāng)用戶觸摸屏幕時(shí)匀钧,將產(chǎn)生的觸摸行為(Touch事件)

事件的類型:

  • MotionEvent.ACTION_DOWN 手指剛接觸屏幕
  • MotionEvent.ACTION_UP 手指從屏幕上松開
  • MotionEvent.ACTION_MOVE 手指在屏幕上滑動(dòng)
  • MotionEvent.ACTION_CANCEL 非人為因素取消

正常情況下夺谁,一次手指觸摸屏幕的行為會(huì)觸發(fā)一系列點(diǎn)擊事件

  • 點(diǎn)擊屏幕后立即松開含懊,事件序列為DOWN—>UP
  • 點(diǎn)擊屏幕滑動(dòng)一會(huì)再松開上鞠,事件序列為DOWN——>MOVE——>……——>MOVE—>UP


    image.png

事件分發(fā)對(duì)象

Activity ——> ViewGroup——>View

主要方法

  • dispatchTouchEvent(MotionEvent ev) 用來進(jìn)行事件分發(fā)
  • onInterceptTouchEvent(MotionEvent ev) 判斷是否攔截事件(只存在于ViewGroup中)
  • onTouchEvent(MotionEvent ev) 處理點(diǎn)擊事件

Activity

當(dāng)一個(gè)事件發(fā)生時(shí)首先會(huì)調(diào)用Activity的dispatchTouchEvent()事件
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction(); //這是一個(gè)空方法子類可以實(shí)現(xiàn)來獲取到當(dāng)前用戶觸摸屏幕的監(jiān)聽  
    }
    //getWindow 返回的是PhoneWindow 實(shí)際上就是調(diào)用PhoneWindow的superDispatchTouchEvent的方法
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //如果沒有View消費(fèi)事件,就會(huì)執(zhí)行Activity的onTouchEvent事件
    return onTouchEvent(ev);
}
然后在PhoneWindow中的superDispatchTouchEvent()方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    //mDecor 是繼承自FrameLayout的一個(gè)子類
    return mDecor.superDispatchTouchEvent(event);
}
然后再看一下DecorView的superDispatchTouchEvent()方法
public boolean superDispatchTouchEvent(MotionEvent event) {
    //它調(diào)用了父類的dispatchTouchEvent()方法
    return super.dispatchTouchEvent(event);
}
由于DecorView是FrameLayout的子類所以就到了ViewGroup的dispatchTouchEvent()方法

流程就是

image.png

Activity收到事件后會(huì)一層一層的往下派發(fā)到ViewGroup到了ViewGroup之后就涉及到了ViewGroup的dispatchTouchEvent方法。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
    }
    if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
        ev.setTargetAccessibilityFocus(false);
    }

    boolean handled = false;
    //mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED!=0
    //就是檢查上述兩個(gè)標(biāo)志上述兩個(gè)標(biāo)志均為true時(shí)onFilterTouchEventForSecurity會(huì)返回false 
    //mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 是View有設(shè)置被遮擋時(shí)不處理觸摸事件的flag
    //event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED!=0 檢查受到該事件的窗口是被其它窗口遮擋
    //FILTER_TOUCHES_WHEN_OBSCURED可以通過在xml文件中的android:filterTouchesWhenObscured來設(shè)置可婶,或者在Java中通過setFilterTouchesWhenObscured()來添加或移除
    //DecorView默認(rèn)是沒有這個(gè)標(biāo)志位的,而其他View基本上默認(rèn)都是有的
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // Handle an initial down.
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            //當(dāng)時(shí)down事件時(shí)援雇,是一個(gè)新的事件的開始矛渴,會(huì)進(jìn)行一系列的reset操作,對(duì)上一次的事件一些狀態(tài)進(jìn)行重置&mFirstTouchTarget設(shè)置成null
            //清除TouchTarget的緩存
            cancelAndClearTouchTargets(ev);
            //對(duì)mGroupFlags的標(biāo)志進(jìn)行重置為~FLAG_DISALLOW_INTERCEPT
            resetTouchState();
        }

        // Check for interception.
        //記錄當(dāng)前事件能否被攔截
        final boolean intercepted;
        //如果當(dāng)前為down事件  或者mFirstTouchTarget為null
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            //FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)志很重要 當(dāng)子View調(diào)用requestDisallowInterceptTouchEvent(boolean)方法時(shí)就是設(shè)置的mGroupFlags的屬性
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //這個(gè)disallowIntercept就是判斷是否允許攔截除了down之外的事件 為什么是down事件 是因?yàn)樵谏弦粋€(gè)判斷中對(duì)mGroupFlags的標(biāo)志進(jìn)行重置所以在down事件中disallowIntercept永遠(yuǎn)為false
            //所以就會(huì)執(zhí)行onInterceptTouchEvent()方法
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                //onInterceptTouchEvent這個(gè)方法默認(rèn)為false就是viewgroup默認(rèn)不會(huì)攔截事件 
                ev.setAction(action); // restore action in case it was changed
            } else {
                //如果子View調(diào)用getParent().requestDisallowInterceptTouchEvent(true)就會(huì)執(zhí)行下邊語句惫搏,不在攔截事件
                intercepted = false;
            }
        } else {
            //這里默認(rèn)就是mFirstTouchTarget為null,可以理解為沒有子View能夠分發(fā)此事件 所以 intercepted標(biāo)志就變?yōu)閠rue,
            intercepted = true;
        }
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

        // 標(biāo)識(shí)本次事件需不需要取消
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // 檢查父View是否支持多點(diǎn)觸控具温,即將多個(gè)TouchEvent分發(fā)給子View,
        // 通過setMotionEventSplittingEnabled()可以修改這個(gè)值筐赔。
        // FLAG_SPLIT_MOTION_EVENTS在3.0是默認(rèn)為true的铣猩,即支持多點(diǎn)觸控的分發(fā)
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        //默認(rèn)不攔截    
        if (!canceled && !intercepted) {
            // 檢查TouchEvent是否可以觸發(fā)View獲取焦點(diǎn),如果可以茴丰,查找本View中有沒有獲得焦點(diǎn)的子View达皿,
            // 有就獲取它,沒有就為null
            View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                    ? findChildWithAccessibilityFocus() : null;
            //如果當(dāng)前為down事件
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE    //鼠標(biāo)移動(dòng)) {
                // 獲取當(dāng)前觸摸手指在多點(diǎn)觸控中的排序
                // 這個(gè)值可能因?yàn)橛惺种赴l(fā)生Down或Up而發(fā)生改變
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                // 標(biāo)識(shí)當(dāng)前是那一個(gè)點(diǎn)的觸摸事件
                //ev.getPointerId()此時(shí)獲取到手指的Id贿肩,這個(gè)值在Down到Up這個(gè)過程中是不會(huì)改變的
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)        
                        : TouchTarget.ALL_POINTER_IDS;
                // 清理之前觸摸事件中的目標(biāo)
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                
                    //5.0加入的 將所有子View放到集合中峦椰,按照添加順序排序,但是受到Z軸影響 
                    //只有子View數(shù)量大于1汰规,并且其中至少有一個(gè)子View的Z軸不為0汤功,其實(shí)就是elevation屬性大于0,它才不為null
                    // 7.0中溜哮,View的elevation默認(rèn)為0
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    //對(duì)Viewgroup的所有子View進(jìn)行倒序遍歷(為什么是倒序  是因?yàn)?默認(rèn)最后添加的View在最上層滔金,應(yīng)該最優(yōu)先得到事件)
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
                        // 如果當(dāng)前已經(jīng)有View獲得焦點(diǎn)了,找到它茬射。后面的觸摸事件會(huì)優(yōu)先傳給它鹦蠕。
                        // 應(yīng)該主要影響后面觸摸點(diǎn)的Down事件
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            //// 找到后i設(shè)為最后一個(gè)View,強(qiáng)制結(jié)束for循環(huán)不再繼續(xù)查找
                            i = childrenCount - 1;
                        }
                        //此方法判斷子View是否可見&沒有在執(zhí)行動(dòng)畫
                        if (!canViewReceivePointerEvents(child)
                                    //判斷觸摸區(qū)域是否在View中
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        //找到了可以分發(fā)的View
                        //根據(jù)當(dāng)前child查找是否已經(jīng)記錄在mFirstTouchTarget這個(gè)單鏈表中
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        // 再次重置View
                        resetCancelNextUpFlag(child);
                        //將child傳遞到dispatchTransformedTouchEvent方法中如果傳入的child為null會(huì)調(diào)用super.dispatchTouchEvent
                        // 否則會(huì)對(duì)想X,Y計(jì)算根據(jù)當(dāng)前的View進(jìn)行偏移然后調(diào)用child.dispatchTouchEvent方法將事件傳遞到child中
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 記錄這次TouchEvent按下的時(shí)間
                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
                                // childIndex points into presorted list, find original index
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            //將mFirstTouchTarget指向消費(fèi)該事件的View
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            // 標(biāo)記已經(jīng)把事件分發(fā)給了newTouchTarget在抛,退出子View遍歷
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }
                //這個(gè)只有在多點(diǎn)觸控才會(huì)執(zhí)行
                // newTouchTarget在不是Down事件钟病,或者沒有找到處理事件的View時(shí)是null
                // mFirstTouchTarget在Down事件時(shí),如果找到了處理的View就不為null
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // 直接讓上次處理的View繼續(xù)處理
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            //到這里 ACTION_DOWN的事件處理完畢
            }
        }

        //mFirstTouchTarget == null 表示沒有能相應(yīng)該事件的child刚梭,那么就調(diào)用父類(也就是View)的dispatchTouchEvent肠阱,如果在down事件中intercepted為true,則newTouchTarget也為null也會(huì)執(zhí)行此方法
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                //表示在Down事件處理中,已經(jīng)將這個(gè)事件交給newTouchTarget處理過了朴读,就不重復(fù)處理了
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                //對(duì)Move Up事件的處理
                } else {
                    //cancelChild 為true 如果事件被父View攔截了屹徘,或者child原本被打上了暫時(shí)不可接收TouchEvent的標(biāo)記PFLAG_CANCEL_NEXT_UP_EVENT
                    // 就給他給它發(fā)送取消事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    //將Move Up等事件的分發(fā)給子View
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    //cancelChild為true 就會(huì)清空事件隊(duì)列,這樣后續(xù)事件就會(huì)被ViewGroup本身攔截掉
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }

        // Update list of touch targets for pointer up or cancel, if needed.
        //可以忽略衅金,跟主流程無關(guān)
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

因?yàn)榇a注釋太多了噪伊,仔細(xì)分析可以看注釋即可簿煌,簡單分析就是

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean concume = fasle ;
    //判斷是否要攔截,
    if(onInterceptTouchEvent(ev)){
        //如果攔截就調(diào)用自身的onTouchEvent()事件鉴吹。 ViewGroup的onTouchEvent是用的父類View的姨伟,自身并沒有實(shí)現(xiàn)這個(gè)方法
        consume = onTouchEvent(ev)
     }else{
        //如果不攔截就調(diào)用child.dispatchTouchEvent再對(duì)事件分發(fā)給子View
        Consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}

還需要注意一下下邊的代碼

            //FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)志很重要 當(dāng)子View調(diào)用requestDisallowInterceptTouchEvent(boolean)方法時(shí)就是設(shè)置的mGroupFlags的屬性
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //這個(gè)disallowIntercept就是判斷是否允許攔截除了down之外的事件 為什么是down事件 是因?yàn)樵谏弦粋€(gè)判斷中對(duì)mGroupFlags的標(biāo)志進(jìn)行重置所以在down事件中disallowIntercept永遠(yuǎn)為false
            //所以就會(huì)執(zhí)行onInterceptTouchEvent()方法
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                //onInterceptTouchEvent這個(gè)方法默認(rèn)為false就是viewgroup默認(rèn)不會(huì)攔截事件 
                ev.setAction(action); // restore action in case it was changed
            } else {
                //如果子View調(diào)用getParent().requestDisallowInterceptTouchEvent(true)就會(huì)執(zhí)行下邊語句,不在攔截事件
                intercepted = false;
            }

再看一下dispatchTransformedTouchEvent這個(gè)方法

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;
     // 先記錄原本的Action
    //如果cancel為true就會(huì)發(fā)送cancel事件豆励,實(shí)際傳遞進(jìn)來的事件就會(huì)被覆蓋掉
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        // 可能過來的事件沒有ACTION_CANCEL夺荒,如果希望取消的話,那么為事件添加取消標(biāo)志良蒸。
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            // 如果沒有子View了技扼,調(diào)用View中的dispatchTouchEvent
            // 進(jìn)而調(diào)用View的onTouch或者onTouchEvent方法,觸發(fā)ACTION_CANCEL邏輯
            handled = super.dispatchTouchEvent(event);
        } else {
            // 如果有子View嫩痰,將這個(gè)取消事件傳遞給子View
            handled = child.dispatchTouchEvent(event);
        }
        // 在設(shè)置回原本的Action
        // 此時(shí)TouchEvent的行為相當(dāng)于沒變
        // 但是卻把該ViewGroup的
        event.setAction(oldAction);
        return handled;
    }
    // 獲取觸摸事件的觸摸點(diǎn)id
    final int oldPointerIdBits = event.getPointerIdBits();
    // 看和期望的觸摸點(diǎn)是不是一個(gè)
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
       if (newPointerIdBits == 0) {
        //不是
        return false;
    }
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            //這里邊如果child == null 就會(huì)調(diào)用自身的super.dispatchTouchEvent方法
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                //否則就會(huì)計(jì)算偏移量然后調(diào)用 就會(huì)調(diào)用child.dispatchTouchEvent方法進(jìn)行分發(fā)
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                handled = child.dispatchTouchEvent(event);
   
             // 恢復(fù)TouchEvent坐標(biāo)到原來位置剿吻,避免影響后面的流
                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }
    // 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);
    }


    // Done.
    transformedEvent.recycle();
    return handle;
}

再記錄一下cancelAndClearTouchTargets這個(gè)方法

private void cancelAndClearTouchTargets(MotionEvent event) {
    // 如果觸摸事件目標(biāo)隊(duì)列不為空才執(zhí)行后面的邏輯
    if (mFirstTouchTarget != null) {
        boolean syntheticEvent = false;
        if (event == null) {
            final long now = SystemClock.uptimeMillis();
            //自己創(chuàng)建一個(gè)ACTION_CANCEL事件
            event = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            // 設(shè)置事件源類型為觸摸屏幕
            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
            // 標(biāo)記一下,這是一個(gè)合成事件
            syntheticEvent = true;
        }
        // TouchTarget是一個(gè)鏈表結(jié)構(gòu)始赎,保存了事件傳遞的子一系列目標(biāo)View
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            // 檢查View是否設(shè)置了暫時(shí)不在接收事件的標(biāo)志位和橙,如果有清除該標(biāo)志位
            // 這樣該View就能夠接收下一次事件了仔燕。
            //這個(gè)標(biāo)志位是PFLAG_CANCEL_NEXT_UP_EVENT 一個(gè)View如果有PFLAG_CANCEL_NEXT_UP_EVENT標(biāo)志造垛,表示它跟ViewGroup解除了綁定,通常會(huì)在調(diào)用ViewGroup#detachViewFromParent(View),很少用
            resetCancelNextUpFlag(target.child);
            // 將這個(gè)取消事件傳給子View晰搀,給上一次接收事件流的子View發(fā)送模擬的ACTION_CANCEL事件五辽,可以重置這些子View的觸摸狀態(tài)。比如取消它們的點(diǎn)擊或者長按事件
            dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
        }
        // 清空觸摸事件目標(biāo)隊(duì)列
        clearTouchTargets();
        if (syntheticEvent) {
            // 如果是合成事件外恕,需要回收它
            event.recycle();
        }
    }
}

再學(xué)習(xí)一個(gè)類

TouchTarget是一個(gè)內(nèi)部類 他是一個(gè)單向的鏈表杆逗,mFirstTouchTarget表示的就是頭,他記錄的就是能夠處理child的TouchTarget鳞疲。它內(nèi)部有一個(gè)鏈表組成的TouchTarget對(duì)象池能夠起到復(fù)用的機(jī)制
private static final class TouchTarget {
    //這個(gè)值表示最大個(gè)數(shù)罪郊,這個(gè)值也就決定了事件最多傳遞32層,所以當(dāng)寫一個(gè)layout的層級(jí)超過32的時(shí)候尚洽,子View就會(huì)收不到事件悔橄。
    private static final int MAX_RECYCLED = 32;
    private static final Object sRecycleLock = new Object[0];
    //這一個(gè)static的值。所有的ViewGroup對(duì)象共用一個(gè)這點(diǎn)很重要
    private static TouchTarget sRecycleBin;
    private static int sRecycledCount;

    public static final int ALL_POINTER_IDS = -1; // all ones

    // The touched child view.
    public View child;

    // The combined bit mask of pointer ids for all pointers captured by the target.
    public int pointerIdBits;

    // The next target in the target list.
    public TouchTarget next;

    private TouchTarget() {
    }
    //獲取一個(gè)可以復(fù)用的target
    public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
        if (child == null) {
            throw new IllegalArgumentException("child must be non-null");
        }

        final TouchTarget target;
        synchronized (sRecycleLock) {
            //如果第一次為null就會(huì)創(chuàng)建一個(gè)新的
            if (sRecycleBin == null) {
                target = new TouchTarget();
            } else {
                //這基本就是鏈表操作
                //會(huì)在sRecycleBin取一個(gè)出來腺毫,將對(duì)象傳遞出去
                target = sRecycleBin;
                sRecycleBin = target.next;
                //sRecycleBin 的count-1 ,因?yàn)橐瞥鋈チ艘粋€(gè)
                 sRecycledCount--;
                target.next = null;
            }
        }
        target.child = child;
        target.pointerIdBits = pointerIdBits;
        return target;
}
    //對(duì)this進(jìn)行回收操作癣疟,將它放進(jìn)sRecycleBin的鏈表里
    public void recycle() {
        if (child == null) {
            throw new IllegalStateException("already recycled once");
        }
        //基本鏈表操作
        synchronized (sRecycleLock) {
            if (sRecycledCount < MAX_RECYCLED) {
                next = sRecycleBin;
                sRecycleBin = this;
                sRecycledCount += 1;
            } else {
                next = null;
            }
            child = null;
    }
}

總結(jié):

  • ViewGroup的事件會(huì)先判斷是否被攔截,如果true就調(diào)用super.dispatchTouchEvent也就是自己的View父類的dispatchTouchEven否則就會(huì)下發(fā)child.dispatchTouchEvent
  • Down事件是一系列事件的開端潮酒,所以很重要睛挚,花費(fèi)的事件也最長
  • 子View可以通過調(diào)用getParent().requestDisallowInterceptTouchEvent(true)通知ViewGroup不攔截事件
  • ViewGroup的onTouchEvent()沒有實(shí)現(xiàn),是用的View的
  • ViewGroup通過TouchTarget來緩存down事件接收的View,然后其他事件通過TouchTarget來分發(fā)事件急黎,防止再次循環(huán)遍歷扎狱,提高效率
  • TouchTarget是一個(gè)單鏈表實(shí)現(xiàn)的對(duì)象池侧到,這個(gè)思路值得學(xué)習(xí)。Android源碼內(nèi)部有很多類似的設(shè)計(jì)
    下邊看一下View的淤击,View的事件非常簡單床牧,因?yàn)樗粫?huì)再繼續(xù)往下分發(fā)了。所以只要判斷處不處理就可以了
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.isTargetAccessibilityFocus()) {
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        event.setTargetAccessibilityFocus(false);
    }

    boolean result = false;

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        //5.0內(nèi)嵌滑動(dòng)的處理
        stopNestedScroll();
    }
    //跟ViewGroup一樣的判斷
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
  
        ListenerInfo li = mListenerInfo;
        //優(yōu)先執(zhí)行l(wèi)i.mOnTouchListener.onTouch方法遭贸。
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //如果執(zhí)行l(wèi)i.mOnTouchListener.onTouch方法返回為true就不會(huì)再次執(zhí)行onTouchEvent了這個(gè)需要注意
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

可以看到

  • View的dispatchTouchEvent方法很簡單戈咳,并沒有再次分發(fā)事件的邏輯了
  • onTouch優(yōu)先于onTouchEvent執(zhí)行,并且返回true之后onTouchEvent就不再執(zhí)行
    所以事件分發(fā)的總體流程就是


    image.png

    最后再分析一下View的onTouchEvent事件就完事了

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    //如果ENABLED為false 則直接return
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        //如果ENABLED為false 則直接return 不在繼續(xù)后續(xù)操作
        return clickable;
    }
    if (mTouchDelegate != null) {
        //如果有TouchDelegate則直接返回true
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    handleTooltipUp();
                }
                if (!clickable) {
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // take focus if we don't have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (prepressed) {
                     
                        setPressed(true, x, y);
                    }
                    //mHasPerformedLongPress 會(huì)有1中情況會(huì)true  只有當(dāng)觸摸了超過500ms并且onLongClick回調(diào)返回了true mHasPerformedLongPress就會(huì)true 可查看源碼比較清晰壕吹,
                    //這種情況下就不會(huì)出發(fā)點(diǎn)擊事件
                    //所以就會(huì)又2種情況 低于500ms會(huì)移除長按事件著蛙,并觸發(fā)點(diǎn)擊事件,超過500ms之后并且設(shè)置了長按監(jiān)聽會(huì)優(yōu)先處理長按事件然后根據(jù)返回值來處理是否執(zhí)行下邊代碼
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // This is a tap, so remove the longpress check
                        //移除長按事件
                        removeLongPressCallback();

                        if (!focusTaken) {
                             //點(diǎn)擊事件
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }

                   // 省去若干代碼

                    removeTapCallback();
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                }
                //mHasPerformedLongPress 改成false
                mHasPerformedLongPress = false;

                if (!clickable) {
                    checkForLongClick(0, x, y);
                    break;
                }

                if (performButtonActionOnTouchDown(event)) {
                    break;
                }

                boolean isInScrollingContainer = isInScrollingContainer();

                // For views inside a scrolling container, delay the pressed feedback for
                // a short period in case this is a scroll.
                if (isInScrollingContainer) {
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    //發(fā)送一個(gè)100ms的點(diǎn)擊任務(wù)
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    //mPendingCheckForTap 里邊會(huì)發(fā)送一個(gè)500ms的長按事件的延遲任務(wù) 是一個(gè)CheckForLongPress的類
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                    setPressed(true, x, y);
                    checkForLongClick(0, x, y);
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                if (clickable) {
                    setPressed(false);
                }
                removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                break;

            case MotionEvent.ACTION_MOVE:
                if (clickable) {
                    drawableHotspotChanged(x, y);
                }

              ////省去若干代碼
                break;
        }

        return true;
    }

    return false;
}

總結(jié)

  • 一個(gè)時(shí)間序列從手指觸摸屏幕到手指離開屏幕耳贬,在這個(gè)過程中產(chǎn)生一系列事件踏堡,以DOWN事件開始,中間含有不定數(shù)的MOVE事件咒劲,以UP事件結(jié)束
  • 正常情況下顷蟆,一個(gè)事件序列只能被一個(gè)View攔截并且消耗
  • 某個(gè)View一旦決定攔截,那么這個(gè)事件序列都將由它的onTouchEvent處理腐魂,并且它的OnInterceptTouchEvent不會(huì)再調(diào)用
  • 某個(gè)View一旦開始處理事件帐偎,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件都不會(huì)再交給它處理蛔屹,并且重新交由它的父級(jí)元素處理(父元素onTouchEvent被調(diào)用)
  • 事件的傳遞過程是由外向內(nèi)的削樊,即事件總是先傳遞給父元素,然后再由父元素分發(fā)給子View,通過requestDisallowInterceptTouchEvent方法可以在子View中干預(yù)父元素的事件分發(fā)過程兔毒,ACTION_DOWN除外
  • ViewGroup默認(rèn)不攔截任何事件漫贞,即onInterceptTouchEvent默認(rèn)返回false。View沒有onInterceptTouchEvent方法育叁,一旦有點(diǎn)擊事件傳遞給它迅脐,那么它的onTouchEvent方法就會(huì)被調(diào)用
  • View的onTouchEvent默認(rèn)會(huì)消耗事件(返回true),除非它是不可點(diǎn)擊的(clickable和longClickable同時(shí)為false)View的longClickable默認(rèn)都是false豪嗽,clickable要分情況谴蔑,比如Button的clickable默認(rèn)為true,TextView的默認(rèn)為false
  • View的enable屬性不影響onTouchEvent的默認(rèn)返回值昵骤。哪怕一個(gè)View是disable狀態(tài)的树碱,只要它的clickable或者longClickable有一個(gè)為true,那么它的onTouchEvent就會(huì)返回true
  • onClick回響應(yīng)的前提是當(dāng)前View是可點(diǎn)擊的,并且收到了ACTION_DOWN和ACTION_UP的事件变秦,并且受長按事件影響成榜,當(dāng)長按事件返回true時(shí),onClick不會(huì)響應(yīng)
  • onLongClick在ACTION_DOWN里判斷是否進(jìn)行響應(yīng)蹦玫,要想執(zhí)行長按事件該View必須是longClickable的并且設(shè)置了OnLongClickListener
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赎婚,一起剝皮案震驚了整個(gè)濱河市刘绣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挣输,老刑警劉巖纬凤,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異撩嚼,居然都是意外死亡停士,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門完丽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恋技,“玉大人,你說我怎么就攤上這事逻族◎叩祝” “怎么了?”我有些...
    開封第一講書人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵聘鳞,是天一觀的道長薄辅。 經(jīng)常有香客問我,道長抠璃,這世上最難降的妖魔是什么站楚? 我笑而不...
    開封第一講書人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮鸡典,結(jié)果婚禮上源请,老公的妹妹穿的比我還像新娘枪芒。我一直安慰自己彻况,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開白布舅踪。 她就那樣靜靜地躺著纽甘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抽碌。 梳的紋絲不亂的頭發(fā)上悍赢,一...
    開封第一講書人閱讀 52,696評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音货徙,去河邊找鬼左权。 笑死,一個(gè)胖子當(dāng)著我的面吹牛痴颊,可吹牛的內(nèi)容都是我干的赏迟。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蠢棱,長吁一口氣:“原來是場噩夢啊……” “哼锌杀!你這毒婦竟也來了甩栈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤糕再,失蹤者是張志新(化名)和其女友劉穎量没,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體突想,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡殴蹄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喊括。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肌割,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妓蛮,到底是詐尸還是另有隱情,我是刑警寧澤圾叼,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布蛤克,位于F島的核電站,受9級(jí)特大地震影響夷蚊,放射性物質(zhì)發(fā)生泄漏构挤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一惕鼓、第九天 我趴在偏房一處隱蔽的房頂上張望筋现。 院中可真熱鬧,春花似錦箱歧、人聲如沸矾飞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洒沦。三九已至,卻和暖如春价淌,著一層夾襖步出監(jiān)牢的瞬間申眼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來泰國打工蝉衣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留括尸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓病毡,卻偏偏與公主長得像濒翻,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子剪验,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361