天天看點

事件分發機制詳解(上)

事件分發機制是 Android 開發過程中的重點又是難點,是必須掌握的一個重要的知識點,因為在複雜的頁面經常要處理各種View的滑動沖突。是以要學好它。

一.擁有事件傳遞功能的類

首先在Android中有哪幾個類擁有事件分發傳遞功能呢。

1. Activity

Activity擁有 dispathTouchEvent方法 和 onTouchEvent 方法。

2.ViewGroup(RelativeLayout,LinearLayout,ListView,ScrollView等等)

ViewGroup擁有 dispatchTouchEvent方法、onTouchEvent方法、onInterceptTouchEvent方法。

3.View(Button,TextView等等)

View擁有 dispathTouchEvent方法 和 onTouchEvent 方法。

總結:

也就是說,隻有ViewGroup類才有onInterceptTouchEvent方法。那麼這三個方法的作用是什麼呢?下面講解。

二.三個方法簡單說明

1.事件分發(順序由上到下):dispatchTouchEvent

用來進行事件的分發,如果事件能夠傳遞給目前View,則該方法一定會被調用。傳回結果受目前View的onTouchEvent方法和下級的dispatchTouchEvent方法的影響,表示是否消耗目前事件。

return:

ture:目前View消耗所有事件。

false:停止分發,交由上層控件的onTouchEvent方法進行消費,如果本層控件是Activity,則事件将被系統消費,處理。

super:正常分發。

2.事件攔截(順序由上到下):onInterceptTouchEvent

需注意的是在Activity,ViewGroup,View中隻有ViewGroup有這個方法。故一旦有點選事件傳遞給View,則View的onTouchEvent方法就會被調用。

在dispatchTouchEvent内部使用,用來判斷是否攔截事件。如果目前View攔截了某個事件,那麼該事件序列的其它方法也由目前View處理,故該方法不會被再次調用,因為已經無須詢問它是否要攔截該事件。

return:

ture:對事件攔截,交給本層的onTouchEvent進行處理。

false:不攔截,分發到子View,由子View的dispatchTouchEvent進行處理。

super:預設不攔截。

3.事件處理(順序由下到上):onTouchEvent

在dispatchTouchEvent中調用,用來處理點選事件,傳回結果表示是否消耗目前事件,如果不消耗,則在同一事件序列中,目前View無法再接受到剩下的事件,并且事件将重新交給它的父元素處理,即父元素的onTouchEvent會被調用。

return:

true:表示onTouchEvent處理後消耗了目前事件。

false:不響應事件,不斷的傳遞給上層的onTouchEvent方法處理,直到某個View的onTouchEvent傳回true,則認為該事件被消費,如果到最頂層View還是傳回false,則該事件不消費,将交由Activity的onTouchEvent處理。

super:預設消耗目前事件,與傳回true一緻。

三.事件分發的基本認識

1.事件分發的對象

事件分發的對象是點選事件(Touch事件),而當使用者觸摸螢幕時,将産生點選事件。事件類型分為四種。

<1> MotionEvent.ACTION_DOWN:手指按下。

<2> MotionEvent.ACTION_MOVE:手指移動。

<3> MotionEvent.ACTION_UP:手指擡起。

<4> MotionEvent.ACTION_CANCEL:取消 ,事件結束,一般不執行,非人為。

一次事件序列:指從手指剛接觸螢幕,到手指離開螢幕的那一刻結束,在這一過程産生的一系列事件,這個序列一般以down事件開始,中間含有多個move事件,最終以up事件結束。

2.事件分發的順序

事件傳遞的順序:Activity->Window->DecorView->ViewGroup->View。一個點選事件發生後,總是先傳遞給目前的Activity,然後通過Window傳給DecorView再傳給ViewGroup,最終傳到View。

Window是抽象類,其唯一實作類為PhoneWindow,PhoneWindow将事件直接傳遞給DecorView,而DecorView繼承FrameLayout,FrameLayout又是ViewGroup的子類。

是以也可以認為事件傳遞的順序是:Activity->ViewGroup->View。

四.事件分發順序說明

上述說到了,事件分發的順序是:Activity—>Window—>DecorView—>ViewGroup—>View。那麼怎麼證明呢?

首先看一下Activity的dispatchTouchEvent方法。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    return super.dispatchTouchEvent(ev);
}
           

也就是說,Activity的dispatchTouchEvent方法預設傳回super.dispatchTouchEvent(ev);。點選進入檢視Activity的dispatchTouchEvent方法的源碼

Activity類dispatchTouchEvent方法源碼

/**
 * Called to process touch screen events.  You can override this to
 * intercept all touch screen events before they are dispatched to the
 * window.  Be sure to call this implementation for touch screen events
 * that should be handled normally.
 *
 * @param ev The touch screen event.
 *
 * @return boolean Return true if this event was consumed.
 */

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
           

此方法的注釋中也能看到大概(Activity早于Window)

You can override this to intercept all touch screen events before they are dispatched to the window.
           

繼續

if (ev.getAction() == MotionEvent.ACTION_DOWN) {
   onUserInteraction();
}
           

也就是,執行按下操作時執行onUserInteraction();方法。

onUserInteraction()方法源碼

protected void onUserLeaveHint() {

     
}
           

空方法,有點類似重寫Activity的public void onWindowFocusChanged(boolean hasFocus)方法。

繼續,往下執行

if (getWindow().superDispatchTouchEvent(ev)) {
    return true;
}
           

可以看出,是執行了getWindow()方法擷取的對象的superDispatchTouchEvent(ev)方法。那麼getWindow()方法擷取的對象是什麼呢?

public Window getWindow() {
    return mWindow;
}
           

也就是說,擷取的是Window對象。也就是說執行的是Window對象的superDispatchTouchEvent(ev)方法。

Window類的部分源碼

public abstract class Window {

   ...


   public abstract boolean superDispatchTouchEvent(MotionEvent event);

   ...

}
           

可以看出,Window類是個抽象類,superDispatchTouchEvent(ev)方法也是一個抽象方法。那麼肯定是Window的繼承類調用了superDispatchTouchEvent(ev)方法。那麼繼承類是誰呢?

小結1

到這裡,事件分發就從Activity的dispatchTouchEvent方法傳到了Window的superDispatchTouchEvent(ev)方法。

繼續

PhoneWindow類是Window抽象類的繼承類之一。那麼這個類怎麼和Activity關聯的呢?

Activity類的部分源碼

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


   ...

   final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);


  }


  ...


}
           

也就是說在Activity源碼中的attach方法中使用PhoneWindow類建立了Window類的對象。

PhoneWindow類部分源碼

@hide
public class PhoneWindow extends Window implements MenuBuilder.Callback {


   ...


   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }   

   ...

}
           

也就是說,在Activity的dispatchTouchEvent方法中調用 getWindow().superDispatchTouchEvent(ev) 其實是調用的PhoneWindow類的superDispatchTouchEvent方法。而通過源碼可知,PhoneWindow類的superDispatchTouchEvent方法中隻有一行代碼 

return mDecor.superDispatchTouchEvent(event);
           

即,調用mDecor對象的superDispatchTouchEvent(event);方法。那麼mDecor是什麼對象呢?

private DecorView mDecor;
           

mDecor變量就是DecorView的對象。也就是說現在事件分發又從PhoneWindow類的superDispatchTouchEvent方法傳到了DecorView類的superDispatchTouchEvent(event)方法。那麼這個DecorView是在哪裡初始化的呢?

public PhoneWindow(Context context, Window preservedWindow,ActivityConfigCallback activityConfigCallback) {
        this(context);
        mUseDecorContext = true;
        if (preservedWindow != null) {
            mDecor = (DecorView) preservedWindow.getDecorView();
            
            ...
        }


...


}
           

也就是說,PhoneWindow類的構造方法中初始化了DecorView對象。

小結2

<1> Window類是個抽象類,它有很多的繼承類。其中一個是PhoneWindow類。

<2> PhoneWindow類是在Activity源碼中的attach方法中初始化的。且該類是@hide類型的。也就是說PhoneWindow類系統是不允許我們new的。

mWindow = new PhoneWindow(this, window, activityConfigCallback);
           

<3> PhoneWindow類的構造方法中初始化了DecorView對象。

<4> 那麼到這裡 事件分發就從Window的superDispatchTouchEvent(ev)方法傳到了DecorView的superDispatchTouchEvent(event)方法。

繼續

DecorView類部分源碼

@hide
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {


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



}
           

可以看出,DecorView類中superDispatchTouchEvent方法也隻有一行代碼

return super.dispatchTouchEvent(event);
           

那麼 super.dispatchTouchEvent(event);到底調用的是哪個類的方法呢?點進去看一下

事件分發機制詳解(上)

也就是說,DecorView類中superDispatchTouchEvent方法調用的是ViewGroup的dispatchTouchEvent方法。

小結3

<1> DecorView類是@hide類的,也就是不允許我們new。

<2> DecorView類在PhoneWindow類的構造方法中初始化。

<3> 那麼到這裡 事件分發就從DecorView的superDispatchTouchEvent(event)方法傳到了ViewGroup的dispatchTouchEvent方法。

繼續

ViewGroup類的dispatchTouchEvent方法部分源碼

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

   ...



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

       if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
  
       ...     

       }
    }


        





   ...
   
}
           

也就是說,ViewGroup類的dispatchTouchEvent方法中,會調用View的onFilterTouchEventForSecurity(ev)方法。

View類的onFilterTouchEventForSecurity(ev)方法源碼

public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    //noinspection RedundantIfStatement
    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
            && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
        // Window is obscured, drop this touch.
        return false;
    }
    return true;
}
           

然後也會執行到ViewGroup本類的

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) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    // Calculate the number of pointers to deliver.
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

    // If for some reason we ended up in an inconsistent state where it looks like we
    // might produce a motion event with no pointers in it, then drop the event.
    if (newPointerIdBits == 0) {
        return false;
    }

    // If the number of pointers is the same and we don't need to perform any fancy
    // irreversible transformations, then we can reuse the motion event for this
    // dispatch as long as we are careful to revert any changes we make.
    // Otherwise we need to make a copy.
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);

                handled = child.dispatchTouchEvent(event);

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

ViewGroup類的dispatchTransformedTouchEvent方法,顯然調用了好多個方法比如

super.dispatchTouchEvent(event);


child.dispatchTouchEvent(event);
           

那麼這幾個方法,最終調用到了那個類的那個方法呢?

繼續

事件分發機制詳解(上)

也就是說,到了View的dispatchTouchEvent方法。

小結4

<1> ViewGroup類的dispatchTouchEvent方法中會調用View類的好多方法。

<2> 那麼到這裡 事件分發就從DecorView的superDispatchTouchEvent(event)方法傳到了ViewGroup的dispatchTouchEvent方法。然後傳到了View的dispatchTouchEvent方法。

總結

<1> 由源碼可知,我們常常用到的Window類是個抽象類,PhoneWindow是它的一個實作類。PhoneWindow類在Activity類的attach方法中初始化。

<2> PhoneWindow類在構造方法中初始化了DecorView類。

<3> PhoneWindow類和DecorView類都是@hide類型的。是以都不允許我們new。而是由系統初始化。

<4> 事件分發的順序是 Activity類的dispatchTouchEvent方法 到 PhoneWindow類的superDispatchTouchEvent方法 到 DecorView類的superDispatchTouchEvent方法 到 ViewGroup類的dispatchTouchEvent方法 到 View類的dispatchTouchEvent方法。

繼續閱讀