Android的事件分发从模块分的话 一个是分发,一个是处理,对应的类就是ViewGroup和View。从调用关系来看的话,事件分发从Activity开始,到最后的view处理事件。我们首先对事件从Avtivity开始调用的时候开始入手。
事件的开始到ViewGroup
当我们的手指触碰到屏幕的时候就开始了事件的开始action_down。我们都知道页面的最高级是Activity,所以这个事件首先到达的是Activity的dispatch。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 这个地方开始代理给PhoneWindow->DecorView->GroupView
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果上面这个方法返回false。 avtivity自己处理
return onTouchEvent(ev);
}
View 事件处理
同样是dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//事件处理逻辑 ,当有onTouchListener的时候且返回true的时候,可以理解为事件被消费。不会走onTouchEvent中
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 仅当上面判断逻辑为false 即没有onTouchListener & onTouch->false 才会走下面逻辑。
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
// 执行click事件
performClickInternal();
}
}
}
break;
}
public boolean performClick() {
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;
}
return result;
}
从分析View的事件处理流程可以看出,View处理事件的流程是将事件先传给onTouchListener 如果返回值为false,继续给onTouchEvent方法,在这个方法里会调用click方法。这也就能解释一些为什么重写onTouchListener事件后onclick事件失效的现象。
事件分发
相比事件处理,事件分发就复杂的多,我们平常遇到的事件冲突都是和这个事件分发密不可分,我们接下来看,介于代码量太多,好多健壮式代码,所以我把与逻辑相关的代码抽出来看
事件拦截
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//事件我们知道是一个down 多个move和一个up构成一套,所以刚上来会判断onInterceptTouchEvent的值,如果是true,这面会直接走下面的逻辑
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;
}
}
// 开始处理
if (mFirstTouchTarget == null) {
// 注意这里的child参数为null
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
return handled;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 直接执行view的事件处理流程
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;
}
Down事件处理
down事件是整个事件传递的开始。我们直接刨除拦截的部分直接看关键代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
TouchTarget newTouchTarget = null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
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.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//首先会进来这里
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// 这里处理view的前后顺序,我们只需要知道是正序加进去,倒序取出的即可。
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
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 (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 当down的时候mFirstTouchTarget =null,so 这里newTouchTarget =null
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//这里分发事件到子view,child不为空,调用child.dispatchTouchEvent,接着循环,是否有view处理
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
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();
// 这里有处理down事件的几个关键值赋值newTouchTarget !=null ;newTouchTarget.next=null;alreadyDispatchedToNewTouchTarget = true;mFirstTouchTarget!=null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
// 当找到处理事件的view,跳出循环
break;
}
}
}
if (mFirstTouchTarget == null) {
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
// 进循环,只循环一次
final TouchTarget next = target.next;
// 前面分析过这个地方是走if的
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// chancelChild =false;
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;
}
return handled;
}
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
总结一下down事件首先也是看是否拦截,如果不拦截,那么遍历子view寻找事件处理者。如果都不处理,那么返回自己处理。自己如果不处理,那么返回上层。
MOVE 事件
move事件要结合down来看。流程依旧是正常流程,
// 最终会到这个位置,从down事件赋值mFirstTouchTarget不为null
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;
// 这个地方直接分发move事件
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;
}
}
这个move事件没什么好说的,就是直接分发给down的target.child。有一个很关键的变量mFirstTouchTarget 这个主要是判断是否要自己view处理逻辑。如果大家自己看源码的会深有感触。到这结束,我们的事件分发主流程就走完了,但是有一些情况类似于像子view 开始接收事件,然后dispatch返回fasle这些情况没有一一分析,我觉得剩下这些自己看一遍源码,会理解的更深刻的一点,所以这个就不一一说明了。
事件冲突和拦截
事件冲突和拦截,基本上是有2个以上的父子布局才能有的情况,经过上面的分析,我们确定了down事件是没法做拦截处理的,所以所有的拦截都需要在move事件做处理。关键性代码在于
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
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;
}
}
可以看出这个地方主要是onInterceptTouchEvent函数的返回值和disallowIntercept变量的值影响着事件传递。
disallowIntercept这个参数是用requestDisallowInterceptTouchEvent这个函数改变的值,这里就会有两种拦截事件的方式,1. 父布局直接重写onInterceptTouchEvent 主动拦截事件与分发。2.子布局用getParent().requestDisallowInterceptTouchEvent()子布局拦截事件与分发。这里要注意一点的是,如果是子布局拦截分发,必须要让down事件经过子view,所以父布局的onInterceptTouchEvent 在down事件里必须是false。
下面是横竖滑动的两个解决方案。
内部拦截
// 父布局
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN){
return false;
}
return true;
// 子view
private int mLastX, mLastY;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
外部拦截
// 父布局
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
mLastX = (int) event.getX();
mLastY = (int) event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
return true;
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
return super.onInterceptTouchEvent(event);
}