diff options
Diffstat (limited to 'src/com/android/launcher3/touch/BaseSwipeDetector.java')
-rw-r--r-- | src/com/android/launcher3/touch/BaseSwipeDetector.java | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java new file mode 100644 index 000000000..12ca5ee7b --- /dev/null +++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.touch; + +import static android.view.MotionEvent.INVALID_POINTER_ID; + +import android.graphics.PointF; +import android.util.Log; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; + +import androidx.annotation.NonNull; + +/** + * Scroll/drag/swipe gesture detector. + * + * Definition of swipe is different from android system in that this detector handles + * 'swipe to dismiss', 'swiping up/down a container' but also keeps scrolling state before + * swipe action happens. + * + * @see SingleAxisSwipeDetector + * @see BothAxesSwipeDetector + */ +public abstract class BaseSwipeDetector { + + private static final boolean DBG = false; + private static final String TAG = "BaseSwipeDetector"; + private static final float ANIMATION_DURATION = 1200; + /** The minimum release velocity in pixels per millisecond that triggers fling.*/ + private static final float RELEASE_VELOCITY_PX_MS = 1.0f; + private static final PointF sTempPoint = new PointF(); + + private final PointF mDownPos = new PointF(); + private final PointF mLastPos = new PointF(); + protected final boolean mIsRtl; + protected final float mTouchSlop; + protected final float mMaxVelocity; + + private int mActivePointerId = INVALID_POINTER_ID; + private VelocityTracker mVelocityTracker; + private PointF mLastDisplacement = new PointF(); + private PointF mDisplacement = new PointF(); + protected PointF mSubtractDisplacement = new PointF(); + private ScrollState mState = ScrollState.IDLE; + + protected boolean mIgnoreSlopWhenSettling; + + private enum ScrollState { + IDLE, + DRAGGING, // onDragStart, onDrag + SETTLING // onDragEnd + } + + protected BaseSwipeDetector(@NonNull ViewConfiguration config, boolean isRtl) { + mTouchSlop = config.getScaledTouchSlop(); + mMaxVelocity = config.getScaledMaximumFlingVelocity(); + mIsRtl = isRtl; + } + + public static long calculateDuration(float velocity, float progressNeeded) { + // TODO: make these values constants after tuning. + float velocityDivisor = Math.max(2f, Math.abs(0.5f * velocity)); + float travelDistance = Math.max(0.2f, progressNeeded); + long duration = (long) Math.max(100, ANIMATION_DURATION / velocityDivisor * travelDistance); + if (DBG) { + Log.d(TAG, String.format( + "calculateDuration=%d, v=%f, d=%f", duration, velocity, progressNeeded)); + } + return duration; + } + + public int getDownX() { + return (int) mDownPos.x; + } + + public int getDownY() { + return (int) mDownPos.y; + } + /** + * There's no touch and there's no animation. + */ + public boolean isIdleState() { + return mState == ScrollState.IDLE; + } + + public boolean isSettlingState() { + return mState == ScrollState.SETTLING; + } + + public boolean isDraggingState() { + return mState == ScrollState.DRAGGING; + } + + public boolean isDraggingOrSettling() { + return mState == ScrollState.DRAGGING || mState == ScrollState.SETTLING; + } + + public void finishedScrolling() { + setState(ScrollState.IDLE); + } + + public boolean isFling(float velocity) { + return Math.abs(velocity) > RELEASE_VELOCITY_PX_MS; + } + + public boolean onTouchEvent(MotionEvent ev) { + int actionMasked = ev.getActionMasked(); + if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) { + mVelocityTracker.clear(); + } + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: + mActivePointerId = ev.getPointerId(0); + mDownPos.set(ev.getX(), ev.getY()); + mLastPos.set(mDownPos); + mLastDisplacement.set(0, 0); + mDisplacement.set(0, 0); + + if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { + setState(ScrollState.DRAGGING); + } + break; + //case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_POINTER_UP: + int ptrIdx = ev.getActionIndex(); + int ptrId = ev.getPointerId(ptrIdx); + if (ptrId == mActivePointerId) { + final int newPointerIdx = ptrIdx == 0 ? 1 : 0; + mDownPos.set( + ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x), + ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y)); + mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx)); + mActivePointerId = ev.getPointerId(newPointerIdx); + } + break; + case MotionEvent.ACTION_MOVE: + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == INVALID_POINTER_ID) { + break; + } + mDisplacement.set(ev.getX(pointerIndex) - mDownPos.x, + ev.getY(pointerIndex) - mDownPos.y); + if (mIsRtl) { + mDisplacement.x = -mDisplacement.x; + } + + // handle state and listener calls. + if (mState != ScrollState.DRAGGING && shouldScrollStart(mDisplacement)) { + setState(ScrollState.DRAGGING); + } + if (mState == ScrollState.DRAGGING) { + reportDragging(ev); + } + mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + // These are synthetic events and there is no need to update internal values. + if (mState == ScrollState.DRAGGING) { + setState(ScrollState.SETTLING); + } + mVelocityTracker.recycle(); + mVelocityTracker = null; + break; + default: + break; + } + return true; + } + + //------------------- ScrollState transition diagram ----------------------------------- + // + // IDLE -> (mDisplacement > mTouchSlop) -> DRAGGING + // DRAGGING -> (MotionEvent#ACTION_UP, MotionEvent#ACTION_CANCEL) -> SETTLING + // SETTLING -> (MotionEvent#ACTION_DOWN) -> DRAGGING + // SETTLING -> (View settled) -> IDLE + + private void setState(ScrollState newState) { + if (DBG) { + Log.d(TAG, "setState:" + mState + "->" + newState); + } + // onDragStart and onDragEnd is reported ONLY on state transition + if (newState == ScrollState.DRAGGING) { + initializeDragging(); + if (mState == ScrollState.IDLE) { + reportDragStart(false /* recatch */); + } else if (mState == ScrollState.SETTLING) { + reportDragStart(true /* recatch */); + } + } + if (newState == ScrollState.SETTLING) { + reportDragEnd(); + } + + mState = newState; + } + + private void initializeDragging() { + if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { + mSubtractDisplacement.set(0, 0); + } else { + mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop; + mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop; + } + } + + protected abstract boolean shouldScrollStart(PointF displacement); + + private void reportDragStart(boolean recatch) { + reportDragStartInternal(recatch); + if (DBG) { + Log.d(TAG, "onDragStart recatch:" + recatch); + } + } + + protected abstract void reportDragStartInternal(boolean recatch); + + private void reportDragging(MotionEvent event) { + if (mDisplacement != mLastDisplacement) { + if (DBG) { + Log.d(TAG, String.format("onDrag disp=%s", mDisplacement)); + } + + mLastDisplacement.set(mDisplacement); + sTempPoint.set(mDisplacement.x - mSubtractDisplacement.x, + mDisplacement.y - mSubtractDisplacement.y); + reportDraggingInternal(sTempPoint, event); + } + } + + protected abstract void reportDraggingInternal(PointF displacement, MotionEvent event); + + private void reportDragEnd() { + mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); + PointF velocity = new PointF(mVelocityTracker.getXVelocity() / 1000, + mVelocityTracker.getYVelocity() / 1000); + if (mIsRtl) { + velocity.x = -velocity.x; + } + if (DBG) { + Log.d(TAG, String.format("onScrollEnd disp=%.1s, velocity=%.1s", + mDisplacement, velocity)); + } + + reportDragEndInternal(velocity); + } + + protected abstract void reportDragEndInternal(PointF velocity); +} |