深入了解View的事件分发过程

1,683 阅读19分钟

View基础

View的集成关系树:


ViewGroup继承View,用来包含显示View。View的子类是一个矩形用来显示图案,也称为Widget。

View的事件分发

View的事件基础分析

View的onClick事件分析

例子说明:我们给一个View设置两个事件,一个是OnClickListener,另外一个是onTouchListener。

 findViewById(R.id.tv).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i("MainActivityLog", "onclick");
            }
        });
        findViewById(R.id.tv).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                Log.i("MainActivityLog", "onTouch,action="+motionEvent.getAction());
                // return false;
                return true;
            }
        });

在点击该View的时候

  1. 当onTouch返回true的时候,OnClick将不会有响应
  2. 当onTouch返回false的时候,二者都有响应。
    为什么会这样子的呢?黑人问好❓
    我们需要知道onClick事件的流程,以及他在哪里被调用了。

View的点击事件响应流程:

  1. 在setOnClickListener的时候做的事情是设置可点击,以及把改Listener存储到ListenerInfo这个List中。对于设置OnTouchLdiistener的时候,同样的,主要的代码如下:
    // Click事件
    public void setOnClickListener(@Nullable OnClickListener l) {
         if (!isClickable()) {
             setClickable(true);
         }
         getListenerInfo().mOnClickListener = l;
     }
     // onTouch事件
    public void setOnTouchListener(OnTouchListener l) {
         getListenerInfo().mOnTouchListener = l;
     }
    这是设置click和Touch事件的时候,会把OnClickListener和OnTouchListener赋值进去。
  2. 寻找自己的dispatchTouchEvent方法,自己寻找不到,寻找父类的。(事件都是先去执行该方法,后面说到)
    在TextView中没有dispatchTouchEvent方法,所有去寻找父类的,对于TextView该方法在View类中,主要的代码是:
    public boolean dispatchTouchEvent(MotionEvent event) { 
             // 省略代码...
          ListenerInfo li = mListenerInfo;  //第1行
             if (li != null && li.mOnTouchListener != null  //第2行
                     && (mViewFlags & ENABLED_MASK) == ENABLED  //第3行
                     && li.mOnTouchListener.onTouch(this, event)) {  //第4行
                 result = true;  //第5行
             }  //第6行
             if (!result && onTouchEvent(event)) {  //第7行
                 result = true;  //第8行
             } //第9行
         // ... 省略代码
            return result;  //第10行
    }
    使用的是API23的源码,其中reslut就是dispatchTouchEvent()返回的值。当我们设置了OnTouchListener的时候,OnTouchListener的 onTouch(View view, MotionEvent motionEvent) 返回的是true的时,就不会去执行View的 onTouchEvent(MotionEvent event)事件了。也就是第二行的条件使得result为true了,那么第七行的onTouchEvent(event)方法就不会被执行了,而我们的OnClickListener就是在onTouchEvent(MotionEvent event)中执行的。
    在View的onTouch方法中,在action为ACTION_UP的时候,有这样子的代码:
                             if (!post(mPerformClick)) {
                                     performClick();
                                 }
    其中performClick()是:
     public boolean performClick() {
         final boolean result;
         final ListenerInfo li = mListenerInfo;
         if (li != null && li.mOnClickListener != null) {
             playSoundEffect(SoundEffectConstants.CLICK);
               li.mOnClickListener.onClick(this);  //执行onClick事件
             result = true;
         } else {
             result = false;
         }
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
         return result;
     }
    所以,假如是在onTouchEvent(event)中的ACTION_UP返回了true,onClick事件就不会被调用了。
    流程图是:
    View的onTouch(view,event) 事件说明:
    首先是需要知道有两个onTouchEvent(event)的,一个是View的,一个是Activity的,优先级是Activity的比较高一点,对于View而言,他还可以设置onTouchListener,里面有一个onTouch(view,event),他的优先级比View的onTouchListener高。后面会说到。

在View的onTouchEvent中,假如他被调用了(view的onTouch(view,event)没有返回true),流程有一个判断是:

  public boolean onTouchEvent(MotionEvent event) {
// 省略代码...
// 条件
 if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                   // 省略代码...
                case MotionEvent.ACTION_DOWN:
                    // 省略代码...
                    break;
                case MotionEvent.ACTION_CANCEL:
                 // 省略代码...
                    break;
                case MotionEvent.ACTION_MOVE:
                   // 省略代码...  
            }
            return true;
        }}
// 省略代码...

当这个条件是true的时候,onTouchEvent会返回true,无论是哪一个ACTION(DOWN,UP,MOVE)都会返回true,使得方法onTouchEvent返回true。
在View设置了onClickListener事件的时候,假如该View是不可以点击的,就会激活它(看上面的setOnClickListener()方法),调用setFlag方法,使得上面这个条件为true,View的onTouchEvent可以返回true。disPatchTouchEvent只有在返回了true的时候,才会继续分发事件(后面会在提到)。所以结论是:

  1. 在View没有设置onTouchListener的时候,会执行onTouchEvent(event)方法,会执行那几次,需要看返回值。(DOWN,MOVE,UP)的时候的返回值,在返回了false之后不会再执行之后的事件,只有DOWN是一定会执行的。DOWN返回了false之后就不在执行,对于其他的类型。当直接返回super.onTouchEvent(event)作为返回值的时候,需要看继承的View,比如TextView的就会在DOWN之后就返回了false,那么默认就是只会执行DOWN。对于Button而已却是返回了true,事件可以传递下去,所以MOVE和UP都可以执行。
  2. 在设置了onTouchEvent的时候,onTouch(view,event)返回了false就会执行onTouchEvent(event),假如这时候onTouchEvent(event)返回了true,那么事件就可以继续分发。假如返回了false,那么事件就会终止。假如返回了onTouch(view,event)true就不会执行。(看上面的dispatchEvent(event)方法)。
    流程图是:

    View事件的深入了解

    View的事件涉及到几个方法,dispatchTouchEvent,onInterceptTouchEvent(只有ViewGroup以及他的子类才会有该方法)以及onTouchEvent,他们都会返回一个boolean值。下面是一些拥有改方法的一些比较:
方法 描述 Activity ViewGroup View
dispatchTouchEvent 事件分发
onInterceptTouchEvent 事件拦截
onTouchEvent 事件消费

事件的传递大概顺序是:Activity-ViewGropu-View
其实完整的事件传递是:

WMS -> ViewRootImp -> PhoneWindow$decorView -> Activity -> PhoneWindow->PhoneWindow$decorView -> ViewGroup ->  -> View(ViewGroup)
为什么从Activity开始解析

ActivityThread在handlerResume调用Activity的performResume(),之后再去通过Instrumentation调用Activity的onResume()方法,之后再调用Activity的makeVisible()方法,显示DecorView。
makeVisible()代码如下:

  void makeVisible() {
        if (!mWindowAdded) { //第一行
            ViewManager wm = getWindowManager();//第二行
            wm.addView(mDecor, getWindow().getAttributes());//第三行
            mWindowAdded = true;//第四行
        }
        mDecor.setVisibility(View.VISIBLE);//第五行
    }

其中,在WindowManager的addView()中,通过WindowManager的实现类,WindowManagerImpl,调用了WindowManagerGlobal的addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) 方法,代码如下:

 @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

mGlobal的addView方法会调用实例化一个ViewRootImpl,并且调用他的setView方法,重要代码如下

  public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
      // 省略代码...
        ViewRootImpl root; 
      // 省略代码...
            root = new ViewRootImpl(view.getContext(), display);//创建
        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);//调用
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

该setView方法通过跨进程调用WMS(WindowManagerService),然后把WMS,Activity,DecorView,Window绑定起来。通过WMS接受硬件的输入事件,传递给ViewRootImpl,然后ViewRootImpl中通过调用不同的事件舞台(InputStage)处理相关事件,对于点击事件是ViewPostImeInputStage,在他的processPointerEvent()方法中调用如下:

   private int processPointerEvent(QueuedInputEvent q) {
            //..省略代码
            boolean handled = mView.dispatchPointerEvent(event);
               //..省略代码
            return handled ? FINISH_HANDLED : FORWARD;
        }

其中,这一个View当初的setView方法赋值进来的,也就是DecorView他没有实现View的processPointerEvent(q),View中的实现如下:

    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

假如是点击触摸事件,则会调用dispatchTouchEvent(event),也就是DecorView的dispatchTouchEvent(event)方法,注意的是他并没有去super在ViewGroup的dispatchTouchEvent(),在DecorView中实现是:

@Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            final Callback cb = getCallback();
            return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                    : super.dispatchTouchEvent(ev);
        }

这里的cb对象就是我们的Activity了。
怎么说吗cb就是Activity呢?首先在Activity中,实现了Window.Callback接口,然后在Activity中 attach()方法实例化Window的唯一子类PhoneWindow,并且把Activity的引用传递进去。代码如下:

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) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
    }

交给了Activity之后,在Activity的dispatchTouchEvent中会调用PhoneWindow的superDispatchTouchEvent()

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

然后再在PhoneWindow的superDispatchTouchEvent(ev)中调用DecorView的superDispatchTouchEvent,之后就会到DecorView的super.dispatchTouchEvent()(ViewGroup中)进行分发。
这样子的就知道是为什么从我们的Activity开始分发事件了。整个流程是:WMS收到硬件的touch事件,传递给ViewRootImpl,ViewRootImpl调用DecorView的dispatchPointerEvent(父类ViewGroup中),然后dispatchPointerEvent再调用DecorView的dispatchTouchEvent(),最后调用了Activity的dispatchTouchEvent(),然后调用PhoneWindow,然后再去调用DecorView的super.dispatchTouchEvent(event)进行分发。
可以看一下这边文章Android中MotionEvent的来源和ViewRootImpl

事件分发逻辑流程

从上面的分析,我们可以可以接触到的其实是Activity的,ViewGroup的,View的事件分发,像PhoneWindow,DecorView,ViewRootImpl的事件分发一般不用我们处理(但是知道整个流程对于我们的理解有好处)。所以我们就分析一下Activity,ViewGroup,View这三者的事件。
例子代码:
Activity的

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("MainLog", "Activity dispatchTouchEvent:" + event.getAction());
        return super.dispatchTouchEvent(event);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("MainLog", "Activity onTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }
}

ViewGroup的:

public class CustomViewGroup extends LinearLayout {
    public CustomViewGroup(Context context) {
        super(context);
    }
    public CustomViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("MainLog", "CustomViewGroup dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("MainLog", "CustomViewGroup onInterceptTouchEvent"+ev.getAction() );
        return super.onInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("MainLog", "CustomViewGroup onTouchEvent"+event.getAction());
        return super.onTouchEvent(event);
    }
}

View的:

public class CustomView extends TextView {
    public CustomView(Context context) {
        super(context);
    }
    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("MainLog", "CustomView onTouchEvent"+event.getAction());
        return super.onTouchEvent(event);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("MainLog", "CustomView dispatchTouchEvent"+event.getAction());
        return super.dispatchTouchEvent(event);
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<com.example.customview.CustomViewGroup
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <com.example.customview.CustomView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>
</com.example.customview.CustomViewGroup>

运行之后直接点击Hello World结果是:

Activity dispatchTouchEvent:0
CustomViewGroup dispatchTouchEvent0
CustomViewGroup onInterceptTouchEvent0
CustomView dispatchTouchEvent0
CustomView onTouchEvent0
CustomViewGroup onTouchEvent0
Activity onTouchEvent0
Activity dispatchTouchEvent:1
Activity onTouchEvent1

我们所有的方法事件都是处理为super,暂时不返回true或者是false,可以看到按照这个事件流程,在ACTION_DOWN的时候,Activity先执行dispatchTouchEvent开始分发事件,然后按照上面Activity的dispatchTouchEvent()方法逻辑,他会去调用DecorView的dispatchTouchEvent()方法进行分发,然后一路下去,给CustomViewGroup, CustomViewGroup再去调用子View的dispatchTouchEvent,然后是到子View的onTouchEvent()一路返回回来,但是在ACTION_UP的时候,我们的CustomViewGroup和CustomView就没有分发到UP事件,这是为什么呢?难道我们的ViewGroup无法接受到UP事件?后面我们再提这个,我们来分析一下这次log的流程,按照事件打印的顺序,DOWN事件是很明显符合我们上面为什么从Activity开始分发的逻辑的:

本文是使用 API23的源码进行分析和debug的。

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

看Activity的该方法源码我们知道

  1. 假如我们什么都不做,他将会执行Activity的onTouchEvent,或者是无卵返回了其他的东西,只要去super了父类,他都会执行自身的onTouchEvent方法。
  2. 假如重写了该方法,但是我们没有手动调用super.dispatchTouchEvent(event)或者是调用onTouchEvent(event),那么事件将终止传递,相当于消费了touch事件。
  3. 假如调用的getWindow().superDispatchTouchEvent(ev)返回了true,那么就不会去调用Activity的onTouchEvent(v)了。
    所以一般情况下我们没有必要重写该方法,因为一不小心可能我们的其他任何时间都无法接受到了。
    接下来来分析假如去super了该方法的时候发生什么事情,在没有重写或者是super了该方法的时候,我们从为什么从Activity开始分发事件分析知道,他将会去调用DecorView父类(ViewGroup)的dispatchTouchEvent事件。
ViewGroup的dispatchTouchEvent(event)

主要涉及代码:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        //默认处理事件
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
         //省略代码
           if (actionMasked == MotionEvent.ACTION_DOWN) {
                //以ACTION_DOWN为开始,清空之前的TouchTarget链表
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            final boolean intercepted;
            //判断是否是ACTION_DOWN事件或是者mFirstTouchTarget是否为空
            // mFirstTouchTarget是一个链表,他会把dispatchTouchEvent()事件返回true的子view添加进来。
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //是否拦截事件,看onInterceptTouchEvent的返回值,默认是false
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    //不是ACTION_DOWN,拦截,但是还需要看mFirstTouchTarget是否为null。
                    intercepted = false;
                }
            } else {
            //假如不是ACTION_DOWN事件,那么就拦截掉
                intercepted = true;
            }
            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            //是否处理了改taget手势,这个将会觉得调用了子View的方法之后时候还会执行自己的onTouchEvent方法
            boolean alreadyDispatchedToNewTouchTarget = false;
            //没有取消手势而且没有拦截
            //这时候才会去遍历整个ViewGroup的子View
            if (!canceled && !intercepted) {
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    // 省略代码
                    final int childrenCount = mChildrenCount;//有几个子View
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        final ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //遍历子View
                        for (int i = childrenCount - 1; i >= 0; i--) {
                              //省略代码,省略的代码是用来获取child的
                            //给子View分发事件,就会去调用dispatchTransformedTouchEvent方法
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                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;
                           }   
                            //调用addTouchTarget()方法,为mFirstTouchTarget添加新的节点,上一个节点作为next,然后指向当前节点。
                             newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            // 处理了事件,因为dispatchTransformedTouchEvent返回了true
                            alreadyDispatchedToNewTouchTarget = true;
                                //省略代码
                                break;
                            }
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
                    //省略代码
                }
            }
            // 假如没有子View的dispatchTouchEvent返回true,那么他就是null的
            if (mFirstTouchTarget == null) {
                // 调用dispatchTransformedTouchEvent方法,传入参数View是空,会去调用View的dispatchTouchEvent方法
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    //假如已经处理了事件而且target和newTouchTarget相同,就不会去调用dispatchTransformedTouchEvent()方法
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
          //省略代码
        return handled;
    }

代码比较多,大家可以看注释的部分,注意看到不是ACTION_DOWN的时候,而且默认子View一直是返回了false(后面分析),默认是拦截掉事件的,这也就解析了上面打印的Log,为什么在ViewGroup中没有看到ACTION_UP的事件,因为在PhoneWindow调用ViewGroup的时候,由于action是UP,主动消费拦截了事件,当前子View(CustomViewGroup)返回了false,没有添加到父ViewTouchTarget中,也就没有给子View传递(详细看后面的一点疑问)。除此之外,我们看到当onInterceptTouchEvent方法方法返回了false,也是会去分发事件的(此时的应该是ACTION_DOWN),他的代码是:

 public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

可以看到他是默认没有拦截事件的,默认返回了false。只有没有拦截事件,ViewGroup的dispatchTransformedTouchEvent()方法,他的主要代码是:

//只要有子view或者是View处理了事件的分发,就返回handled;
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
           View child, int desiredPointerIdBits) {
       final boolean handled;
       final int oldAction = event.getAction();
       //假如是cancel手势的时候,或者是cancel的时候
       if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
           event.setAction(MotionEvent.ACTION_CANCEL);
           if (child == null) {
              //调用View的dispatchTouchEvent
               handled = super.dispatchTouchEvent(event);
           } else {
          //调用子view的dispatchTouchEvent
               handled = child.dispatchTouchEvent(event);
           }
           event.setAction(oldAction);
           return handled;
       }
       final int oldPointerIdBits = event.getPointerIdBits();
       final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
       if (newPointerIdBits == 0) {
           return false;
       }
       final MotionEvent transformedEvent;
       if (newPointerIdBits == oldPointerIdBits) {
           if (child == null || child.hasIdentityMatrix()) {
               if (child == null) {
                   handled = super.dispatchTouchEvent(event);
               } else {
                   handled = child.dispatchTouchEvent(event);
               }
               return handled;
           }
           transformedEvent = MotionEvent.obtain(event);
       } else {
           transformedEvent = event.split(newPointerIdBits);
       }
       if (child == null) {
            //调用View的dispatchTouchEvent,onTouchEvent()方法就是在View的哪里被调用的。
           handled = super.dispatchTouchEvent(transformedEvent);
       } else {
           final float offsetX = mScrollX - child.mLeft;
           final float offsetY = mScrollY - child.mTop;
           transformedEvent.offsetLocation(offsetX, offsetY);
           //child不为空,给child的dispatchTouchEvent去处理。
           if (! child.hasIdentityMatrix()) {
               transformedEvent.transform(child.getInverseMatrix());
           }
           handled = child.dispatchTouchEvent(transformedEvent);
       }
       return handled;
   }

可以看到,该返回的返回值是看View中的dispatchTouchEvent或者是子view的dispatchTouchEvent的返回值。ViewGroup本身去super调用是在mFirstTouchTarget对象为空或者是传入上面的child对象为空的时候被调用,也就是ViewGroup的onTouchEvent是在子View的dispatchTouchEvent和onTouchEvent之后的。

分析总结:

  1. 在我们重写了该方法,而没有去super父类的dispatchTouchEvent(),事件终止传递。
  2. 在我们有super该调用方法的时候,他将会在最后调用父类容器的dispatchTouchEvent,同时之前会去进行事件的分发。有以下情况:
    1. 首先他肯定回去调用onInterceptTouchEvent()方法,假如onInterceptTouchEvent返回了true,则是onInterceptTouchEvent消费了该事件,终止事件传递,,默认是返回false不消费的,向下传递。
    2. ViewGroup的onInterceptTouchEvent没有拦截事件,则会去遍历子view,调用子view的dispatchTouchEvent()。
    3. 在mFirstTouchTarget为空或者是子View的dispatchTouch()返回了false的时候,回去调用父类的dispatchTouchEvent方法,其实是通过dispatchTransformedTouchEvent()去调用。
      接下来我们讨论View的dispatchTouchEvent();
ViewGroup的onInterceptTouchEvent(event)

该方法的源码比较简单,默认是返回false的,上面也有分析到,当放回false的时候,他是向下继续传递,当返回了true的时候,那么他就会把事件交给自己的onTouchEvent()去处理了。

View的dispatchTouchEvent(event)

他的大概源码是

public boolean dispatchTouchEvent(MotionEvent event) {
        //可接受焦点
        if (event.isTargetAccessibilityFocus()) {
            if (!isAccessibilityFocusedViewOrHost()) {
                return alse;
            }
            event.setTargetAccessibilityFocus(false);
        }
        boolean result = false;
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
        //省略代码
        if (onFilterTouchEventForSecurity(event)) {
            //是否有设置ouTouchListener,假如有,那么返回值假如是true,那么result就是true,没有否则是返回值为false,result为false
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //没有设置View的onTouchListener或者是返回了false,这时候才可以调用onTouchEvent方法
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }
        return result;
    }

注释写大概写明白了,注意的是View的是没有onInterceptTouchEvent的,因为他是最小的组件,不能包含其他组件,所以不能分发事件。
可以看到在我们没有手动给View设置onTouchListener或者是返回了false的时候,才会去执行onTouchEvent()方法,然后假如onTouchEvent()返回了false则是View的dispatchTouchEvent返回false,true则是true。假如是设置了onTouchListenr,在onTouch()里面返回了true的话,那么dispatchTouchEvent也会返回true。

View的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();
       //不可用的时候
       //条件1
       if ((viewFlags & ENABLED_MASK) == DISABLED) {
           if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
               setPressed(false);
           }
         // 0x00004000 CLICKABLE
         //0x00200000 LONG_CLICKABLE
         //0x00800000 CONTEXT_CLICKABLE
           return (((viewFlags & CLICKABLE) == CLICKABLE
                   || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                   || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
       }
       //mTouchDelegate对象是一个可以扩大点击的一个对象,详情可以看http://blog.csdn.net/a220315410/article/details/9141265这篇文章,我们这里不讨论,设置了的话就是交由他处理
       if (mTouchDelegate != null) {
           if (mTouchDelegate.onTouchEvent(event)) {
               return true;
           }
       }
        //条件2
       if (((viewFlags & CLICKABLE) == CLICKABLE ||
               (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
               (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
               //省略处理ev的代码
           return true;
       }
       return false;
   }

可以看到,当View是不可用的时候,那么就交由他的一些状态的与操作决定返回值,当他设置了mTouchDelegate的时候就交由mTouchDelegate对象去处理,否则,只要是View为CLICKABLE,LONG_CLICKABLE,CONTEXT_CLICKABLE,无论是如何处理action ,都将会返回true。
View的viewFlags默认值是0x18000000,条件2默认是为false的,除非我们设置一些操作,比如setClickListener之类的才会变成true。

ViewGroup的onTouchEvent(event)

在Viewgroup中,我们发现他并没有定义或者是override onTouchEvent()方法,只是通过用父类的super.dispatchTouchEvent去实现的。而这个调用时机就是上面分析ViewGroup的本身的dispatchTouchEvent讲到的,只要mFirstTouchTarge为空,或者是子View的dispatchTouchEvent()返回了false的时候去调用。

Activity的onTouchEvent(event)

源码是:

   public boolean onTouchEvent(MotionEvent event) {
        //Window类判断触发关闭Activity的action,只有在ACTION_DOWN的时候才有可能返回true
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }

Window的shouldCloseOnTouch源码:

  public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
                && isOutOfBounds(context, event) && peekDecorView() != null) {
            return true;
        }
        return false;
    }

mCloseOnTouchOutside是一个boolean值,由android:windowCloseOnTouchOutside决定,isOutOfBounds是是否在UI内,peekDecorView()是返回当前根View。我们有时候需要popupWindow点击外部消失就可以使用上面的那个属性了。

一些疑问

上面Log的打印过程已经从源码角度走了一遍了,我们总结一下关于整一个View的事件传递过程,以及解析一些问题?比如我们应该怎么使得我么自己定义的ViewGroup收到除了ACTION_DOWN事件。继承APi23的Activity,我们通过View的分析看到他的View排布是:

com.android.internal.policy.PhoneWindow$DecorView@ee2b93c
  -com.android.internal.widget.ActionBarOverlayLayout@b6156c5 
    -android.widget.FrameLayout@859a41 //内容区域
      -com.example.customview.CustomViewGroup@ec3a072
          -com.example.customview.CustomView@cc17940
    -com.android.internal.widget.ActionBarContainer@d28abe6 //顶部
      -android.widget.Toolbar@b409f0b
        -android.widget.TextView@87b41e8
          -android.widget.ActionMenuView@bfff001
        -com.android.internal.widget.ActionBarContextView@8dfaea6
  -android.view.View@240281a 虚拟键盘 
  -android.view.View@fa52f4b 状态栏

针对内容区域,FrameLayout中,事件分发从Activity开始,然后到PhoneWindow,然后回到ActionBarOverlayLayout,然后是到FrameLayout,这个过程我们假如没有重写Activity中的方法是无法干预的。从FrameLayout到CustomViewGroup的事件分发过程,由于CustomViewGroup是可接受焦点的,可以被FrameLayout分发到事件。所以,假如我们需要ViewGroup中接受到除了ACTION_DOWN之外的action,按照之前的分析,在FrameLayout指向dispatchTouchEvent()的时候,DOWN的时候回清空列表,当子View(CustomViewGroup)的dispatchTouchEvent()返回了true,则会把当前的子View添加到mFirstTouchTarget链表中去。所以当CustomViewGroup的dispatchTouchEvent返回了true,FrameLayout在执行UP的时候,mFirstTouchTarget就会变成了CustomViewGroup了,默认是不会拦截了,他将会再次去执行CustomViewGroup的dispatchTouchEvent(),传递到哪里的就是UP事件了或者是MOVE事件了。但是,在CustomViewGroup的dispatchTouchEvent返回了false的时候,他是不会被添加到FrameLayout的mFirstTouchTarget中的,所以他就不会响应到UP事件。

总结

我们使用一张图来总结View的事件出传递过程:
可以右键看大图

有几点结论:

  1. 只有有View对ACTION_DOWN事件感兴趣,即使返回了true,其他的ACTION事件才会传递下去给他,比如我们一开始的例子中,自定义的ViewGroup就是没有收到UP事件,因为他以及他的子View都是对他不感兴趣,没有返回true,或者说是mFirstTouchTarget一直是null的。导致事件自从DOWN之后是一直被拦截的,可以看上面的ViewGroup的dispatchTouchEvent()方法说明。
  2. 只要在dispatchTouchEvent的过程中对于各个的View或者是ViewGroup,没有拦截掉事件(onInterceptTouceEvent返回true)或者是对事件感兴趣(OnTouchEvent返回true),那么事件就会一直被传递下去。直到View Tree最底层的View中。
  3. 只要是最底层的View Tree也是一直不处理,不感兴趣(不考虑激活),对于事件,那么这个事件就会一直往回传,传递给父ViewGroup的onTouchEvent,直到最顶级的Activity的onTouchEvent事件中。
    4,当View对事件不感兴趣,只是单纯的super的时候,假如改View是激活的,是的在View的onTouchEvent()能够走进switch语句中的条件为true,比如设置了OnClickListener,那么就是表明他是对事件感兴趣的,只是隐式的而已,那么就可以使得ACTION_UP等事件可以向下传递。
  4. 只要是dispatchTouchEvent返回了true,就表明改方法自身消费了该事件
  5. 假如是拦截了事件,那么消费事件将会在改View/ViewGroup的onTouchEvent中进行

文章比较长,自己也写了比较久,难免会有地方错误,谢谢指正提出^_^。