「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」
相关文章:
Android View的事件分发(一)-事件分发的过程
Android View的事件分发(二)-事件传递顺序
Android View的事件分发(三)-事件的分发(dispatchTouchEvent)
Android View的事件分发(四)-事件处理(onTouchEvent)
Android View的事件分发(五)-事件拦截(onInterceptTouchEvent)
上一篇文章,介绍了View事件处理的传递顺序,已经整个过程中事件处理的传递和回调,怎么处理,在哪里处理,通过流程图可以看得很清楚,但是,要想详细的了解整个过程的原理,还是需要通过源码的分析,才知道,那么下面就介绍dispatchTouchEvent 的源码,看看它是怎么分发事件的。
DispatchTouchEvent
自上而下的分发,先从Activity的dispatchTouchEvent看:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
调用的是super也就是父类的dispatchTouchEvent:
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
该方法是只要用户在 Activity 的任何一处点击或者滑动都会响应,一般不使用。接下去看getWindow().superDispatchTouchEvent(ev) 所代表的具体含义。getWindow() 返回对应的 Activity 的 window。一个Activity 对应一个 Window 也就是 PhoneWindow, 一个 PhoneWindow 持有一个 DecorView 的实例, DecorView 本身是一个 FrameLayout。这句话一定要牢记。
父类的实现中,有一个onUserInteraction方法,这个方法主要是响应用户在界面的点击或者滑动,一般不会使用,下面看getWindow().superDispatchTouchEvent(ev),getWindow很明显,是返回Activity 的 window对象,一个Activity 对应一个 Window 也就是 PhoneWindow, 一个 PhoneWindow 持有一个 DecorView 的实例, DecorView 本身是一个 FrameLayout,这个可能是面试过程中会问的,最好记住,哈哈
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
在PhoneWindow找到上面的方法:
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
这个方法又调用了父类的superDispatchTouchEvent,PhoneWindow的父类,就是DecorView,也就是根View了,我们调用setContentView,设置的就是它的子View这个在前面的View的绘制流程中已经说过,这里不多说了,到这里事件已经被传递到根 View 中,而根 View 其实也是 ViewGroup,所以最后走到的是ViewGroup的事件分发了,那么看下ViewGroup的事件分发。
ViewGroup 事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
......
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 当有 down 操作,会把之前的target 以及标志位都复位
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
//清除 FLAG_DISALLOW_INTERCEPT,并且设置 mFirstTouchTarget 为 null
resetTouchState(){
if(mFirstTouchTarget!=null){mFirstTouchTarget==null;}
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
......
};
}
final boolean intercepted;//ViewGroup是否拦截事件
// mFirstTouchTarget是ViewGroup中处理事件(return true)的子View
//如果没有子View处理则mFirstTouchTarget=null,ViewGroup自己处理
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);//onInterceptTouchEvent
ev.setAction(action);
} else {
intercepted = false;
//如果子类设置requestDisallowInterceptTouchEvent(true)
//ViewGroup将无法拦截MotionEvent.ACTION_DOWN以外的事件
}
} else {
intercepted = true;
//actionMasked != MotionEvent.ACTION_DOWN并且没有子View处理事件,则将事件拦截
//并且不会再调用onInterceptTouchEvent询问是否拦截
}
......
......
}
上面代码里有个标志位:mFirstTouchTarget,在if条件里,2个条件,意思是当按下事件触发后,或者有子View处理了事件,再到里面,看判断条件,当子View没有调用requestDisallowInterceptTouchEvent来拦截ViewGroup 的拦截,这句话有点绕哈,意思就是子View不做请求,由ViewGroup处理,那么 ViewGroup 的 onInterceptTouchEvent 就会被调用,来判断是否是要拦截。当子 view 调用requestDisallowInterceptTouchEvent,请求不允许拦截事件(字面意思哈),即使父 View的onInterceptTouchEvent 中返回true 也没用了。
这里需要注意的就是:onInterceptTouchEvent 默认返回 false。 当 ACTION_DOWN 事件到来时,此时 mFirstTouchTarget 为 null,此时其实也还未收到子 view requestDisallowInterceptTouchEvent。所以这时候,只要父 view 把 ACTION_DOWN 事件给拦截了,那么子 view 就收不到任何事件消息了。所以,一般在 ACTION_DOWN 的时候,父 view 不作拦截。
所以如果子View想父类不拦截事件的处理,就调用requestDisallowInterceptTouchEvent,让子View来处理相应的MotionEvent。
FLAG_DISALLOW_INTERCEPT 这个标记位就是通过子 View requestDisallowInterceptTouchEvent 方法设置的。
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
如果这个 ViewGroup 有父 View 的时候,还得让父父 View 不能拦截。mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
public boolean dispatchTouchEvent(MotionEvent ev) {
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 (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null))
{
ev.setTargetAccessibilityFocus(false);
//如果子View没有播放动画,而且点击事件的坐标在子View的区域内,继续下面的判断
continue;
}
//判断是否有子View处理了事件
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//如果已经有子View处理了事件,即mFirstTouchTarget!=null,终止循环。
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//点击dispatchTransformedTouchEvent代码发现其执行方法实际为
//return child.dispatchTouchEvent(event); (因为child!=null)
//所以如果有子View处理了事件,我们就进行下一步:赋值
......
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//addTouchTarget方法里完成了对mFirstTouchTarget的赋值
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
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) {
......
if (child == null) {
//如果没有子View处理事件,就自己处理
handled = super.dispatchTouchEvent(event);
} else {
//有子View,调用子View的dispatchTouchEvent方法
handled = child.dispatchTouchEvent(event);
......
return handled;
}
所以需要哪层处理事件,就让父类不拦截,不需要怎么默认是父类处理,如果有子View,则调用子View的dispatchTouchEvent方法判断当前事件是否处理了,如果处理了则给赋值 mFirstTouchTarget,赋值成功则跳出循环。ViewGroup的事件分发,最终还是调用了View的dispatchTouchEvent,代码里可以清楚的看到。分发是一层层由上而下,回调结果则是由下而上,直到回到顶层,或者被消费了,也就是处理了,上一篇的文章的流程图可以清晰的看到。
源码还是很清晰的,包括命名
View最终是怎么去处理事件的
class View:
public boolean dispatchTouchEvent(MotionEvent ev) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
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;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
上面是View的dispatchTouchEvent方法的全部代码。很明显View是单独的一个元素,它没有子View,所以也没有分发的代码。我们需要关注的也只是上面当中的一部分代码。
//如果窗口没有被遮盖
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
//当前监听事件
ListenerInfo li = mListenerInfo;
//需要特别注意这个判断当中的li.mOnTouchListener.onTouch(this, event)条件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//result为false调用自己的onTouchEvent方法处理
if (!result && onTouchEvent(event)) {
result = true;
}
}
通过上面代码我们可以看到View会先判断是否设置了OnTouchListener,如果设置了OnTouchListener并且onTouch方法返回了true,那么onTouchEvent不会被调用。 当没有设置OnTouchListener或者设置了OnTouchListener但是onTouch方法返回false则会调用View自己的onTouchEvent方法。
至此,事件的分发介绍的差不多了,从Activity的分发,到Window(这里是PhoneWindow),再到DecorView最后到ViewGroup,就是整个事件分发的过程,当然最后还是调用的View的dispatchTouchEvent处理的。是个清晰,自然的分发过程。有介绍有误的,还请指正,欢迎留言评论。