Android事件分發(fā)源碼分析

  1. 事件分發(fā)
    事件從Activity開始分發(fā),首先Activity 實現(xiàn)了Window.Callback接口,并實現(xiàn)了接口里面的dispatchTouchEvent()方法
public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback,  // Activity 實現(xiàn)了 Window.Callback接口 并實現(xiàn)了接口里面的dispatchTouchEvent()方法
        KeyEvent.Callback,OnCreateContextMenuListener, ComponentCallbacks2,Window.OnWindowDismissedCallback, WindowControllerCallback, AutofillManager.AutofillClient {
         //開始處理事件
        public boolean dispatchTouchEvent(MotionEvent ev) {  
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
              //如果收到 ACTION_DOWN事件, 調(diào)用onUserInteraction(), 里面是一個空實現(xiàn),等用戶自己實現(xiàn)一個交互
                onUserInteraction();  
            }
            if (getWindow().superDispatchTouchEvent(ev)) {  //getWindow()即是PhoneWindow  把事件從Activity傳入到PhoneWindow中,若PhoneWindow返回true,代表消費了此事件
                return true;  //事件被getWindow().superDispatchTouchEvent(ev)里面消費了膳叨,有可能是里面的ViewGroup或View
            }
            //若事件在以上都沒用被消費,則執(zhí)行Activity的onTouchEvent()方法去處理該事件
            return onTouchEvent(ev); 
        }
            
        public boolean onTouchEvent(MotionEvent event) {
            if (mWindow.shouldCloseOnTouch(this, event)) { //查看window是否應該關(guān)閉OnTouch事件 如果關(guān)閉痘系,則finish()當前Activity
                finish();
                return true;
            }
            return false;
    }
            
}

在Activity中調(diào)用getWindow().superDispatchTouchEvent(ev) 菲嘴, 事件從Activity派發(fā)到PhoneWindow在把事件從PhoneWindow派發(fā)到DecorView中

// PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
    // DecorView extends FrameLayout 
    private DecorView mDecor;    //DecorView也即是一個FrameLayout extends ViewGroup
    
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        //在PhoneWindow中又把事件派發(fā)到 mDecor處理
        return mDecor.superDispatchTouchEvent(event);
    }
}


// DecorView.java    
// mDecor.superDispatchTouchEvent(event)
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
     public boolean superDispatchTouchEvent(MotionEvent event) {
         //在此處又調(diào)用了super.dispatchTouchEvent(event) ,即調(diào)用父類的dispatchTouchEvent(event) 方法去處理事件
        return super.dispatchTouchEvent(event);
    }
}

因為FrameLayout extends ViewGroup 汰翠, 即在DecorView中又默認調(diào)用了ViewGroup的dispatchTouchEvent()方法去處理事件

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    /**
     *  事件分發(fā)到ViewGroup中龄坪,在dispatchTouchEvent()方法處理
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        // 
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;   // 事件是否被處理
        if (onFilterTouchEventForSecurity(ev)) {
            //省略部分代碼......
            
            // Check for interception.
            final boolean intercepted;  // 該事件是否被攔截
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    // 調(diào)用onInterceptTouchEvent(ev)方法查看事件是否被攔截,一般情況下默認是不攔截复唤。自定義ViewGroup時可根據(jù)實際開發(fā)情況重寫onInterceptTouchEvent()方法
                    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;
            }

            //省略部分代碼......
        
            //如果事件沒有取消并且沒有被攔截
            if (!canceled && !intercepted) {
                    //省略部分代碼......
             
                    final int childrenCount = mChildrenCount;  // 子view數(shù)量
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        
                        //遍歷子所有view健田, 分發(fā)事件
                        for (int i = childrenCount - 1; i >= 0; i--) { 
                            //省略部分代碼......  
                            
                            //開始把事件派發(fā)給子view處理, 在dispatchTransformedTouchEvent()把事件真正交給子view處理
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                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();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
                    //省略部分代碼......  
                }
            }
           //省略部分代碼...... 
        }

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

    /**
     *  判斷事件是否被攔截(攔截事件是在ViewGroup中處理佛纫,重寫該方法妓局,根據(jù)實際條件去攔截事件分發(fā))
     */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }


    /**
     *  事件派發(fā)給子view處理
     */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                //如果傳入的view為空,則默認調(diào)用super.dispatchTouchEvent(event)呈宇,即View的dispatchTouchEvent(event)方法
                handled = super.dispatchTouchEvent(event);  
            } else {
                // 調(diào)用子view自己的的dispatchTouchEvent()
                handled = child.dispatchTouchEvent(event);  //
            }
            event.setAction(oldAction);
            return handled;
        }

      //省略部分代碼...... 

        // 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();
        //返回事件處理結(jié)果
        return handled;
    }
}

ViewGroup遍歷子view好爬,把事件派發(fā)給View處理,即調(diào)用View中的dispatchTouchEvent()方法處理事件

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    
    /**
    *事件最后傳入到View中處理
    */
     public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        //該view是否是focus
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }
        //處理結(jié)果(是否消費了該事件)
        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
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            // 如果ListenerInfo不為空甥啄, 該View設(shè)置了setOnTouchListener存炮,并且在OnTouchListener接口中的onTouch方法中返回了true,即消費了該事件
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                //以上if判斷都滿足時,則消費了該事件(即處理了此次的事件分發(fā))
                result = true;
            }
            //如果沒有設(shè)置setOnTouchListener或者在在OnTouchListener接口中的onTouch方法中返回false,則執(zhí)行下面的的代碼
            //result為false時型豁,執(zhí)行onTouchEvent方法
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        //省略部分代碼...... 
        
        return result;
    }
    
    
    /**
    * 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();

        //判斷view是否可點擊的,即是否設(shè)置了setClickable() ,Button是默認設(shè)置了setClickable()尚蝌,TextView默認沒有設(shè)置
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        //省略部分代碼...... 
        
        //如果該view是可點擊的迎变,即設(shè)置了setClickable(),或者在xml中設(shè)置了clickable = true
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                   //省略部分代碼...... 
             
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                       //省略部分代碼...... 
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();
                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    //如果該view中設(shè)置了setOnClickListener()方法,則在ACTION_UP事件中會處理 點擊事件
                                    performClickInternal();
                                }
                            }
                        }
                       //省略部分代碼...... 
                    break;
               //省略部分代碼...... 
            return true;
        }
        return false;
    }

     /**
     *處理點擊事件
     */
    private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();
        return performClick();
    }
            
   /**
    * 處理view的點擊事件并消費該事件
    */
    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;
        }
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        notifyEnterOrExitForAutoFillIfNeeded(true);
        return result;
    }

}

事件分發(fā)大致流程圖:


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末飘言,一起剝皮案震驚了整個濱河市衣形,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖谆吴,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倒源,死亡現(xiàn)場離奇詭異,居然都是意外死亡句狼,警方通過查閱死者的電腦和手機笋熬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腻菇,“玉大人胳螟,你說我怎么就攤上這事〕锿拢” “怎么了糖耸?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長丘薛。 經(jīng)常有香客問我嘉竟,道長,這世上最難降的妖魔是什么洋侨? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任舍扰,我火速辦了婚禮,結(jié)果婚禮上凰兑,老公的妹妹穿的比我還像新娘妥粟。我一直安慰自己,他們只是感情好吏够,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布勾给。 她就那樣靜靜地躺著,像睡著了一般锅知。 火紅的嫁衣襯著肌膚如雪播急。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天售睹,我揣著相機與錄音桩警,去河邊找鬼。 笑死昌妹,一個胖子當著我的面吹牛捶枢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播飞崖,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼烂叔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了固歪?” 一聲冷哼從身側(cè)響起蒜鸡,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胯努,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后逢防,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叶沛,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年忘朝,在試婚紗的時候發(fā)現(xiàn)自己被綠了灰署。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡辜伟,死狀恐怖氓侧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情导狡,我是刑警寧澤约巷,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站旱捧,受9級特大地震影響独郎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜枚赡,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一氓癌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贫橙,春花似錦贪婉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至莫湘,卻和暖如春尤蒿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背幅垮。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工腰池, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忙芒。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓示弓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親呵萨。 傳聞我的和親對象是個殘疾皇子奏属,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355