1.Android事件类型
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return true;
}
如上图所示android主要有四种事件类型,Down、Move、Up、Cancel。
在一次事件流中:
Down触发一次,手指在屏幕按下时。
Move事件会触发0次或多次,手指在屏幕上滑动时。
Up事件会触发0次或一次,手指在屏幕抬起时。
Cancel事件会触发0次或一次,滑动超出空间边界时。
2.android App层事件分发机制调用链是怎么样的?
App层事件分发的入口在Activity中,dispatchTouchEvent是在Framework层的WindowManagerService掉用的,WMS中的事件是来源于Native层,Linux层,这块的事件传递有机会在写文章进行分析,这篇文章主要分析Android事件分发机制在App成是如何展现的。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow()返回的是PhoneWindow对象。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
mDecor是DecorView,是PhoneWindow的一个内部类,继承FrameLayout。
FrameLayout继承自ViewGroup,接下来看事件传递在ViewGroup中是如何处理的。事件传递的主要逻辑就在ViewGroup中。
接下来看ViewGroup,dispatchToucheEvent(MotionEvent ev)是如何对事件进行分发的。
public class ViewGroup {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Check for interception.
//按下后,第一次调用时actionMasked=down,mFirstTouchTarget=null
//一直按着 actionMasked=move, 如果mFirstTouchTarget!=null说明有子view处理了touch事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//调用子类复写的方法 如果为true表示拦截,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.
//如果没有子view处理touch事件,并且不是第一次按下的down事件,group会对touch事件进行拦截
intercepted = true;
}
TouchTarget newTouchTarget = null;
//第一次按下时,判断view是否处理事件的标志
boolean alreadyDispatchedToNewTouchTarget = false;
//如果不拦截,则进入事件分发
if (!canceled && !intercepted) {
//注意,只有ACTION_DOWN时才会遍历子View
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final ArrayList preorderedList = buildTouchDispatchChildList();
final View[] children = mChildren;
//开始遍历子view
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//isTransformedTouchPointInView 通过比对坐标,来判断事件是否落在了child上,
// true是,false不是
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
// 如果事件没有落在child上,则跳过此次循环,执行下一次循环
continue;
}
//通过遍历TouchTarget链表查找TouchTarget,如果找到了,则break跳出循环
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;
}
//第一次进来newTouchTarget==null,进入下面的逻辑
//如果dispatchTransformedTouchEvent 返回true,表示子view对事件进行了处理,则跳出此循环,
// 不会再向子view进行分发
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
//将child缓存到TouchTarget的链表中
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//注意这个标志位,true,下面的逻辑会用到
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
}
}
// Dispatch to touch targets.
//ACTION_DOWN和ACTION_MOVE 的时都会执行下面逻辑
//mFirstTouchTarget==null 说明没有子View处理touch事件,dispatchTransformedTouchEvent的第三个参数为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 链表进行遍历,TouchTarget里缓存了处理事件的view,
//一次完整事件,move会调用多次,所以在ACTION_DOWN时找到的对应的view,缓存起来,
// 而不是每次都遍历ViewGroup的子View,这样太损坏性能
TouchTarget target = mFirstTouchTarget;
//开始对链表进行遍历,为啥是一个链表呢,这是为了进行多点触摸,会有多个view响应触摸事件。
while (target != null) {
final TouchTarget next = target.next;
//alreadyDispatchedToNewTouchTarget ==true ,并且缓存的target和newTouchTarget是同一个
//直接将handled置为true,防止ACTION_DOWN时dispatchTransformedTouchEvent方法重复调用
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
target = next;
}
}
//将handled 返回dispatchTouchEvent的调用者。
return handled;
}
/**
* Gets the touch target for specified child view.
* Returns null if not found.
* 通过遍历TouchTarget 链表,找到touch target
*/
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
* 将TouchTarget和view进行关联,并且把这个TouchTarget放在链表的头部
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 只看最关键的两行代码
if (child == null) {
//如果child为null,没有子view处理touch事件,则调用父类View的dispatchTouchEvent。
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//如果child不为空,则调用child view 的dispatchTouchEvent
//这行代码很关键,如果child是ViewGroup则继续在ViewGoup的进行事件分发,这会是一个递归调用
//如果child 是一个View,则调用View的dispatchTouchEvent,进行事件的分发。
handled = child.dispatchTouchEvent(transformedEvent);
}
//以上两行代码最终会调用到View的dispatchTouchEvent方法。
return handled;
}
}
接下来看View dispatchTouchEvent如何处理的
public class View {
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = false;
ListenerInfo li = mListenerInfo;
//如果view设置了mOnTouchListener,先调用mOnTouchListener.onTouch,
//如果返回true,则表示消费了此事件,不在向下传递,为啥这样说?那是因为,通过他的返回值,进行了逻辑判断
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果result为false,则调用View.onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
return result;
}
public boolean onTouchEvent(MotionEvent event) {
switch (action) {
case MotionEvent.ACTION_UP:
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//调用performClick mOnClickListener.onClick()方法
performClickInternal();
}
break;
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_CANCEL:
break;
case MotionEvent.ACTION_MOVE:
break;
}
return false;
}
public boolean performClick() {
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;
}
}
如果仔细看了ViewGroup和View事件分发逻辑代码。我们可以总结为以下几点。
1.ViewGroup接收到DispatchTouchEvent调用后,先调用了onInterceptTouchEvent,通过返回值进行判断是否对事件进行拦截。我们可以在代码中复写这个方法来进行事件的拦截。
2.down事件后,ViewGoup会遍历子View,找到处理Touch事件的View,并且缓存到了TouchTarget链表中,一次事件流中,只有down的时候才会进行子View的遍历。
只有做的好处提升了性能,缓存的链表中也是为了多点触摸,会有多个view响应触摸事件。
move事件时,则会从这个链表中进行遍历,通过调用dispatchTransformedTouchEvent方法进行事件的传递。
3.在dispatchTransformedTouchEvent方法中,根据child的值是否为null进入不同的分发流程。
1)如果为null说明没有子view没有响应事件,则调用父类View的dispatchTouchEvent--->onTouchEvent方法。
2)如果child值不为null。则调用child的dispatchTouchEvent方法,
此时如果child是一个ViewGoup则还是执行ViewGoup中的分发逻辑,进行递归调用。
如果child是一个View,则调用View的dispatchTouchEvent--->onTouchEvent方法。
4.在View的dispatchTouchEvent方法中:
1)listener的onTouch优先级要高于View的onTouchEvent
2)会先调用mOnTouchListener.onTouch方法,并根据返回值进行判断是否消费事件。
3)如果上面的返回值为false,则会调用View的onTouchEvent方法。
5.在View的onTouchEvent方法中UP事件时,才会调用View设置的onClickListener的监听事件。
这就是为什么了,同时给一个View设置了onTouch监听和lonClickListener监听时,当onTouch返回true无法调用onClick事件的原因。