1. 程式人生 > >Android ViewGroup攔截觸控事件詳解

Android ViewGroup攔截觸控事件詳解

/**  * {@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中進行處理。