Android ViewGroup攔截觸控事件詳解
阿新 • • 發佈:2018-12-21
/** * {@inheritDoc} */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (!onFilterTouchEventForSecurity(ev)) { return false; } final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; // 是否禁用攔截,如果為true表示不能攔截事件;反之,則為可以攔截事件 boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // ACTION_DOWN事件,即按下事件 if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget != null ) { mMotionTarget = null; } // If we're disallowing intercept or if we're allowing and we didn't // intercept。如果不允許事件攔截或者不攔截該事件,那麼執行下面的操作 if (disallowIntercept || !onInterceptTouchEvent(ev)) // 1、是否禁用攔截、是否攔截事件的判斷 // reset this event's action (just to protect ourselves) ev.setAction(MotionEvent.ACTION_DOWN); // We know we want to dispatch the event down, find a child // who can handle it, start with the front-most child. final int scrolledXInt = (int) scrolledXFloat; final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren; final int count = mChildrenCount; for (int i = count - 1; i >= 0; i--) // 2、迭代所有子view,查詢觸控事件在哪個子view的座標範圍內 final View child = children[i]; // 該child是可見的 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { // 3、獲取child的座標範圍 child.getHitRect(frame); // 4、判斷髮生該事件座標是否在該child座標範圍內 if (frame.contains(scrolledXInt, scrolledYInt)) // offset the event to the view's coordinate system final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; // 5、child處理該事件,如果返回true,那麼mMotionTarget為該child。正常情況下, // dispatchTouchEvent(ev)的返回值即onTouchEcent的返回值。因此onTouchEcent如果返回為true, // 那麼mMotionTarget為觸控事件所在位置的child。 if (child.dispatchTouchEvent(ev)) // 6、 mMotionTarget為該child mMotionTarget = child; return true; } } } } } }// end if boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { // Note, we've already copied the previous state to our local // variable, so this takes effect on the next event mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // 觸控事件的目標view, 即觸控所在的view final View target = mMotionTarget; // 7、如果mMotionTarget為空,那麼執行super.super.dispatchTouchEvent(ev), // 即View.dispatchTouchEvent(ev),就是該View Group自己處理該touch事件,只是又走了一遍View的分發過程而已. // 攔截事件或者在不攔截事件且target view的onTouchEvent返回false的情況都會執行到這一步. if (target == null) { // We don't have a target, this means we're handling the // event as a regular view. ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } return super.dispatchTouchEvent(ev); } // 8、如果沒有禁用事件攔截,並且onInterceptTouchEvent(ev)返回為true,即進行事件攔截. ( 在某個事件時攔截的情形 ) if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL); ev.setLocation(xc, yc); // if (!target.dispatchTouchEvent(ev)) { // target didn't handle ACTION_CANCEL. not much we can do // but they should have. } // clear the target mMotionTarget = null; // Don't dispatch this event to our own view, because we already // saw it when intercepting; we just want to give the following // event to the normal onTouchEvent(). return true; } if (isUpOrCancel) { mMotionTarget = null; } // finally offset the event to the target's coordinate system and // dispatch the event. final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; } // 9、事件不攔截,且target view在ACTION_DOWN時返回true,那麼後續事件由target來處理事件 return target.dispatchTouchEvent(ev); }
如果不對事件進來攔截,且TouchTv對事件的處理返回true,那麼在DOWN事件時,mMotionTarget就是TouchTv,後續的事件就會通過註釋9來處理,即直接交給TouvhTv來處理。如果在DOWN時就攔截事件,那麼mMotionTarget為空,則會執行註釋7出的程式碼,一直呼叫super.dispatchTouchEvent處理事件,即呼叫本類的事件處理,最終會呼叫onTouchEvent方法。如果在DOWN時不攔截,MOVE時攔截,那麼會引發註釋8的程式碼,target view收到一個cancel事件,且mMotionTarget被置空,後續事件在註釋7出的代理進行處理,即在自己的onTouchEvent中進行處理。