View的点击事件分发机制

来源:互联网 发布:excel数据提取工具 编辑:程序博客网 时间:2024/06/11 04:10

点击事件的分发,其实就是对MotionEvent事件的分发。
当事件产生后,系统会把这个事件传递到某个具体的View,这个传递的过程是由三个很重要的方法共同完成。

dispatchTouchEvent:    进行事件的分发,如果事件传递到了该View,那么此方法一定会被调用。    返回的结果受到当前View的onTouchEvent和下级View的onInterceptTouchEvent的影响。onInterceptTouchEvent:    处理是否拦截某个事件,如果当前的View拦截了,那么同一个事件序列中,此方法不会被调用。返回结果表示是否拦截该事件。    同一个事件序列是指手指从按下到手指离开屏幕,在这个过程所产生的一系列事件。    DOWN事件开始,很多MOVE事件,UP事件结束。   onTouchEvent:    处理点击事件,返回的结果表示是否消耗当前的事件。如果不消耗,则在同一事件序列中,当前的View无法再次接收到事件。   
结合上面的三个方法说一下大体的过程:当点击事件产生了事件最先传递给Activity,Activity再传递到Window,Window再传递到最顶层的View。而最顶层的View接收到事件就会进行事件的分发。顶层View,一般都是ViewGroup的dispatchTouchEvent调用,用于处理事件的分发,如果onInterceptTouchEvent返回true,表示拦截此事件,那么该ViewGroup的onTouchEvent就会调用。如果onInterceptTouchEvent返回false,那么表示它不拦截该事件。这个事件就会传递到它的子元素,而子元素同样也会重复该过程。直到事件被最终处理。

一,Activity对事件分发的过程
Activity—->Window—–>最顶层的View

这个过程究竟是怎么样的?点击事件产生了首先Activity的dispatchTouchEvent进行事件的分发:public boolean dispatchTouchEvent(MotionEvent ev) {    if (ev.getAction() == MotionEvent.ACTION_DOWN) {        onUserInteraction();    }    if (getWindow().superDispatchTouchEvent(ev)) {        return true;    }    return onTouchEvent(ev);}(getWindow返回的是Window对象)会把MotionEvent事件交给Window的superDispatchTouchEvent去处理。查看Window的源码得知,Window是个抽象类,Window的实现类是PhoneWindow。指定到PhoneWindow的superDispatchTouchEvent方法:public boolean superDispatchTouchEvent(MotionEvent event) {        return mDecor.superDispatchTouchEvent(event);}(mDecor是DecorView类型的变量)PhoneWindow又会把事件传递到DecorView中。而DecorView继承自FrameLayout。FrameLayout继承自View,DecorView它就是最顶层的View。至此事件由Activity---->Window----->最顶层的View的过程就完成了。现在写了一个十分简单的布局来理解上面所说的:<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent">    <Button        style="@style/Botton_Common"        android:id="@+id/btn_next"        android:text="下一个"/></LinearLayout>然后在Activity的setContenView中去加载这个layout。它的层级关系是下面这样的:

这里写图片描述

DecorView正是位于最顶层,而我们布局的View,是它的子View。可以通过(ViewGroup)getWindow().getDecorView().findViewById(R.id.content).getChild(0)来获得布局的View。

二,顶层View对点击事件的分发

最顶层View的事件分发过程:
最顶层View一般都是一个ViewGroup,上面提到的DecorView就是ViewGroup。

最顶层View的dispatchTouchEvent调用负责事件分发,
onInterceptTouchEvent返回true的情况:
如果设置了setOnTouchListener(此时的mOnTouchListener不为null),就会调用onTouch方法。onTouch返回false,onTouchEvent将会被调用,返回true,onTouchEvent不会调用。
如果onTouchEvent中设置了setOnClickListener(此时的mClickListener不为null),那么onClick会被调用。

onInterceptTouchEvent返回false的情况:(不拦截)
则事件会传递到它所在点击事件链上的子View。
这时事件已经从顶层View传递到了子View中。

传递到子View会继续循环上面的过程。

最顶层View的dispatchTouchEvent查看ViewGroup的dispatchTouchEvent方法public boolean dispatchTouchEvent(MotionEvent ev) {            final boolean intercepted;            if (actionMasked == MotionEvent.ACTION_DOWN                    || mFirstTouchTarget != null) {                final boolean disallowIntercept =                         (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;                if (!disallowIntercept) {                    intercepted = onInterceptTouchEvent(ev);                    ev.setAction(action);                 } else {                    intercepted = false;                }            } else {                intercepted = true;            }}先看这个条件(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)MotionEvent.ACTION_DOWN表示的是DOWN事件。当事件成功的由ViewGroup的子View处理后,mFirstTouchTarget会被赋值,不为null。ViewGroup不拦截事件,交由ViewGroup的子View去处理后,mFirstTouchTarget!=null成立。那么当一个MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP事件到来时,上面那个条件就不成立。那么onInterceptTouchEvent将不会再被调用。那如果只面对MotionEvent.ACTION_DOWN事件,那么ViewGroup的onInterceptTouchEvent会被调用,拦截。一种特殊情况的考虑:FLAG_DISALLOW_INTERCEPT标记位,它会在子View的requestDisallowInterceptTouchEvent方法中进行设置。它一旦设置,ViewGroup将不拦截事件,但除了ACTION_DOWN事件。为什么是除了ACTION_DOWN?因为ACTION_DOWN会重置FLAG_DISALLOW_INTERCEPT标记位。在子View中设置是没有用处的。if(actionMasked == MotionEvent.ACTION_DOWN) {    cancelAndClearTouchTargets(ev);    resetTouchState();//重置FLAG_DISALLOW_INTERCEPT}
一个结论:当ViewGroup决定拦截事件,那么后续的点击事件都会交给它处理,并且不再调用它的onInterceptTouchEvent。我的解释(可能有误):当ViewGroup拦截了DOWN事件后,调用onInterceptTouchEvent去拦截处理,那么后来的MOVE,UP事件过来时就不会通过(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)这个判断。那么表示后续的点击事件默认都会交给该ViewGroup去处理,并且不再调用onInterceptTouchEvent。另一个结论:在子View中可以通过requestDisallowInterceptTouchEvent方法来设置FLAG_DISALLOW_INTERCEPT标记位。来干预父View的事件分发处理,但是DOWN事件除外。总结:①onInterceptTouchEvent并不是每个事件都会被调用。如果我们想提前处理所有的点击事件,那么在dispatchTouchEvent去处理。(后面这个总结不太理解)②可以利用子View来标记FLAG_DISALLOW_INTERCEPT来干扰父View的事件分发过程。------未完待续......----------
1 1
原创粉丝点击