/**
  * Duplicate touch events to child views. We want to dispatch a down motion event and the move
  * events to child views, but calling dispatchTouchEvent() causes StackOverflowError. Therefore we
  * do it manually.
  *
  * @param ev motion event to be passed to children
  * @param pendingEvents pending events like ACTION_DOWN. This will be passed to the children
  *     before ev
  */
 private void duplicateTouchEventForChildren(MotionEvent ev, MotionEvent... pendingEvents) {
   if (ev == null) {
     return;
   }
   for (int i = getChildCount() - 1; 0 <= i; i--) {
     View childView = getChildAt(i);
     if (childView != null) {
       Rect childRect = new Rect();
       childView.getHitRect(childRect);
       MotionEvent event = MotionEvent.obtainNoHistory(ev);
       if (!childRect.contains((int) event.getX(), (int) event.getY())) {
         continue;
       }
       float offsetX = -childView.getLeft();
       float offsetY = -childView.getTop();
       boolean consumed = false;
       if (pendingEvents != null) {
         for (MotionEvent pe : pendingEvents) {
           if (pe != null) {
             MotionEvent peAdjusted = MotionEvent.obtainNoHistory(pe);
             peAdjusted.offsetLocation(offsetX, offsetY);
             consumed |= childView.dispatchTouchEvent(peAdjusted);
           }
         }
       }
       event.offsetLocation(offsetX, offsetY);
       consumed |= childView.dispatchTouchEvent(event);
       if (consumed) {
         break;
       }
     }
   }
 }
  private void postClick() {
    synchronized (mLock) {
      if (!mPostClickScheduled) {
        return;
      }
      mPostClickScheduled = false;

      MotionEvent event = mPostTouchStream.getLastEvent();
      if (event == null || event.getAction() != MotionEvent.ACTION_UP) {
        return;
      }

      showTapCandidateLocked();
      MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
      DispatchEvent d =
          obtainDispatchEventLocked(
              eventToEnqueue,
              EVENT_TYPE_CLICK,
              0,
              mPostLastWebKitXOffset,
              mPostLastWebKitYOffset,
              mPostLastWebKitScale);
      enqueueEventLocked(d);
    }
  }
  private void postLongPress() {
    synchronized (mLock) {
      if (!mPostLongPressScheduled) {
        return;
      }
      mPostLongPressScheduled = false;

      MotionEvent event = mPostTouchStream.getLastEvent();
      if (event == null) {
        return;
      }

      switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
        case MotionEvent.ACTION_POINTER_DOWN:
        case MotionEvent.ACTION_POINTER_UP:
          break;
        default:
          return;
      }

      MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
      eventToEnqueue.setAction(MotionEvent.ACTION_MOVE);
      DispatchEvent d =
          obtainDispatchEventLocked(
              eventToEnqueue,
              EVENT_TYPE_LONG_PRESS,
              0,
              mPostLastWebKitXOffset,
              mPostLastWebKitYOffset,
              mPostLastWebKitScale);
      enqueueEventLocked(d);
    }
  }
  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (mTouchInterceptionListener == null) {
      return false;
    }

    // In here, we must initialize touch state variables
    // and ask if we should intercept this event.
    // Whether we should intercept or not is kept for the later event handling.
    switch (ev.getActionMasked()) {
      case MotionEvent.ACTION_DOWN:
        mInitialPoint = new PointF(ev.getX(), ev.getY());
        mPendingDownMotionEvent = MotionEvent.obtainNoHistory(ev);
        mDownMotionEventPended = true;
        mIntercepting = mTouchInterceptionListener.shouldInterceptTouchEvent(ev, false, 0, 0);
        mBeganFromDownMotionEvent = mIntercepting;
        mChildrenEventsCanceled = false;
        return mIntercepting;
      case MotionEvent.ACTION_MOVE:
        // ACTION_MOVE will be passed suddenly, so initialize to avoid exception.
        if (mInitialPoint == null) {
          mInitialPoint = new PointF(ev.getX(), ev.getY());
        }

        // diffX and diffY are the origin of the motion, and should be difference
        // from the position of the ACTION_DOWN event occurred.
        float diffX = ev.getX() - mInitialPoint.x;
        float diffY = ev.getY() - mInitialPoint.y;
        mIntercepting =
            mTouchInterceptionListener.shouldInterceptTouchEvent(ev, true, diffX, diffY);
        return mIntercepting;
    }
    return false;
  }
 private void enqueueDoubleTapLocked(MotionEvent event) {
   MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
   DispatchEvent d =
       obtainDispatchEventLocked(
           eventToEnqueue,
           EVENT_TYPE_DOUBLE_TAP,
           0,
           mPostLastWebKitXOffset,
           mPostLastWebKitYOffset,
           mPostLastWebKitScale);
   enqueueEventLocked(d);
 }
 private void enqueueHitTestLocked(MotionEvent event) {
   mUiCallbacks.clearPreviousHitTest();
   MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
   DispatchEvent d =
       obtainDispatchEventLocked(
           eventToEnqueue,
           EVENT_TYPE_HIT_TEST,
           0,
           mPostLastWebKitXOffset,
           mPostLastWebKitYOffset,
           mPostLastWebKitScale);
   enqueueEventLocked(d);
 }
示例#7
0
 @Override
 public void onInputEvent(InputEvent event) {
   boolean handled = false;
   try {
     if (event instanceof MotionEvent
         && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
       MotionEvent dup = MotionEvent.obtainNoHistory((MotionEvent) event);
       dispatchPointer(dup);
       handled = true;
     }
   } finally {
     finishInputEvent(event, handled);
   }
 }
  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    if (mCallbacks != null) {
      switch (ev.getActionMasked()) {
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
          mIntercepted = false;
          mDragging = false;
          mCallbacks.onUpOrCancelMotionEvent(mScrollState);
          break;
        case MotionEvent.ACTION_MOVE:
          if (mPrevMoveEvent == null) {
            mPrevMoveEvent = ev;
          }
          float diffY = ev.getY() - mPrevMoveEvent.getY();
          mPrevMoveEvent = MotionEvent.obtainNoHistory(ev);
          if (getCurrentScrollY() - diffY <= 0) {
            // Can't scroll anymore.

            if (mIntercepted) {
              // Already dispatched ACTION_DOWN event to parents, so stop here.
              return false;
            }

            // Apps can set the interception target other than the direct parent.
            final ViewGroup parent;
            if (mTouchInterceptionViewGroup == null) {
              parent = (ViewGroup) getParent();
            } else {
              parent = mTouchInterceptionViewGroup;
            }

            // Get offset to parents. If the parent is not the direct parent,
            // we should aggregate offsets from all of the parents.
            float offsetX = 0;
            float offsetY = 0;
            for (View v = this; v != null && v != parent; v = (View) v.getParent()) {
              offsetX += v.getLeft() - v.getScrollX();
              offsetY += v.getTop() - v.getScrollY();
            }
            final MotionEvent event = MotionEvent.obtainNoHistory(ev);
            event.offsetLocation(offsetX, offsetY);

            if (parent.onInterceptTouchEvent(event)) {
              mIntercepted = true;

              // If the parent wants to intercept ACTION_MOVE events,
              // we pass ACTION_DOWN event to the parent
              // as if these touch events just have began now.
              event.setAction(MotionEvent.ACTION_DOWN);

              // Return this onTouchEvent() first and set ACTION_DOWN event for parent
              // to the queue, to keep events sequence.
              post(
                  new Runnable() {
                    @Override
                    public void run() {
                      parent.dispatchTouchEvent(event);
                    }
                  });
              return false;
            }
            // Even when this can't be scrolled anymore,
            // simply returning false here may cause subView's click,
            // so delegate it to super.
            return super.onTouchEvent(ev);
          }
          break;
      }
    }
    return super.onTouchEvent(ev);
  }
  @Override
  public boolean onTouch(View v, MotionEvent event) {
    if (!callback.canSwipe()) return false;

    event.offsetLocation(translationX, 0);
    if (targetWidth < 2) targetWidth = view.getWidth();

    switch (event.getActionMasked()) {
      case MotionEvent.ACTION_DOWN:
        {
          downX = event.getRawX();

          velocityTracker = VelocityTracker.obtain();
          velocityTracker.addMovement(event);

          return false;
        }
      case MotionEvent.ACTION_UP:
        {
          if (velocityTracker == null) break;

          velocityTracker.addMovement(event);
          velocityTracker.computeCurrentVelocity(1000);

          if (swiping) {
            view.animate()
                .translationX(0f)
                .setDuration(animTime)
                .setListener(
                    new AnimatorListenerAdapter() {
                      @Override
                      public void onAnimationEnd(Animator animation) {
                        callback.onBound(canSwitch, swipingLeft);
                      }
                    });
          }

          downX = 0;
          translationX = 0;
          swiping = false;
          velocityTracker.recycle();
          velocityTracker = null;

          break;
        }
      case MotionEvent.ACTION_CANCEL:
        {
          if (velocityTracker == null) {
            break;
          }

          view.animate().translationX(0f).setDuration(animTime).setListener(null);

          downX = 0;
          translationX = 0;
          swiping = false;
          velocityTracker.recycle();
          velocityTracker = null;

          break;
        }
      case MotionEvent.ACTION_MOVE:
        {
          if (velocityTracker == null) {
            break;
          }

          velocityTracker.addMovement(event);

          float deltaX = event.getRawX() - downX;
          if (Math.abs(deltaX) > slop) {
            swiping = true;
            swipingLeft = deltaX < 0;
            canSwitch =
                Math.abs(deltaX)
                    >= ViewUnit.dp2px(
                        view.getContext(),
                        48); // Can switch tabs when deltaX >= 48 to prevent misuse
            swipingSlop = (deltaX > 0 ? slop : -slop);
            view.getParent().requestDisallowInterceptTouchEvent(true);

            MotionEvent cancelEvent = MotionEvent.obtainNoHistory(event);
            cancelEvent.setAction(
                MotionEvent.ACTION_CANCEL
                    | (event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
            view.onTouchEvent(cancelEvent);
            cancelEvent.recycle();
          }

          if (swiping) {
            translationX = deltaX;
            view.setTranslationX(deltaX - swipingSlop);
            callback.onSwipe();

            return true;
          }

          break;
        }
    }

    return false;
  }
 private void updateLastEvent(MotionEvent event) {
   if (mLastEvent != null) {
     mLastEvent.recycle();
   }
   mLastEvent = event != null ? MotionEvent.obtainNoHistory(event) : null;
 }
 private MotionEvent obtainMotionEvent(MotionEvent base, int action) {
   MotionEvent ev = MotionEvent.obtainNoHistory(base);
   ev.setAction(action);
   return ev;
 }
  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    if (mTouchInterceptionListener != null) {
      switch (ev.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
          if (mIntercepting) {
            mTouchInterceptionListener.onDownMotionEvent(ev);
            duplicateTouchEventForChildren(ev);
            return true;
          }
          break;
        case MotionEvent.ACTION_MOVE:
          // ACTION_MOVE will be passed suddenly, so initialize to avoid exception.
          if (mInitialPoint == null) {
            mInitialPoint = new PointF(ev.getX(), ev.getY());
          }

          // diffX and diffY are the origin of the motion, and should be difference
          // from the position of the ACTION_DOWN event occurred.
          float diffX = ev.getX() - mInitialPoint.x;
          float diffY = ev.getY() - mInitialPoint.y;
          mIntercepting =
              mTouchInterceptionListener.shouldInterceptTouchEvent(ev, true, diffX, diffY);
          if (mIntercepting) {
            // If this layout didn't receive ACTION_DOWN motion event,
            // we should generate ACTION_DOWN event with current position.
            if (!mBeganFromDownMotionEvent) {
              mBeganFromDownMotionEvent = true;

              MotionEvent event = MotionEvent.obtainNoHistory(mPendingDownMotionEvent);
              event.setLocation(ev.getX(), ev.getY());
              mTouchInterceptionListener.onDownMotionEvent(event);

              mInitialPoint = new PointF(ev.getX(), ev.getY());
              diffX = diffY = 0;
            }

            // Children's touches should be canceled
            if (!mChildrenEventsCanceled) {
              mChildrenEventsCanceled = true;
              duplicateTouchEventForChildren(obtainMotionEvent(ev, MotionEvent.ACTION_CANCEL));
            }

            mTouchInterceptionListener.onMoveMotionEvent(ev, diffX, diffY);

            // If next mIntercepting become false,
            // then we should generate fake ACTION_DOWN event.
            // Therefore we set pending flag to true as if this is a down motion event.
            mDownMotionEventPended = true;

            // Whether or not this event is consumed by the listener,
            // assume it consumed because we declared to intercept the event.
            return true;
          } else {
            if (mDownMotionEventPended) {
              mDownMotionEventPended = false;
              MotionEvent event = MotionEvent.obtainNoHistory(mPendingDownMotionEvent);
              event.setLocation(ev.getX(), ev.getY());
              duplicateTouchEventForChildren(ev, event);
            } else {
              duplicateTouchEventForChildren(ev);
            }

            // If next mIntercepting become true,
            // then we should generate fake ACTION_DOWN event.
            // Therefore we set beganFromDownMotionEvent flag to false
            // as if we haven't received a down motion event.
            mBeganFromDownMotionEvent = false;

            // Reserve children's click cancellation here if they've already canceled
            mChildrenEventsCanceled = false;
          }
          break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
          mBeganFromDownMotionEvent = false;
          if (mIntercepting) {
            mTouchInterceptionListener.onUpOrCancelMotionEvent(ev);
          }

          // Children's touches should be canceled regardless of
          // whether or not this layout intercepted the consecutive motion events.
          if (!mChildrenEventsCanceled) {
            mChildrenEventsCanceled = true;
            if (mDownMotionEventPended) {
              mDownMotionEventPended = false;
              MotionEvent event = MotionEvent.obtainNoHistory(mPendingDownMotionEvent);
              event.setLocation(ev.getX(), ev.getY());
              duplicateTouchEventForChildren(ev, event);
            } else {
              duplicateTouchEventForChildren(ev);
            }
          }
          return true;
      }
    }
    return super.onTouchEvent(ev);
  }