Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 結合源碼分析ViewGroup中事件分發流程

結合源碼分析ViewGroup中事件分發流程

編輯:關於Android編程

序言

這篇博文不是對事件分發機制全面的介紹,只是從源碼的角度分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件在ViewGroup中的分發邏輯,了解各個事件在ViewGroup的分發邏輯對理解、解決滑動沖突問題很有幫助。

ViewGroup中事件分發流程

這裡我是用的是2.3.3版本的源碼,原因在於這個版本的源碼易讀,當你理順了整個分發流程,再去看其他更加高級版本的源碼,你會發現思想是一樣。如果一個事件傳遞給ViewGroup,那麼dispatchTouchEvent方法會被調用,以下是對dispatchTouchEvent的分析。

 

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (!onFilterTouchEventForSecurity(ev)) {
            return false;
        }

        final int action = ev.getAction();
	//獲取事件的坐標
        final float xf = ev.getX();
        final float yf = ev.getY();
        final float scrolledXFloat = xf + mScrollX;
        final float scrolledYFloat = yf + mScrollY;
        final Rect frame = mTempRect;

 	//disallowIntercept 默認是false,
	//可以通過requestDisallowItercepctTouchEvent來設置參數
	//被設置成true後,ViewGroup無法攔截除ACTION_DOWN以外的事件(只能攔截Down)
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
	
 	//這裡是ACTION_DOWN的處理邏輯
        if (action == MotionEvent.ACTION_DOWN) {
            if (mMotionTarget != null) {
                // this is weird, we got a pen down, but we thought it was
                // already down!
                //We should probably send an ACTION_UP to the current target
                mMotionTarget = null;
            }
            // If we're disallowing intercept or if we're allowing and we didn't
            // intercept
		//默認情況下disallowIntercept為false,表示允許攔截, 
              //默認情況ViewGroup的onInterceptTouchEvent返回false
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
                // reset this event's action (just to protect ourselves)
                ev.setAction(MotionEvent.ACTION_DOWN);
                // We know we want to dispatch the event down, find a child
                // who can handle it, start with the front-most child.
                final int scrolledXInt = (int) scrolledXFloat;
                final int scrolledYInt = (int) scrolledYFloat;
                final View[] children = mChildren;
                final int count = mChildrenCount;
                for (int i = count - 1; i >= 0; i--) {//遍歷子View
                    final View child = children[i];
			//判斷子元素是否可以接收到事件,兩條件決定
			//條件一:子View是VISIBLE或者在播動畫
			//條件二:點擊坐標落在子View區域內(體現在內嵌的if)
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                            || child.getAnimation() != null) {
                        child.getHitRect(frame);
                       //判斷是否點擊坐標落在子控件區域內
                        if (frame.contains(scrolledXInt, scrolledYInt)) {
                            final float xc = scrolledXFloat - child.mLeft;
                            final float yc = scrolledYFloat - child.mTop;
                            ev.setLocation(xc, yc);
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                            //將事件派發給子View,返回true表示子View處理該事件,
                            if (child.dispatchTouchEvent(ev))  {
                                // Event handled, we have a target now.
                                mMotionTarget = child;//將處理事件的目標View保存在變量
                                return true;
                            }
                            // The event didn't get handled, try the next view.
                            // Don't reset the event's location, it's not
                            // necessary here.
                        }
                    }
                }
            }
        }

        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
                (action == MotionEvent.ACTION_CANCEL);

        if (isUpOrCancel) {
           //重置mGroupFlags,
	//使得在下一個事件ACTION_DOWN來臨時disallowIntercept為false
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // The event wasn't an ACTION_DOWN, dispatch it to our target if
        // we have one.
        final View target = mMotionTarget;
        if (target == null) {//沒有找到可以處理事件的子View
            // We don't have a target, this means we're handling the
            // event as a regular view.
            ev.setLocation(xf, yf);
            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
                ev.setAction(MotionEvent.ACTION_CANCEL);
                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            }
            //子控件不處理,所以此處判斷一下自己是否處理
	    //此時ViewGroup調用的是父類View的dispatchTouchEvent
            return super.dispatchTouchEvent(ev);
        }

        // if have a target, see if we're allowed to and want to intercept its
        // events
	//允許並且想要攔截事件
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
            final float xc = scrolledXFloat - (float) target.mLeft;
            final float yc = scrolledYFloat - (float) target.mTop;
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            ev.setAction(MotionEvent.ACTION_CANCEL);
            ev.setLocation(xc, yc);
            if (!target.dispatchTouchEvent(ev)) {
                // target didn't handle ACTION_CANCEL. not much we can do
                // but they should have.
            }
            // clear the target
            mMotionTarget = null;
            // Don't dispatch this event to our own view, because we already
            // saw it when intercepting; we just want to give the following
            // event to the normal onTouchEvent().
            return true;
        }

        if (isUpOrCancel) {
            mMotionTarget = null;
        }

        // finally offset the event to the target's coordinate system and
        // dispatch the event.
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        ev.setLocation(xc, yc);

        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            mMotionTarget = null;
        }

        return target.dispatchTouchEvent(ev);
    }


 

 

我們知道一些操作會產生事件,比方說在屏幕上滑動一下,這樣會產生一系列事件,但這些事件是屬於同一序列,它以ACTION_DOWN事件開始,中間有若干個ACTION_MOVE事件,以ACTION_UP事件結束。

ACTION_DOWN事件分發過程

ACTION_DOWN事件被分發到ViewGroup的時候是如何進行邏輯判斷的呢,在第31行代碼可以看到,首先會通過條件:disallowIntercept||!onInterceptTouchEvent判斷是否攔截,disallowIntercept 默認是false,可以通過requestDisallowItercepctTouchEvent來設置參數,若這個條件不成立表示攔截則此時mMotionTarget = null,則來到第80行,那麼target = null,表示沒有找到可以處理事件的子控件,接下來執行第91行代碼,此時調用super.dispatchTouchEvent即View的dispatchTouchEvent方法表示ViewGroup嘗試自己處理事件;若條件成立表示不攔截,見第40行代碼首先是遍歷該ViewGroup的子控件,結合第45、49行代碼可以知道在每遍歷一個子控件的時候,首先判斷子控件是不是visiable或者正在播動畫並且點擊事件的坐標落在子控件的區域內,假如兩個條件同時滿足則表明子控件可以接收事件,這時候會來到第55行代碼,通過調用child.dispatchTouchEvent將事件分發給子控件,如果child.dispatchTouchEvent返回true,則表示事件被該子控件消耗了,此時執行第57行,該子控件被視作消耗事件的目標View並將其保存mMotionTarget變量中,返回true結束循環遍歷;如果child.dispatchTouchEvent返回false,則表示該子控件沒有消耗事件,如果該子控件不是ViewGroup遍歷的最後一個子控件,則在繼續循環遍歷下一個子控件。如果此時該子控件是ViewGroup遍歷的最後一個子控件,則表明所有的ViewGroup的子控件均不能處理事件,此時循環遍歷結束,則mMotiononView = null,程序來到第80行,那麼target = null,接下來調用super.dispatchTouchEvent即View的dispatchTouchEvent方法表明ViewGroup嘗試自己處理事件.

 

ACTION_DOWN事件用流程圖表示如下:

\

 

 

ACTION_MOVE事件分發過程

 

ACTION_MOVE事件被分發到ViewGroup的時候,從第81行可以看到首先會判斷target是否等於null,若等於null,表示子控件均不能消耗事件,則調用super.dispatchTouchEvent即View的dispatchTouchEvent來處理事件。若不等於空,此時會來到第97行,此時通過條件(!disallowIntercept&&onInterceptTouchEvent(ev))判斷是否攔截,若條件不成立表示不攔截則執行第131行代碼,調用target.dispatchTouchEvent將事件分發給目標子控件處理,如果攔截則首先生成ACTION_CANCEL事件(見第101行)並分發給目標子控件target(見第103行),告知事件已被攔截,之後執行第108行將mMotionTarget重置為null,目的是讓接下來的ACTION_UP事件直接能給ViewGroup自己處理,最後在第112行返回true表示事件被消耗。

ACTION_MOVE事件用流程圖表示如下:

\

 

 

ACTION_UP事件分發過程

 

ACTION_UP分發到ViewGroup的時候,首先會通過執行mGrouFlags & = ~FALG_DISALLOW_INTERCEPT使得下一個ACTION_DOWN事件來臨時disallowIntecept重置為默認的false,之後的處理邏輯和ACTION_MOVE基本一致,這裡不再重復

ACTION_UP事件用流程圖表示如下:

\

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved