View事件分發(fā)

一個(gè)點(diǎn)擊或觸摸事件會(huì)被內(nèi)部封裝成MotionEvent對(duì)象截驮。而事件分發(fā)就是將MotionEvent往子View傳遞。有View的地方就有Window担神,View必須依附于Window進(jìn)行展示验靡。我們通過(guò)setContentView()設(shè)置的布局弯洗,會(huì)被添加到DecorView中能真,DecorView會(huì)被添加到Window,而Window則被加到Activity中,這里用一張圖展示它們的層次關(guān)系粉铐。


image.png

事件的分發(fā)會(huì)經(jīng)歷3個(gè)方法疼约,往下遞歸調(diào)用,分別是dispatchTouchEvent蝙泼、onInterceptTouchEvent和onTouchEvent方法程剥。

從子View的角度來(lái)說(shuō),dispatchTouchEvent可以理解為接收到事件的意思汤踏,也就是當(dāng)父View要把事件發(fā)給我時(shí)织鲸,會(huì)調(diào)用dispatchTouchEvent來(lái)告知我接收這個(gè)事件,所以Activity溪胶、ViewGroup和View都有這個(gè)方法搂擦。

onInterceptTouchEvent是把事件攔截了的意思,Activity是下發(fā)的源頭哗脖,不能還沒(méi)發(fā)出去就把事件攔截掉瀑踢,而View不是容器,是沒(méi)有子View的才避,只有消費(fèi)和不消費(fèi)事件一說(shuō)橱夭,沒(méi)有必要攔截,所以桑逝,Activity和View都不會(huì)有onInterceptTouchEvent這個(gè)方法棘劣。

最后一個(gè)onTouchEvent是用來(lái)決定是否要消費(fèi)事件的,當(dāng)然消費(fèi)事件不止它一個(gè)楞遏,它的優(yōu)先級(jí)也不是最高的茬暇,還有設(shè)置觸摸監(jiān)聽的setOnTouchListener和點(diǎn)擊事件onClick方法。

這里假設(shè)一個(gè)最簡(jiǎn)單的場(chǎng)景橱健,ContentView里有一個(gè)ViewGroup而钞,ViewGroup里放了一個(gè)View。下面通過(guò)源碼一步步分析拘荡,但手指按下時(shí)臼节,Activity 會(huì)首先收到一個(gè)MotionEvent事件,它為ACTION_DOWN類型的珊皿。

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {

    //事件分發(fā)源頭
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //通過(guò)Window下發(fā)
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //自己處理
        return onTouchEvent(ev);
    }

    //空實(shí)現(xiàn)
    public void onUserInteraction() {
    }
}

Window是Activity的下一層网缝,而Activity接到事件后,并沒(méi)做什么處理蟋定,而是將事件交給Window去做分發(fā)粉臊。如果superDispatchTouchEvent返回了true,即說(shuō)明這個(gè)事件被某個(gè)控件消費(fèi)了驶兜,流程結(jié)束扼仲。如果返回false远寸,說(shuō)明這個(gè)事件沒(méi)有控件消費(fèi),則調(diào)用自己的onTouchEvent把事件消費(fèi)掉屠凶。

接下來(lái)看Window如何將事件傳遞給ViewGroup驰后。

public abstract class Window {
  
  public abstract boolean superDispatchTouchEvent(MotionEvent event);
 
}

Window 是一個(gè)抽象類,superDispatchTouchEvent也是一個(gè)抽象方法矗愧,它的唯一實(shí)現(xiàn)類為PhoneWindow灶芝。

public class PhoneWindow extends Window implements MenuBuilder.Callback {
  private DecorView mDecor;
   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
}

PhoneWindow也什么都沒(méi)做,將事件交給Window去做分發(fā)唉韭。

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

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

}

前面的調(diào)用方法都是superDispatchTouchEvent夜涕,而不是dispatchTouchEvent,而這里我們第一次看到dispatchTouchEvent的調(diào)用属愤,也就是說(shuō)女器,從此處,事件開始向下一層層分發(fā)給子View了春塌。

而DecorView 繼承自FrameLayout 晓避,我們知道FrameLayout 是一個(gè)容器,即繼承了ViewGroup只壳,而且FrameLayout 也沒(méi)有重寫dispatchTouchEvent方法俏拱。

public class FrameLayout extends ViewGroup {
}

因此,事件將通過(guò)ViewGroup來(lái)處理吼句。上面提到過(guò)锅必,此時(shí)我們可以認(rèn)為,DecorView是第一個(gè)接到事件的ViewGroup惕艳。由于dispatchTouchEvent方法比較長(zhǎng)搞隐,我們一段段分析。

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
  
    protected int mGroupFlags;
  
    private TouchTarget mFirstTouchTarget;

    //DecorView開始事件分發(fā)
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        //事件是否被處理了
        boolean handled = false;

        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

           //每次ACTION_DOWN都重新開始分發(fā)的流程远搪,所以清理掉之前的事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {
               //清除之前的所有事件
                cancelAndClearTouchTargets(ev);
                //重置
                resetTouchState();
            }
            
            //檢查是否要攔截
            final boolean intercepted;
            //當(dāng)不是Down事件時(shí):
            // 如果子View消費(fèi)了事件劣纲,mFirstTouchTarget不為null,
            // mFirstTouchTarget為null,說(shuō)明子View都沒(méi)有消費(fèi)谁鳍,直接跳else執(zhí)行攔截
            if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
                //是否攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //不攔截進(jìn)入判斷
                if (!disallowIntercept) {
                    //如果重寫onInterceptTouchEvent返回true癞季,此處intercepted = true進(jìn)行攔截
                    intercepted = onInterceptTouchEvent(ev);//默認(rèn)為false
                    //恢復(fù)事件防止其改變
                    ev.setAction(action); 
                } else {
                    //不攔截
                    intercepted = false;
                }
            } else {
                //子控件沒(méi)有消費(fèi)DOWN事件
                intercepted = true;
            }

這里有幾個(gè)關(guān)鍵的變量,intercepted用來(lái)控制事件是否要繼續(xù)往下分發(fā)倘潜,初始值為false绷柒,即不攔截。正常情況下涮因,當(dāng)按下手指時(shí)废睦,第一個(gè)事件會(huì)為ACTION_DOWN,即actionMasked是ACTION_DOWN养泡。mFirstTouchTarget 嗜湃,見名知意奈应,它表示第一個(gè)事件由誰(shuí)消費(fèi),但不包括自己购披,意思是钥组,如果有子控件消費(fèi)了down事件,那么mFirstTouchTarget 就會(huì)指向那個(gè)對(duì)象今瀑,不為null,如果沒(méi)有子控件消費(fèi)down事件点把,則為null橘荠。

那么當(dāng)用戶按下手指餅滑動(dòng)時(shí),第二個(gè)傳遞過(guò)來(lái)的事件則為ACTION_MOVE郎逃,actionMasked == MotionEvent.ACTION_DOWN不成立哥童,如果剛才的ACTION_DOWN事件子控件沒(méi)有消費(fèi),mFirstTouchTarget !=null也不成立褒翰,將進(jìn)入else判斷贮懈,intercepted 置為true。

也就是說(shuō)优训,子控件一開始沒(méi)有消費(fèi)ACTION_DOWN事件的話朵你,那則往后的事件都默認(rèn)攔截,不會(huì)往下發(fā)了揣非。同時(shí)也不會(huì)再執(zhí)行到onInterceptTouchEvent這個(gè)方法抡医。


   private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        //重置為0,即false
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

  //請(qǐng)求父控件不攔截事件
   public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        //如果子控件請(qǐng)求的和當(dāng)前狀態(tài)相同早敬,無(wú)需往下執(zhí)行
        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
           return;
        }
        //是否攔截
        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
        //有父類繼續(xù)告知
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

FLAG_DISALLOW_INTERCEPT是一個(gè)標(biāo)志位忌傻,它可以通過(guò)requestDisallowInterceptTouchEvent來(lái)設(shè)置是否進(jìn)行攔截方法的調(diào)用,它一般用于子View來(lái)請(qǐng)求父控件攔截或不攔截事件搞监,但只能作用于非ACTION_DOWN的情況下水孩。因?yàn)槊看蜛CTION_DOWN都是一輪分發(fā)的重新開始,也就是說(shuō)之前的事件都廢棄了琐驴。resetTouchState會(huì)重置狀態(tài)俘种,包括FLAG_DISALLOW_INTERCEPT這個(gè)標(biāo)志位。

所以棍矛,接到ACTION_DOWN事件時(shí)安疗,總會(huì)執(zhí)行到onInterceptTouchEvent方法。

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

這個(gè)方法很簡(jiǎn)單够委,如果子類不重寫的話默認(rèn)返回false荐类,不攔截。

            //檢查事件是否取消
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            //如果有必要茁帽,為DOWN事件檢查所有的目標(biāo)對(duì)象
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;

            TouchTarget newTouchTarget = null;//鏈表節(jié)點(diǎn)
            boolean alreadyDispatchedToNewTouchTarget = false;
            //事件沒(méi)有取消玉罐,同時(shí)沒(méi)有攔截屈嗤,則往下分發(fā)
            if (!canceled && !intercepted) {
                //如果事件為起始事件
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); 
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                   
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    //newTouchTarget為null,并且有子View
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                      
                        final ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //倒序?qū)ふ沂欠裼凶覸iew處理此事件
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);

                            //如果子View有動(dòng)畫在執(zhí)行吊输,或者沒(méi)有落在這個(gè)View的范圍內(nèi)饶号,就繼續(xù)找下一個(gè)子View
                            if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }

                            //將接收事件的子View賦值給newTouchTarget 
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                   newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            //1,如果子View是ViewGroup季蚂,會(huì)遞歸調(diào)用此dispatchTouchEvent往下分發(fā)茫船,如果返回某個(gè)View返回true,則說(shuō)明被消費(fèi)了扭屁,否則沒(méi)有消費(fèi)算谈,繼續(xù)下發(fā),直到最后一個(gè)是View料滥,即2的情況
                            //2然眼,如果子View不包含子View了,dispatchTransformedTouchEvent會(huì)調(diào)用View的dispatchTouchEvent方法葵腹,進(jìn)而調(diào)用onTouchEvent如果返回true高每,說(shuō)明被消費(fèi)了,否則沒(méi)有消費(fèi)
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                              
                                 ......



  //是否在執(zhí)行動(dòng)畫
   private static boolean canViewReceivePointerEvents(View child) {
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                || child.getAnimation() != null;
    }

此時(shí)如果事件沒(méi)有被取消践宴,則會(huì)遍歷所有的子控件鲸匿,看哪一個(gè)能接收這個(gè)事件,條件有兩個(gè)浴井,即控件是否有動(dòng)畫在執(zhí)行晒骇,和是否落在觸摸的區(qū)域。getTouchTarget這個(gè)方法用于查找這個(gè)View是否在當(dāng)前的ViewGroup中磺浙。

private TouchTarget getTouchTarget(View child) {
        for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
            if (target.child == child) {
                return target;
            }
        }
        return null;
    }

當(dāng)View被添加到ViewGroup中時(shí)洪囤,會(huì)通過(guò)鏈表結(jié)構(gòu),將View封裝成一個(gè)TouchTarget的鏈表節(jié)點(diǎn)撕氧,這里是遍歷鏈表瘤缩,如果找到了,返回這個(gè)TouchTarget節(jié)點(diǎn)伦泥,并跳出上面的遍歷循環(huán)剥啤。

此時(shí),流程執(zhí)行到dispatchTransformedTouchEvent方法不脯,它是處理事件繼續(xù)向下分發(fā)的關(guān)鍵方法府怯。

    //傳遞到子View的事件
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        //是否消費(fèi)了事件
        final boolean handled;

        final int oldAction = event.getAction();
        //取消事件
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            //沒(méi)有子View消費(fèi)事件,看自己消不消費(fèi)
            if (child == null) {
                //直接調(diào)用父類的dispatchTouchEvent
                handled = super.dispatchTouchEvent(event);
            } else {
                //如果有子View防楷,將cancle事件傳遞到子View中
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
       
        //計(jì)算即將被傳遞的點(diǎn)的數(shù)量
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        //如果事件沒(méi)有相應(yīng)的點(diǎn)牺丙,那么丟棄該事件
        if (newPointerIdBits == 0) {
            return false;
        }

        //保存坐標(biāo)轉(zhuǎn)換后的MotionEvent
        final MotionEvent transformedEvent;
        //如果事件點(diǎn)的數(shù)量一致
        if (newPointerIdBits == oldPointerIdBits) {
            //子元素為空,或有一個(gè)單位矩陣
            if (child == null || child.hasIdentityMatrix()) {
                //空的情況
                if (child == null) {
                    //調(diào)用父類的dispatchTouchEvent
                    handled = super.dispatchTouchEvent(event);
                } else {
                    //獲取xy方向的偏移量(使用scrollTo或scrollBy滾動(dòng))
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    //將MotionEvent進(jìn)行坐標(biāo)變換
                    event.offsetLocation(offsetX, offsetY);

                    //變換后的MotionEvent傳給子View
                    handled = child.dispatchTouchEvent(event);

                    //復(fù)位MotionEvent以便之后再次使用
                    event.offsetLocation(-offsetX, -offsetY);
                }
                //返回是否消費(fèi)
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

      
        if (child == null) {
            //如果沒(méi)有包含子view了,則使用父類View的dispatchTouchEvent
            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冲簿,則調(diào)用子View的dispatchTouchEvent往下分發(fā)
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        
        transformedEvent.recycle();
        return handled;
    }

這里先對(duì)父類/子類和父View/子View名詞做下強(qiáng)調(diào)粟判,前一組指的是繼承關(guān)系,后一組是包含關(guān)系峦剔。

  • 如果上面遍歷找到接收的子View不為null档礁,則調(diào)用子View的dispatchTouchEvent方法,將事件傳遞給子View吝沫。
  • 如果子View為null呻澜,事件不再往下傳遞,調(diào)用父類惨险,即View的dispatchTouchEvent方法易迹。

我們知道,控件要么是ViewGroup平道,要么是View。這意味著供炼,無(wú)論是ViewGroup還是View一屋,如果進(jìn)入了View類的dispatchTouchEvent方法,也就開始是否消費(fèi)事件的流程了袋哼。

我們回頭再分析View的dispatchTouchEvent方法冀墨。先假設(shè)handled最后返回了true,則dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)判斷滿足涛贯。

                  ......

                  if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                               //子View接收到事件的時(shí)間戳
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                 //找到在列表中的角標(biāo)
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //如果子view消費(fèi)了诽嘉,把消費(fèi)了事件的View對(duì)應(yīng)的消費(fèi)節(jié)點(diǎn),賦值給newTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                        if (preorderedList != null) preorderedList.clear();
                       }

                        //如果此時(shí)觸發(fā)沒(méi)有找到接收的View弟翘,將最近一次的目標(biāo)引用虫腋,作為當(dāng)前事件的的目標(biāo)引用
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
                    }

接收ACTION_DOWN事件的控件找到了,于是稀余,通過(guò)addTouchTarget方法悦冀,一開始定義的mFirstTouchTarget變量便被賦值,指向?qū)?yīng)的控件睛琳,并終止對(duì)子View的遍歷盒蟆。如果上面的handled返回了false,而且還有子View的話师骗,將繼續(xù)遍歷尋找历等。

 private TouchTarget addTouchTarget(View child, int pointerIdBits) {
        //將目標(biāo)控件構(gòu)建成一個(gè)TouchTarget 節(jié)點(diǎn)
        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        //賦值給mFirstTouchTarget 
        mFirstTouchTarget = target;
        return target;
    }

如果找尋到最后,mFirstTouchTarget 仍舊為null辟癌,回看到我們一開始的分析寒屯,intercepted將置為ture,同一輪中后面所有的事件都被默認(rèn)攔截了愿待。也不會(huì)再遍歷包含的子View了浩螺。

                        ......

                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }
            //沒(méi)有子View消費(fèi)此事件
            if (mFirstTouchTarget == null) {
                //child為null靴患,會(huì)執(zhí)行View的dispatchTouchEvent
                handled = dispatchTransformedTouchEvent(ev, canceled, null,  TouchTarget.ALL_POINTER_IDS);
            } else {
               //往后的事件都分發(fā)給mFirstTouchTarget 
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;
                        //mFirstTouchTarget 消費(fèi)事件
                        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;
                }
            }
            
            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;
    }

這里的代碼非常關(guān)鍵,無(wú)論mFirstTouchTarget 是不是null要出,后面的事件都不會(huì)再遍歷找尋子View鸳君。

  • 當(dāng)mFirstTouchTarget 不為null時(shí),后面的事件都直接交給mFirstTouchTarget這個(gè)控件患蹂。

  • 當(dāng)mFirstTouchTarget 為null時(shí)或颊,事件將由自己做消費(fèi)判斷。

出現(xiàn)第二種情形包含3種可能:

  • ViewGroup沒(méi)有子View传于。
  • ViewGroup所有的子View都沒(méi)有落在觸摸的范圍囱挑。
  • 子View的dispatchTouchEvent返回false,一般在onTouchEvent中返回沼溜。

相同的是平挑,最終都會(huì)調(diào)用dispatchTransformedTouchEvent方法,但傳入的參數(shù)不同系草,最主要是第3個(gè)參數(shù)通熄,一個(gè)傳入null菲驴,表示自己消費(fèi)昆汹,一個(gè)傳入target.child,目標(biāo)控件消費(fèi)锉罐。但最終都進(jìn)入View的dispatchTouchEvent方法能耻。

public boolean dispatchTouchEvent(MotionEvent event) {
        boolean result = false;

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

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
          //ListenerInfo封裝了手勢(shì)監(jiān)聽赏枚,觸摸監(jiān)聽等監(jiān)聽接口
            ListenerInfo li = mListenerInfo;
            
            //設(shè)置的OnTouchListener
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //如果上面result不為true,執(zhí)行onTouchEvent
            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;
    }

如源碼所示晓猛,OnTouchListener優(yōu)先級(jí)大于onTouchEvent饿幅,而且隨后的分析將看到,onClick是在onTouchEvent中被調(diào)用的戒职,所以诫睬,如果setOnTouchListener的onTouch返回true,那么onTouchEvent方法不會(huì)執(zhí)行帕涌,setOnClickListener的onClick回調(diào)也不會(huì)執(zhí)行摄凡。

我們依舊假設(shè)OnTouchListener返回false,事件進(jìn)入onTouchEvent方法蚓曼。

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

        //不可用狀態(tài)也消費(fèi)
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            //只要可點(diǎn)擊就返回true
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

        //設(shè)置了代理
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        //注意if作用域結(jié)束后返回true
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                              setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress) {//長(zhǎng)按則不響應(yīng)點(diǎn)擊事件
                            
                            removeLongPressCallback();
                      
                            if (!focusTaken) {
                                
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }

                                //點(diǎn)擊事件進(jìn)入隊(duì)列執(zhí)行
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;
                //按下
                case MotionEvent.ACTION_DOWN:
                  //是否長(zhǎng)按
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }
              
                    boolean isInScrollingContainer = isInScrollingContainer();

                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        //狀態(tài)改為按下亲澡,比如button這時(shí)會(huì)變背景,需要刷新ui
                        setPressed(true, x, y);
                        checkForLongClick(0);//檢查并保存是否長(zhǎng)按狀態(tài)
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);
                   //是否超出View的范圍
                    if (!pointInView(x, y, mTouchSlop)) {
     
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                             //移除長(zhǎng)按的監(jiān)聽
                            removeLongPressCallback();
                            //不是按下狀態(tài)
                            setPressed(false);
                        }
                    }
                    break;
            }
            return true;
        }
        return false;
    }

我們可以得出這樣的結(jié)論:View的enable屬性并不影響事件的消費(fèi)纫版,只要CLICKABLE和LONG_CLICKABLE有一個(gè)為true床绪,onTouchEvent就會(huì)返回ture,將事件消費(fèi)。

ACTION_UP事件發(fā)生時(shí)癞己,如果外部進(jìn)行setClickListener膀斋,將觸發(fā)performClick(),回調(diào)onClick方法痹雅。

 public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            //回調(diào)onClick方法
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

View的LONG_CLICKABLE默認(rèn)為false仰担,不可點(diǎn)擊的View,CLICKABLE默認(rèn)為false绩社,比如TextView摔蓝,可點(diǎn)擊的View,CLICKABLE則為true愉耙,比如Button贮尉。但是,我們知道View有監(jiān)聽點(diǎn)擊的方法朴沿,setOnLongClickListener會(huì)將LONG_CLICKABLE置為true猜谚,setOnClickListener將CLICKABLE置為true。

public void setOnLongClickListener(OnLongClickListener l) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        getListenerInfo().mOnLongClickListener = l;
    }


 public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

到這里赌渣,整個(gè)事件分發(fā)的流程分析完畢龄毡。事件回調(diào)優(yōu)先級(jí)順序也清晰了:

onTouch > onTouchEvent > onClick

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市锡垄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祭隔,老刑警劉巖货岭,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異疾渴,居然都是意外死亡千贯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門搞坝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搔谴,“玉大人,你說(shuō)我怎么就攤上這事桩撮《氐冢” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵店量,是天一觀的道長(zhǎng)芜果。 經(jīng)常有香客問(wèn)我,道長(zhǎng)融师,這世上最難降的妖魔是什么右钾? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上舀射,老公的妹妹穿的比我還像新娘窘茁。我一直安慰自己,他們只是感情好脆烟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布山林。 她就那樣靜靜地躺著,像睡著了一般浩淘。 火紅的嫁衣襯著肌膚如雪捌朴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天张抄,我揣著相機(jī)與錄音砂蔽,去河邊找鬼。 笑死署惯,一個(gè)胖子當(dāng)著我的面吹牛左驾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播极谊,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼诡右,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了轻猖?” 一聲冷哼從身側(cè)響起帆吻,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咙边,沒(méi)想到半個(gè)月后猜煮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡败许,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年王带,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片市殷。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡愕撰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出醋寝,到底是詐尸還是另有隱情搞挣,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布音羞,位于F島的核電站柿究,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏黄选。R本人自食惡果不足惜蝇摸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一婶肩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧貌夕,春花似錦律歼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至们童,卻和暖如春畔况,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背慧库。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工跷跪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人齐板。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓吵瞻,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親甘磨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子橡羞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

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

  • 事件分發(fā)機(jī)制面試也會(huì)經(jīng)常被提及,如果你能get到要領(lǐng)济舆,并跟面試官深入的靈魂交流一下卿泽,那么一定會(huì)讓面試官對(duì)你印象深刻...
    yturg閱讀 805評(píng)論 0 0
  • Android View 雖然不是四大組件,但其并不比四大組件的地位低滋觉。而View的核心知識(shí)點(diǎn)事件分發(fā)機(jī)制則是不少...
    Flyzend閱讀 51,645評(píng)論 39 323
  • View的事件分發(fā)機(jī)制 View的事件分發(fā)機(jī)制簡(jiǎn)單來(lái)說(shuō)就是將用戶與手機(jī)屏幕的交互事件交由正確的控件進(jìn)行處理签夭,從而可...
    蕉下孤客閱讀 850評(píng)論 0 4
  • Activity構(gòu)成 點(diǎn)擊事件由MotionEvent來(lái)表示,當(dāng)一個(gè)點(diǎn)擊事件產(chǎn)生后椎瘟,事件最先傳遞給Activity...
    臟仙人閱讀 427評(píng)論 0 0
  • Android事件分發(fā)機(jī)制主要由“事件分發(fā)”—>“事件攔截”—>“事件響應(yīng)”這三步來(lái)進(jìn)行邏輯控制的。本文也將從這三...
    wayDevelop閱讀 540評(píng)論 0 1