图1
首先认识一下Android界面的架构图,每个Activity中包含一个Window对象,这个Window对象通常由PhoneWindow来实现,然后将DecorView作为根view,DecorView是继承自FrameLayout即是一个ViewGroup,DecorView中有两个view一个是titleView,一个是ContentView(一个ID为content的FrameLayout)。
onTouchEvent()是View和Activity中的方法,默认返回false,如果返回true将对事件进行拦截,表示在当前view层级处理完整(down、move、up)点击事件,将不传递给上一层级的ViewGroup。
dispatchTouchEvent()既在view中也在ViewGroup中,还在activity中,返回值由OnTouchEvent决定。
onInterceptTouchEvent()是viewGroup中的方法,默认false。为true将事件进行截断,不再分发(即不再传事件给下一个view)。
使用一个例子说明:
图3
新建一个layout.xml,根布局为FrameLayout,其下有一个LinearLayout布局,两者皆属于ViewGroup,在下面有一个Button按钮是View控件。将图1和图3结合来看,ui布局时层级为Activity -->PhoneWindow --> DecorView(content中)–>FrameLayout–> LinearLayout–>Button。而View事件的分发刚好是从activity开始的,和ui开始布局的顺序一样。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
DM1:Activity.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event){
return mDecor.superDispatchTouchEvent(event);
}
DM2:PhoneWindow.java
public boolean superDispatchTouchEvent(MotionEvent event){
return super.dispatchTouchEvent(event);
}
DM3 DecorView.java
通过以上三段代码来看,DM1中通过判断PhoneWindow中的dispatchTouchEvent来决定事件是否被消费了,没有消费(false)就交给Activity中的OnTouchEvent来处理。DM2中将事件分发给了DecorView。DM3中,DecorView继承自FrameLayout,所以事件就分发到了跟ViewGroup,也就是到了View中。
此时回到例子中,我们可以通过在LinearLayout和FrameLayout中重写方法OnTouchEvent()、OnInterceptTouchEvent()和dispatchTouchEvent(),在Button中重写OnTouchEvent()和dispatchTouchEvent(),并且添加log打印日志。
通过log日志来看,正常执行顺序时,正常执行就是从Activity到view在回到Activity中执行OnTouchEvent(),因为事件没有被消费。使用OnInterceptTouchEvent产生截断后,事件不再向下分发。使用OnTouchEvent产生截断时,表明事件被消费,所以就不再向上传递。可以看出事件的操作是由OnInterceptTouchEvent和OnTouchEvent来控制的。可以得到一个自定义view的技巧,OnTouchEvent消费以后,事件没有了,后续的OnTouchEvent不再被调用,所以有的操作可以放在DispatchTouchEvent中来避免这种情况的发生。可以得到以下图示:
图4
图4表明了事件在View中的传递顺序,OnInterceptTouchEvent和OnTouchEvent发生的位置。两者是根view(DecorView)的起始。
图5
图5表示了OnInterceptTouchEvent在LinearLayout返回true时,事件的传递顺序。
图6
图7
通过以上来看事件的分发就不叫明了了,至于为什么这样,就得查看源码了,一下是部分源码,具体没有仔细深究,但通过反推,可以大致明了:
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
ViewGroup中dispatchTouchEvent
Intercepted表示是否截断事件,通过此可以看出通常情况下intercepted就是由OnInterceptTouchEvent(默认false)决定的。
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
ViewGroup中dispatchTouchEvent
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
…………省略
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;
}
}
ViewGroup中dispatchTouchEvent
如果intercepted为false,会通过事件分发给下一个view。如果intercepted设为true,则不走这一步,则newTouchTarget为空,alreadyDispatchedToNewTouchTarget=false。
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;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
mFirstTouchTarget不为空,存放接受了触摸事件的view的链表。Target != newTouchTarget,resetCancelNextUPFlag(target.child)方法(重新设置取消的下一个标记。如果之前设置该标志,则返回true。),cancelChild = intercepted = true。
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
ViewGroup中DispatchTransformedTouchEvent
此方法主要决定是将view分发给子view,或者调用父类view中的dispatchTransformedTouchEvent()。事件到view中以后就是主要调用OnTouchEvent了。OnTouchListener>onTouchEvent>onLongClickListener>onClickListener 优先级,后者不触发。