/** * 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); }
@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); }