事件分发
Android可以通过触摸屏幕来实现与用户的交互,Android屏幕上显示了各种各样的view,这些view有可能是竖直排列也有可能是叠在一起的,那么我们触摸屏幕的时候到底由哪个view来响应,这个就是我们的view的事件分发
View的分类
- View,View只具有处理事件的能力
- dispatchTouchEvent
- onTouchEvent
- ViewGroup,ViewGroup具有分发事件和处理事件的能力(由于ViewGroup继承自View)
- dispatchTouchEvent
- onInterceptTouchEvent
- onTouchEvent
- requestDisallowInterceptTouchEvent
事件的种类
对于单点触摸来说事件主要有四种
- Down,用户手指按下的时候触发
- Move,用户手指在屏幕上移动的时候触发
- Up,用户手指抬起的时候触发
- Cancel,事件被取消的时候触发(事件拦截的时候会触发Cancel,点击的时候按住屏幕滑动等)
事件处理的流程
事件的处理一般都是在View中的dispatchTouchEvent进行的,下面是其中比较重要的一段代码
public boolean dispatchTouchEvent(MotionEvent event) {
//...上面的方法
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
//.....下面的方法
}
return result;
}
- li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)
当我们设置了setOnTouchListener的时候对li进行了初始化,并且li.mOnTouchListener也不为null,那么必定会执行onTouch方法,如果onTouch方法返回true,那么result就为true。 - if (!result && onTouchEvent(event)) {
result = true;
}
当result返回true的时候则不会执行onTouchEvent方法。
当result返回false(意味着onTouch方法返回了false),那么就会去执行onTouchEvent,最终result也返回了true,dispatchTouchEvent也返回了true,也就是事件被消费了。
3.接下来就看onTouchEvent做了哪些操作
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//长按事件是由handler去处理的,如果不是长按事件那么就会执行下面的代码
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
break;
case MotionEvent.ACTION_DOWN:
//...down事件校验是都可以点击,对长按事件做了初始化,并且如果在滑动控件内部,对点击事件做了延时响应处理
break;
case MotionEvent.ACTION_CANCEL:
//...cancel的时候也将点击和长按事件移除了
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
//....move事件主要对点击和长按做了移除,比如当手指移出了view等
break;
}
return true;
}
return false;
}
在up事件中我们可以看到如果不是长按事件那么就是执行点击事件
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
//在这里执行了点击事件
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
事件分发
事件分发主要是由ViewGroup的dispatchTouchEvent来处理的
同样的ViewGroup的dispatchTouchEvent的事件也分为Down,Move,Up,Cancel
dispatchTouchEvent部分的主要逻辑
//是否拦截部分的逻辑
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
//拦截部分的逻辑
Boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
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 = buildTouchDispatchChildList();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
//自己处理或者分发的逻辑
// Dispatch to touch targets.
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;
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;
}
}
Down事件
- 当为Down事件的时候根据disallowIntercept以及onInterceptTouchEvent来确定是否拦截事件
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;
}
默认情况下 (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0为false
- 如果onInterceptTouchEvent返回true那么intercepted就为true,那么意味着父控件要进行拦截
- 否则不进行拦截
- 根据是否拦截来判断是处理还是分发
if (!canceled && !intercepted)
- 如果被拦截则不执行上面的这个if内部的逻辑
- 如果没有被拦截,则执行上面的这个if内部的逻辑
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
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 = buildTouchDispatchChildList();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
在上面的代码中我们可以看到对所有的子view进行了遍历,通过dispatchTransformedTouchEvent方法寻找有没有子view要处理这个事件。
private Boolean dispatchTransformedTouchEvent(MotionEvent event, Boolean cancel,
View child, int desiredPointerIdBits) {
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;
}
// 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;
}
调用dispatchTransformedTouchEvent方法传递的参数是(ev, false, child, idBitsToAssign),cancel为false,并且我们是Down事件,child不为null。所以他会执行handled = child.dispatchTouchEvent(transformedEvent);在这里就是将Down事件分发给子view去处理了。
- 如果child.dispatchTouchEvent返回了false,代表他不处理这个事件,那么就接着寻找下一个子View,看看是否处理这个事件
- 如果child.dispatchTouchEvent返回了true,代表他要处理这个事件
//这里将child加入到了TouchTarget的链表中,TouchTarget记录了当前有多少个手指触摸,
//我们在这里分析的是单点触摸,那么这个链表应该是只有一个数据
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
- 接着就执行if (mFirstTouchTarget == null)判断,这里分为三种情况
- 如果上面对Down事件进行拦截,那么mFirstTouchTarget肯定为null,执行handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
我们发现在这里child为null,canceled也为false,那么就会调用
handled = super.dispatchTouchEvent(transformedEvent);去执行View的dispatchTouchEvent去处理这个事件。 - 如果没有对Down事件进行拦截,但是没有子View去处理这个事件,那么他执行的过程和拦截是一样的
- 如果没有对Down事件进行拦截,并且有子View进行处理事件,这个时候mFirstTouchTarget就不为null执行else代码
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;
}
在分发的时候找到了子View的时候将标记设置为true,并且将child加入到了TouchTarget中,那么他就会执行handled = true;表示事件已经被处理。
Move事件
- move事件同样也会判断是否进行拦截,因为mFirstTouchTarget不为null
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
- 如果被拦截的话就不会再分发
- 如果没有被拦截,会遍历所有的子View进行分发,看看是不是有子View要处理这个事件
- 根据是否拦截以及是否有子View处理事件,对事件进行处理
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;
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不为null,那么会执行else,调用了dispatchTransformedTouchEvent,参数为(ev, cancelChild,target.child, target.pointerIdBits)),cancelChild为true,target.child不为null
private Boolean dispatchTransformedTouchEvent(MotionEvent event, Boolean cancel,
View child, int desiredPointerIdBits) {
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;
}
// 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);
}
return handled;
}
做了两步处理,第一步event.setAction(MotionEvent.ACTION_CANCEL);将事件设置为cancel,第二步调用handled = child.dispatchTouchEvent(event);将cancel事件分发给子View。
- 事件没有被拦截,但是没有子View消费这个事件,这个时候mFirstTouchTarget不为null,会执行dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)方法,其中的参数是cancelChild为false,target.child为null,那么会执行handled = super.dispatchTouchEvent(transformedEvent);调用View的dispatchTouchEvent来处理事件。
- 事件没有被拦截,有子View消费事件,这个时候mFirstTouchTarget不为null,执行handled = true;将事件消费。
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
}
会有多个move事件,分发流程和上面是一样的。
滑动冲突解决
- 内部拦截法
requestDisallowInterceptTouchEvent,true代表让父亲不要拦截
false 代表让父亲拦截 - 外部拦截法
onInterceptTouchEvent,返回true则会拦截,返回false则不会拦截