ViewGroup,一组View的集合,是Android中所有布局的父类或间接父类,像LinearLayout、RelativeLayout等都是继承自ViewGroup的。ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。
ViewGroup的事件分发主要涉及三个函数:
1)dispatchTouchEvent():判断事件是否进行分发
2)onInterceptTouchEvent():拦截的意思,用于判断事件要不要通知它的孩子。
3)onTouchEvent():处理事件,没有重写View的onTouchEvent()。
这里需要知道Android事件的传递流程:
当你点击了某个控件,首先会去调用该控件所在布局的dispatchTouchEvent方法,然后在布局的dispatchTouchEvent方法中找到被点击的相应控件,再去调用该控件的dispatchTouchEvent方法。
接下来我们对ViewGroup的事件分发涉及的三个主角的源码进行分析。
首先让我们看看ViewGroup的dispatchTouchEvent()的源码,只看关键部分:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { // ......... <strong>// 1、第一步 判断是否要分发改触摸事件</strong> // onFilterTouchEventForSecurity()表示是否要分发该触摸事件。 // 返回值为false表示该View不是位于顶部,并且有设置属性使该View不在顶部时不响应触摸事件,则不分发该触摸事件,进不了if代码块,dispatchTouchEvent返回false。 // 否则,往下执行,即执行if代码块里的语句。 boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // 1.2、第二步 检查是否需要清空目标和状态 // 如果是ACTION_DOWN(按下事件),则清空之前的触摸事件处理目标和状态。 // 从源码的注释可以看出在开始新的触摸手势前清楚之前的状态 // 主要是包括: // (01)清空mFirstTouchTarget链表,并设置mFirstTouchTarget为null。mFirstTouchTarget是"接受触摸事件的View"所组成的单链表 // (02) 清空mGroupFlags的FLAG_DISALLOW_INTERCEPT标记 if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } // 1.3、第三步 检查当前ViewGroup是否要拦截触摸事件 // 1)如果是"按下事件(ACTION_DOWN)" 或者mFirstTouchTarget不为null;就执行if代码块里面的内容。 // 2)否则的话,设置intercepted为true,即拦截。 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { // 检查禁止拦截标记:FLAG_DISALLOW_INTERCEPT final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { // disallowIntercept默认恒为false // 所以默认情况下都会执行ViewGroup的onInterceptTouchEvent()方法,因为onInterceptTouchEvent()默认返回false,所以执行下面这行代码会使intercepted // 的值为false,不会对事件进行拦截。 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was // changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial // down // so this view group continues to intercept touches. intercepted = true; } // ...... // 1.4、第四步 检查当前触摸事件是否被取消 // 说明: // 对于ACTION_DOWN而言,mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVENT位肯定是0;因此,canceled=false。 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; // 1.5、第五步 将触摸事件分发给"当前ViewGroup的子View和子ViewGroup" // 说明: // 如果触摸"没有被取消",同时也"没有被拦截"的话,则将触摸事件分发给它的子View和子ViewGroup。 // 如果当前ViewGroup的孩子有接受触摸事件的话,则将该孩子添加到mFirstTouchTarget链表中。(注意用的是如果不是否则) 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) { // 这是获取触摸事件的序号 以及 触摸事件的id信息。 // 1)从注释可以知道,对于ACTION_DOWN,actionIndex总是是0 // 2)而getPointerId()是获取的该触摸事件的id,并将该id信息保存到idBitsToAssign中。 // 这个触摸事件的id是为多指触摸而添加的;对于单指触摸,getActionIndex()返回的肯定是0; // 而对于多指触摸,第一个手指的id是0,第二个手指的id是1,第三个手指的id是2,...依次类推。 final int actionIndex = ev.getActionIndex(); // always 0 for // down final int idBitsToAssign = split ? 1 << ev .getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in // case they // have become out of sync. // 清空这个手指之前的TouchTarget链表。 removePointersFromTouchTargets(idBitsToAssign); // 获取该ViewGroup包含的View和ViewGroup的数目, final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; // 递归遍历ViewGroup的孩子,对触摸事件进行分发。 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder( childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // If there is a view that has accessibility focus // we want it // to get the event first and if not handled we will // perform a // normal dispatch. We may do a double iteration but // this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } // 查找child是否存在于mFirstTouchTarget的单链表中。 // // 是的话,返回对应的TouchTarget对象;否则,返回null。 newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its // bounds. // Give it the new pointer in addition to the // ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); // 调用dispatchTransformedTouchEvent()将触摸事件分发给child。 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 如果child能够接受该触摸事件,即child消费或者拦截了该触摸事件的话则调用addTouchTarget()将child添加到mFirstTouchTarget链表的表头. 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; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } //...... } if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added // target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // 1.6、 第六步 进一步的对触摸事件进行分发 // 说明: // 如果mFirstTouchTarget为null,意味着还没有任何View来接受该触摸事件;此时,将当前ViewGroup看作一个View;将会调用"当前的ViewGroup的父类View的dispatchTouchEvent()"对触摸事件进行分发处理。即会将触摸事件交给当前ViewGroup的onTouch(), onTouchEvent()进行处理。 // 否则mFirstTouchTarget不为null,意味着有ViewGroup的子View或子ViewGroup中,有可以接受触摸事件的。那么,就将触摸事件分发给这些可以接受触摸事件的子View或子ViewGroup。 if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. // 注意:这里的第3个参数是null handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if // we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } //....... } predecessor = target; target = next; } } // 1.7 第七步 进行还原状态 if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } //...... return handled; }
注意:第5步ViewGroup尝试将触摸事件分发给它的孩子。该步骤只有在ACTION_DOWN的时候才执行。因为在执行该步骤后,会对mFirstTouchTarget赋值,作为第6步处理Touch事件方式的依据。不管该步骤结果如何即是否有孩子接受Touch事件,都会在第6步对Touch事件处理,处理方式受mFirstTouchTarget影响。如果它的孩子接受了触摸事件,则会调用addTouchTarget()将该孩子添加到mFirstTouchTarget链表中。那么在ACTION_DOWN之后,传递ACTION_MOVE或ACTION_UP时,ViewGroup就在第6步中直接遍历mFirstTouchTarget链表,查找之前接受ACTION_DOWN的孩子,并将触摸事件分配给这些孩子。所以说第5步其实就是分发事件并判断ViewGroup的Touch事件是否有孩子接受。
也就是说,如果ViewGroup的某个孩子没有接受ACTION_DOWN事件;那么,ACTION_MOVE和ACTION_UP等事件也一定不会分发给这个孩子!因为孩子没有接受ACTION_DOWN事件,从常识上讲其后面的ACTION事件肯定也接受不到,而从源码上分析就是孩子在执行dispatchTransformedTouchEvent()方法时返回false,即dispatchTouchEvent()对ACTION_DOWN分发时返回false,所以ACTION事件不会传递下去。
通过以上源码分析能得出以下结论:
1)通过第三步知道,ViewGroup的dispatchTouchEvent()方法的disallowIntercept默认是为false,所以一定会执行ViewGroup的onInterceptTouchEvent(),而该方法默认返回false,所以intercepted就为false,表示不拦截事件,就会执行后面的事件分发程序。
根据该结论扩展:
扩展1:如果重写了onInterceptTouchEvent方法返回值为true,就会对事件拦截,则一定会执行到第六步的if (mFirstTouchTarget == null) 里的代码块,将ViewGroup当作View,执行View的dispatchTouchEvent方法,并将事件交给ViewGroup的onTouch()和onTouchEvent()进行处理。而且ACTION_DOWN后面的一系列ACTION都不会执行(因为View默认是不可点击),因为在执行ACTION_DOWN的时候已经将事件给拦截掉了。
扩展2:在当前ViewGroup包含的子View或者子ViewGroup中可以通过调用
requestDisallowInterceptTouchEvent(true)使disallowIntercep值为true,就不会执行ViewGroup的onInterceptTouchEvent()方法,不会对事件进行拦截,就可以在子View或者子ViewGroup中对事件进行处理。
2)当执行完第六步后,会有两种情况:
一是mFirstTouchTarget值为空,表示没有任何View来接受该触摸事件,此时将当前ViewGroup看作一个View;调用当前的ViewGroup的父类View的dispatchTouchEvent()对触摸事件进行分发处理,即会将触摸事件交给当前ViewGroup的onTouch(), onTouchEvent()进行处理(通过View的事件分发可知)。
另外一种情况则是mFirstTouchTarget值不为空,表示有ViewGroup的子View或子ViewGroup中可以接受触摸事件。那么就会将触摸事件分发给这些可以接受触摸事件的子View或子ViewGroup。
通过对ViewGroup的源码分析,对ViewGroup的事件分发用流程图表示如下:
通过以上对ViewGroup事件分发的源码分析,相信我们对Viewgroup的事件分发的原理有了深刻的了解,那么接下来我们就通过一个例子来对ViewGroup的事件分发进行演示,以加深对其原理的掌握。
如下,自定义一个布局MyViewGroup,继承自LinearLayout,并重写三个方法:
public class MyViewGroup extends LinearLayout { private static final String tag = "MyViewGroup"; public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { String actionName = EventUtils.getActionName(ev); Log.d(tag, "dispatchTouchEvent(start) " + actionName); boolean flag = super.dispatchTouchEvent(ev);//调用VIewGroup的dispatchTouchEvent() Log.d(tag, "dispatchTouchEvent(end) " + actionName+" flag="+flag); return flag; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { String actionName = EventUtils.getActionName(ev); Log.d(tag, "onInterceptTouchEvent(start) " + actionName); boolean flag = super.onInterceptTouchEvent(ev);//调用VIewGroup的onInterceptTouchEvent() Log.d(tag, "onInterceptTouchEvent(end) " + actionName+" flag="+flag); return flag; } @Override public boolean onTouchEvent(MotionEvent event) { String actionName = EventUtils.getActionName(event); Log.d(tag, "onTouchEvent(start) " + actionName); boolean flag = super.onTouchEvent(event);//调用VIewGroup的onTouchEvent() Log.d(tag, "onTouchEvent(end) " + actionName+" flag="+flag); return flag; } }
自定义MyView继承View:
public class MyView extends View { private static final String tag = MyView.class.getSimpleName(); public MyView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { String actionName = EventUtils.getActionName(ev); Log.d(tag, "dispatchTouchEvent(start) " + actionName); boolean flag = super.dispatchTouchEvent(ev);// 调用View的dispatchTouchEvent() Log.d(tag, "dispatchTouchEvent(end) " + actionName + " flag=" + flag); return flag; } @Override public boolean onTouchEvent(MotionEvent event) { String actionName = EventUtils.getActionName(event); Log.d(tag, "onTouchEvent(start) " + actionName); boolean flag = super.onTouchEvent(event);// 调用VIewGroup的onInterceptTouchEvent() Log.d(tag, "onTouchEvent(end) " + actionName + " flag=" + flag); return flag; } }
在测试的项目的Activity的布局将两个自定义的View进行布局,并重写Activity如下:
public class MainActivity extends Activity { protected static final String tag = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { String actionName = EventUtils.getActionName(ev); Log.d(tag, "dispatchTouchEvent(start) " + actionName); boolean flag = super.dispatchTouchEvent(ev);// 调用View的dispatchTouchEvent() Log.d(tag, "dispatchTouchEvent(end) " + actionName+" flag="+flag); return flag; } @Override public boolean onTouchEvent(MotionEvent event) { String actionName = EventUtils.getActionName(event); Log.d(tag, "onTouchEvent(start) " + actionName); boolean flag = super.onTouchEvent(event);// 调用View的dispatchTouchEvent() Log.d(tag, "onTouchEvent(end) " + actionName+" flag="+flag); return flag; } }
当点击MyView 所在区域,log日志如下:
在该示例中我们重写了MainActivity的dispatchTouchEvent()和onTouchEvent(),但都是调用的父类的方法。分析以上日志可知:
当Touch事件到来时,MainActivity通过dispatchTouchEvent将事件分发给MyViewGroup,MyViewGroup.dispatchTouchEvent()调用MyViewGroup.interceptTouchEvent()判断事件是否拦截,没有拦截将事件继续分发,然后事件分发给MyView,进入MyView.dispatchTouchEvent(),然后在调用MyView.onTouchEvent()返回false,然后将false值返回给MyView的dispatchTouchEvent(),使MyView.dispatchTouchEvent()也为false;
退出MyView.dispatchTouchEvent()后返回false给MyViewGroup.dispatchTouchEvent(),表示MyView没有接受该触摸事件。MyViewGroup则得知MyView没有接受该触摸事件之后,将自己当作一个View,调用View.dispatchTouchEvent();View.dispatchTouchEvent()接着就会进入MyViewGroup.onTouchEvent()。MyViewGroup.onTouchEvent()没有消费该触摸事件,因此返回false。 然后,View.dispatchTouchEvent()就会结束,并返回false。接着,MyViewGroup就会退出MyViewGroup.dispatchTouchEvent()并返回false。
MyActivity在得知MyViewGroup没有接受该触摸事件之后,就会调用进入MyActivity.onTouchEvent,并返回false。至此,MyActivity.dispatchTouchEvent()才结束。因此,会退出MyActivity.dispatchTouchEvent(),并返回false。
之后由于MyViewGroup和MyView都没有接受ACTION_DOWN事件,因此ACTION_MOVE和ACTION_UP事件就不会再分发给它们.