您的位置:首页 > 移动开发 > Android开发

Android事件分发机制(下)

2016-05-05 15:35 543 查看

1、ViewGroup事件分发简介

ViewGroup,一组View的集合,是Android中所有布局的父类或间接父类,像LinearLayout、RelativeLayout等都是继承自ViewGroup的。ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。

ViewGroup的事件分发主要涉及三个函数:

1)dispatchTouchEvent():判断事件是否进行分发

2)onInterceptTouchEvent():拦截的意思,用于判断事件要不要通知它的孩子。

3)onTouchEvent():处理事件,没有重写View的onTouchEvent()。

这里需要知道Android事件的传递流程:

当你点击了某个控件,首先会去调用该控件所在布局的dispatchTouchEvent方法,然后在布局的dispatchTouchEvent方法中找到被点击的相应控件,再去调用该控件的dispatchTouchEvent方法。

接下来我们对ViewGroup的事件分发涉及的三个主角的源码进行分析。

2.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。

3、ViewGroup事件分发总结

通过对ViewGroup的源码分析,对ViewGroup的事件分发用流程图表示如下:



4、Case演示

通过以上对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事件就不会再分发给它们.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: