ViewGroup的事件拦截、事件分发、事件处理

xiaoxiao2021-02-28  105

上篇博客说了下View的事件分发和事件处理,接着这里说下ViewGroup,ViewGroup多了一个事件拦截,涉及到有三个相应的方法;

dispatchTouchEvent 事件分发 onInterceptTouchEvent 事件拦截 onTouchEvent 事件处理

先看下下面几种不同情况运行的结果;

正常情况:

ACTION_DOWN: ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>View.dispatchTouchEvent—>View.onTouch—>View.onTouchEvent ACTION_UP: ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>View.dispatchTouchEvent—>View.onTouch—>View.onTouchEvent—>View.onClick

注释掉View.onClick ACTION_DOWN: ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>View.dispatchTouchEvent—>View.onTouch—>View.onTouchEvent—>ViewGroup.onTouchEvent

将View中的onTouchEvent返回值改成true ACTION_DOWN: ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>View.dispatchTouchEvent—>View.onTouch—>View.onTouchEvent ACTION_UP: ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>View.dispatchTouchEvent—>View.onTouch—>View.onTouchEvent

将ViewGroup中的onInterceptTouchEvent返回值改成true ACTION_DOWN: ViewGroup.dispatchTouchEvent—>ViewGroup.onInterceptTouchEvent—>ViewGroup.onTouchEvent

结合上面的运行结果,点进ViewGroup源码去看看并分析下出现上面结果的原因; 点击源码找到dispatchTouchEvent()方法,

@Override public boolean dispatchTouchEvent(MotionEvent ev) { ... boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { ... if (actionMasked == MotionEvent.ACTION_DOWN) { //清楚掉一些标记,主要是mFirstTouchTarget cancelAndClearTouchTargets(ev); resetTouchState(); } //intercepted用来标识事件拦截 默认是false 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); } else { intercepted = false; } } else { intercepted = true; } ... final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //intercepted为true也就是事件拦截那里返回为true就不会走这个if里面 if (!canceled && !intercepted) { ... if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { ... if (newTouchTarget == null && childrenCount != 0) { ... if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { 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(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } ... } if (preorderedList != null) preorderedList.clear(); } ... } } if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ... } ... return handled; }

根据拿到的action来进行判断,为MotionEvent.ACTION_DOWN的时候首先调用cancelAndClearTouchTargets(ev);清除掉target

// 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(); } /** * Clears all touch targets. */ private void clearTouchTargets() { TouchTarget target = mFirstTouchTarget; if (target != null) { do { TouchTarget next = target.next; target.recycle(); target = next; } while (target != null); mFirstTouchTarget = null; } }

会走到clearTouchTargets()方法中将mFirstTouchTarget至为null;mFirstTouchTarget设置为null后,就定义了一个事件拦截的标识,模式是false,

// Check for interception. final boolean intercepted;

如果事件没有被拦截掉的话,就是说intercepted为false的时候,就会调用这里,

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(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } 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); //如果child为null直接调用自己中的dispatchTouchEvent,否则调用子child中的dispatchTouchEvent if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } ... return handled; }

走到这里的话,已经调用了ViewGroup.dispatchTouchEvent、ViewGroup.onInterceptTouchEvent、View.dispatchTouchEvent,到了View.dispatchTouchEvent,接着就会走View.onTouch、View.onTouchEvent、View.onClick这个在View的事件处理里面已经说过了,这个时候运行的就是第一个结果; 如果将View的点击事件屏蔽掉,performClick()方法返回的就是false,就是说没有消费事件, 将View中onTouchEvent的返回值改成true,在View的事件中已经详细说了, 将ViewGroup中onInterceptTouchEvent的返回值改成true,下面这个判断就不会走,

if (!canceled && !intercepted){ ... }

没有走,就不会调用dispatchTransformedTouchEvent方法,就不会去走View中的那些方法了;通过ViewGroup和View的源码知道: 如果说子View没有一个地方返回true,只会进来一次响应DOWN事件,代表不需要消费该事件,如果想响应MOVE,UP必须找个地方返回true 对于ViewGroup来说,如果想拦截子View的touch事件,重写onInterceptTouchEvent返回true即可 如果onInterceptTouchEvent返回的是true会执行ViewGroup的onTouchEvent方法,如果子view没有消费事件也会执行onTouchEvent方法 上面如有对ViewGroup的事件处理写的不对的地方,欢迎交流

转载请注明原文地址: https://www.6miu.com/read-64013.html

最新回复(0)