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

Android MotionEvent事件响应机制

2014-06-10 15:20 483 查看
在android中,事件主要包括点击、长按、拖曳、滑动等操作,这些构成了Android的事件响应,总体来说,所有的事件都由如下三个部分作为基础构成:

按下(action_down),移动(action_move),抬起(action_up)。各种响应归根结底都是基于View以及ViewGroup的,这两者中响应的方法分别有:

View.java中:

publi boolean dispatchTouchEvent(MotionEvent event)

public boolean onTouchEvent(MotionEvent event)

ViewGroup.java中

public boolean dispatchTouchEvent(MotionEvent event)

public boolean onTouchEvent(MotionEvent event)

public boolean onInterceptTouchEvent(MotionEvent event)

在组件嵌套的情况下,对于事件的响应处理会从最顶层的组件不断向子组件传递,一直到最后的View组件。

可以看到ViewGroup中比View中多出了一个onInterceptTouchEvent方法,这是因为ViewGroup组件可以存在子组件,因此需要通过Intercept判断是否将该事件传递到子组件中。

函数的具体功能如下:

onTouchEvent是真正用来进行业务逻辑处理的地方,返回true表示已经将该事件消费,返回false表明事件继续传递。

onInterceptTouchEvent是用来进行判断是否需要对事件进行拦截从而阻止其继续往子组件传递的,返回false表示无需拦截,则递归的调用子组件的dispatchTouchEvent方法;返回true表示需要拦截,则直接调用本组件的onTouchEvent方法进行处理。

以上两个的功能相对好理解一些,最主要的是第三个,之前在网上看了很多,但是都没有讲的特别清楚的。大都说是用于事件分发的,返回true表示不继续分发,返回false表示继续分发。但是一直没讲明白这个跟采用onInterceptTouchEvent的区别在哪里。。

直接点说,Android对于touch事件的处理是通过递归来进行的,而这种递归就体现在dispatchTouchEvent上。以上所写的两个函数就是在dispatchTouchEvent中被调用并且执行从而实现其分发的业务逻辑的。

在dispatchTouchEvent中有可能会调用三个方法:

1、本组件的onInterceptTouchEvent

2、子组件的dispatchTouchEvent

3、本组件的onTouchEvent

ViewGroup中dispatchTouchEvent()具体的执行逻辑:

1、首先执行本组件的onInterceptTouchEvent。如果返回false,表明无需拦截,则调用第二个方法,即子组件的dispatchTouchEvent方法;如果返回true,无需向子组件传递,则直接调用本组件的onTouchEvent方法

2、第一步中如果需要向子组件传递事件。如果递归调用子组件的dispatchTouchEvent返回false,则调用本组件的onTouchEvent方法;如果返回true,则无需调用本组件的onTouchEvent方法

3、根据前两步的执行结果,将该dispatchTouchEvent的返回值返回给父组件的dispatchTouchEvent方法。

view中的dispatchTouchEvent会直接调用其自身的onTouchEvent。

一般没有必要重写dispatchTouchEvent方法,如果一定要重写,请注意调用super.dispatchTouchEvent()方法,否则递归调用到此处即停止。

在不考虑dispatchTouchEvent的情况下,简单的执行流程是这样的:

最顶层的组件首先响应事件,然后不断向子组件进行传递,调用子组件的onInterceptTouchEvent方法,一直到某个组件A的onInterceptTouchEvent方法返回true或者到达了view组件,然后调用该组件的onTouchEvent方法,之后不断向父组件进行返回,调用父组件的onTouchEvent直到某个父组件的onTouchEvent方法返回true。

其实就是个首先从父组件不断向下调用onInterceptTouchEvent,然后从子组件不断向上调用onTouchEvent的过程。

还需要注意的:

1、假如这个过程中某个组件截获并处理了ACTION_DOWN事件,则之后相应的ACTION_MOVE、ACTION_UP等其他事件将不再会被传递到他的子孙组件,而是传递到该组件后就执行返回的流程。

2、如果某个组件的onInterceptTouchEvent对ACTION_DOWN返回true,则之后的ACTION_MOVE,ACTION_DOWN等将不会再执行本onInterceptTouchEvent,而是直接传递给本组件的onTouchEvent,但是依然会经过其父组件的onInterceptTouchEvent。

过程中看到有篇文章里对dispatchTouchEvent的代码进行了注释,可以更清楚的说明问题,具体如下,引用地址为:http://blog.csdn.net/hdxiaoyu2/article/details/25563453

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}

// Check for interception.
final boolean intercepted;//拦截的标记变量
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {

//调用自身的 onInterceptTochEvent,判断是否需要拦截
intercepted = onInterceptTochEvent(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;
}

//假如没拦截
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {

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 View[] children = mChildren;

final boolean customOrder = isChildrenDrawingOrderEnabled();
//遍历所有的子View,并且调用他们的事件分发方法dispatchTouchEvent()

for (int i = childrenCount - 1; i >= 0; i--) {                      final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}

newTouchTarget = getTouchTarget(child);
//newTouchTarget表示事件传递的View目标,当不为空的时候,直接跳出循环
if (newTouchTarget != null) {

newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

resetCancelNextUpFlag(child);
//递归调用子View分发事件方法,
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//设置分发目标newTouchTarget为当前View
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//标记子View的分发结果,为True的话,下面的代码是不会调用当前View的onTouch方法的,也就是规则1生成的原因
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}

}
}

if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
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;

//如果刚才alreadyDispatchedToNewTouchTarget设为True的话,就不执行下面的dispatchTransformedTouchEvent
//alreadyDispatchedToNewTouchTarget是由子View的onTouch返回值决定的,

if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;

//执行自身的Touch事件,
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;
}
}
}


另外还看到两篇将的不错的,可以作为参考:
http://www.infoq.com/cn/articles/android-event-delivery-mechanism http://blog.csdn.net/hdxiaoyu2/article/details/25563453 http://yizhi401.blog.51cto.com/6500239/1364958
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐