事件分发机制详解(上)

事件分发机制是 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

到这里,事件分发就从ActivitydispatchTouchEvent方法传到了WindowsuperDispatchTouchEvent(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> 那么到这里 事件分发就从WindowsuperDispatchTouchEvent(ev)方法传到了DecorViewsuperDispatchTouchEvent(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> 那么到这里 事件分发就从DecorViewsuperDispatchTouchEvent(event)方法传到了ViewGroupdispatchTouchEvent方法

 

 

继续

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> 那么到这里 事件分发就从DecorViewsuperDispatchTouchEvent(event)方法传到了ViewGroupdispatchTouchEvent方法。然后传到了ViewdispatchTouchEvent方法

 

 

总结

<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方法

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值