我们通过点击一个布局所触发的事件详细流程来分析Android的事件分发机制。
需要知道的:
1.Touch事件分发中包含ViewGroup和View:
ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件.
ViewGroup继承于View。
View包含dispatchTouchEvent、onTouchEvent两个相关事件。
所以只有ViewGroup(RelativeLayout,LinearLayout等属于其子类布局)拥有事件拦截的方法。
TextView,ImageView等组件继承View
2一个完整的Touch事件,是由一个Down、一个Up和若干个(可以为0)Move组成的。
Down方式通过dispatchTouchEvent分发,目的是为了找到真正要处理这个Touch请 求的View。
当某个View或者ViewGroup的onTouchEvent事件返回true时,便表示它是真正要处理这次请求的View.
之后的Aciton_UP和Action_MOVE将由它处理。
当所有子 View的onTouchEvent都返回false时,这次的Touch请求就由根ViewGroup。
首先假设有这样一个布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#3ab2ff"> <Button android:layout_width="150dp" android:layout_height="80dp" android:layout_centerInParent="true" android:text="按钮"/> </RelativeLayout>
在这个布局中根部局是一个RelativeLayout,其中包含一个Button控件。
现在假设在布局上进行了一次点击:
首先执行的是ViewGroup的dispatchTouchEvent()方法:
看下dispatchTouchEvent()相关部分源码:
@Override 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; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //进入down事件处理: if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget != null) { mMotionTarget = null; } // 当以下两种情况发生时进入if方法 // 1、当前不允许拦截,即disallowIntercept =true, // 2、当前允许拦截但是不拦截,即disallowIntercept =false,但是onInterceptTouchEvent(ev)返回false ; if (disallowIntercept || !onInterceptTouchEvent(ev)) { ev.setAction(MotionEvent.ACTION_DOWN); 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--) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame); //判断你的点击事件(x'yxyzuo'biaoxy坐标)是否在一个组件上,如果是 执行View的dispatchTouchEvent() if (frame.contains(scrolledXInt, scrolledYInt)) { // offset the event to the view's coordinate system final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; if (child.dispatchTouchEvent(ev)) { mMotionTarget = child; return true; } } } } } }
可以发现在进入dispatchTouchEvent()进行了如下顺序的判断是否可以拦截或者onInterceptTouchEvent()【当 onInterceptTouchEvent返回true为拦截本次事件默认为false】中是否进行拦截:如果进行了拦截则执行ViewGroup的onTouchEvent()方法,就是自己处理了,不再向ViewGroup包含的view中分发。如果没有进行拦截,则会遍历所有包含的View判断包含的View,判断Down点是否在View上:(1)如果没有点击在一个组件上,就是点击在了布局的一个空白的地方,最终还是执行 ViewGroup的 onTouchEvent()方法。(2)如果在一个组建上,则将X,Y传递,将事件分发给View的dispatchTouchEvent();当View接收分发的事件,执行View的dispatchTouchEvent()方法:public boolean dispatchTouchEvent(MotionEvent event) {if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& mOnTouchListener.onTouch( this, event)) {return true;}return onTouchEvent(event);}在这个方法中执行了这样的判断:(1)如果该view设置了 OnTouchListener 事件(clickable==true)并且onTouch()事件返回true的时候,证明事件已经被消费了,不再执行View的 onTouchEvent();(2)否则则执行View的onTouchEvent()方法:public boolean onTouchEvent(MotionEvent event) { ...if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {switch (event.getAction()) { case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: break; } return ture;}return false; }可以看到在这里,我们就可以对我们触摸事件ACTION_UP,ACTION_DOWN,ACTION_MOVE等操作进行处理。在OnTouchListener 事件中 ,当我们:(viewFlags & CLICKABLE) == CLICKABLE 支持点击(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) 支持长按返回true.总结一个点击事件的流程就是:1.ViewGroup的dispatchTouchEvent()如果点击的空白区域不是包含的View上或者dispatchTouchEvent()方法返回true进行拦截,则本身消费执行onTouchEvent()否则执行 2 .2.View接收到事件执行dispatchTouchEvent(),如果被View的OnTouchListener()消费掉,则事件执行完毕,不再执行View的onTouchEvent();否则执行 3 .3.View的onTouchEvent接受到事件进行处理,如果返回true,事件被消费,不再执行其他事件,表示ViewGroup是无法处理这个事件已经被消费掉了。如有问题,敬请指正!