一.介绍
Android三种事件类型:ACTION_DOWN,ACTOIN_MOVE,ACTION_UP。
事件传递的三个阶段:
分发(Dispatch)
方法:public boolean dispatchTouchEvent(MotionEvent ev)
拦截(Intercept)
方法:public boolean onInterceptTouchEvent(MotionEvent ev)
消费(Consume)
方法:public boolean onTouchEvent(MotionEvent event)
二.源码分析
//Activity.java /** * 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) { //当此activity在栈顶时,用户对手机:触屏点击,按home,back,menu键都会触发此方法。 //注:下拉statubar,旋转屏幕,锁屏,不会触发此方法. onUserInteraction(); } //查看getWindow().对event做了什么分发? if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } Window.java 是一个抽象类。在Activity里attach()查看Window创建过程 final void attach(...){ attachBaseContext(context); mFragments.attachHost(null /*parent*/); //PhoneWindow 是 getWindow()的具体实现类。进去查看它的superDispatchTouchEvent() mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); mMainThread = aThread; ... }
//PhoneWindow.java extends Window @Override public boolean superDispatchTouchEvent(MotionEvent event) { //PhoneWindow Event 分发操作有在DecorView实现了,继续进去看看DecorView类。 return mDecor.superDispatchTouchEvent(event); } // This is the top-level view of the window, containing the window decor. private DecorView mDecor;
//DecorView.java public boolean superDispatchTouchEvent(MotionEvent event) { //DecorView就是activity的顶级view, //点击事件已经从activity传到View当中了,接下来要分析的就是顶级View把事件如何分发到各个子view中 //DecorView extends FrameLayout return super.dispatchTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { final Window.Callback cb = mWindow.getCallback(); return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); }
//FrameLayout.java //FrameLayout extends ViewGroup 到 ViewGroup里看看 public class FrameLayout extends ViewGroup {}
//ViewGroup.java public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } // If the event targets the accessibility focused view and this is it, start // normal event dispatch. Maybe a descendant is what will handle the click. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. //当点击事件是MotionEvent.ACTION_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; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //没有被取消并且没有被拦截,事件传递到子View if (!canceled && !intercepted) { // If the event is targeting accessiiblity 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. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : 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); // Find a child that can receive the event. // Scan children from front to back. 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 there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } 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; } resetCancelNextUpFlag(child); 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; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // 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; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }
ViewGroup 分发事件总结:
1. android的事件传递是先传递到父类,再到子类的。
2. ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,但是子View可以通过requestDisallowInterceptTouchEvent(boolean disallowIntercept)控制父类的拦截事件是否调用。
3. 子View消耗掉点击事件后,父类onTouchEvent方法不会调用,子View不消耗点击事件,会传到父类onTouchEvent方法,父类onTouchEvent方法返回false,则最终传递到activity的onTouchEvent方法。
4. ViewGroup一旦调用onInterceptTouchEvent方法拦截点击事件后,本次点击序列事件则都交于该ViewGroup处理,并且onInterceptTouchEvent将不再执行。
5. 当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action.也就是说,子view 未消耗点击事件,dispatchTouchEvent返回false,这样mFirstTouchTarget =null,则后续action直接由ViewGroup执行,不传递给子View。
public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.isFromSource(InputDevice.SOURCE_MOUSE) && ev.getAction() == MotionEvent.ACTION_DOWN && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) && isOnScrollbarThumb(ev.getX(), ev.getY())) { return true; } return false; }
@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); } }
三.总结
一张图介绍PhoneWindow,DecorView,Activity,ViewGroup,View 关系
在来一张图介绍下ViewGroup里的事件分发过程:(该图片来自https://www.jianshu.com/p/cf22ea3b09a5)
注:此图横着分析看,由上到下,由左到右。几个概念注意下 :UserActivity(=Activiyt),ViewParent(=ViewGoup),ChildView(=View)。