From 4d8ec15fb51258ffc4d8fc9808756e2e6ea67de0 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 12 Sep 2018 15:47:06 -0700 Subject: [PATCH] Using velocity tracker for computing the velocity of motion events Change-Id: I14f2f970825a2936f4bb285834405d67daf8667c --- .../uioverrides/TaskViewTouchController.java | 2 +- .../notification/NotificationMainView.java | 2 +- .../AbstractStateChangeTouchController.java | 2 +- .../launcher3/touch/SwipeDetector.java | 98 ++++++++----------- .../launcher3/views/AbstractSlideInView.java | 2 +- .../launcher3/touch/SwipeDetectorTest.java | 37 +++---- 6 files changed, 64 insertions(+), 79 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java index 88f2315757..54269f09fa 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java @@ -220,7 +220,7 @@ public abstract class TaskViewTouchController } @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { float totalDisplacement = displacement + mDisplacementShift; boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0; diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index 5c0e259414..78627ecc10 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -179,7 +179,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { setContentTranslation(canChildBeDismissed() ? displacement : OverScroll.dampedScroll(displacement, getWidth())); mContentTranslateAnimator.cancel(); diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index fd9157e218..85be51312c 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -256,7 +256,7 @@ public abstract class AbstractStateChangeTouchController } @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift); float progress = deltaProgress + mStartProgress; updateProgress(progress); diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java index 6ffc0efae7..a0a410e0dc 100644 --- a/src/com/android/launcher3/touch/SwipeDetector.java +++ b/src/com/android/launcher3/touch/SwipeDetector.java @@ -21,6 +21,7 @@ import android.content.Context; import android.graphics.PointF; import android.util.Log; import android.view.MotionEvent; +import android.view.VelocityTracker; import android.view.ViewConfiguration; import androidx.annotation.NonNull; @@ -52,12 +53,6 @@ public class SwipeDetector { */ public static final float RELEASE_VELOCITY_PX_MS = 1.0f; - /** - * The time constant used to calculate dampening in the low-pass filter of scroll velocity. - * Cutoff frequency is set at 10 Hz. - */ - public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10); - /* Scroll state, this is set to true during dragging and animation. */ private ScrollState mState = ScrollState.IDLE; @@ -75,6 +70,8 @@ public class SwipeDetector { * Distance in pixels a touch can wander before we think the user is scrolling. */ abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos); + + abstract float getVelocity(VelocityTracker tracker); } public static final Direction VERTICAL = new Direction() { @@ -88,6 +85,11 @@ public class SwipeDetector { float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { return Math.abs(ev.getX(pointerIndex) - downPos.x); } + + @Override + float getVelocity(VelocityTracker tracker) { + return tracker.getYVelocity(); + } }; public static final Direction HORIZONTAL = new Direction() { @@ -101,6 +103,11 @@ public class SwipeDetector { float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { return Math.abs(ev.getY(pointerIndex) - downPos.y); } + + @Override + float getVelocity(VelocityTracker tracker) { + return tracker.getXVelocity(); + } }; //------------------- ScrollState transition diagram ----------------------------------- @@ -151,16 +158,16 @@ public class SwipeDetector { private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); - private Direction mDir; + private final Direction mDir; private final float mTouchSlop; + private final float mMaxVelocity; /* Client of this gesture detector can register a callback. */ private final Listener mListener; - private long mCurrentMillis; + private VelocityTracker mVelocityTracker; - private float mVelocity; private float mLastDisplacement; private float mDisplacement; @@ -170,24 +177,22 @@ public class SwipeDetector { public interface Listener { void onDragStart(boolean start); - boolean onDrag(float displacement, float velocity); + boolean onDrag(float displacement); void onDragEnd(float velocity, boolean fling); } public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) { - this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir); + this(ViewConfiguration.get(context), l, dir); } @VisibleForTesting - protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) { - mTouchSlop = touchSlope; + protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l, + @NonNull Direction dir) { mListener = l; mDir = dir; - } - - public void updateDirection(Direction dir) { - mDir = dir; + mTouchSlop = config.getScaledTouchSlop(); + mMaxVelocity = config.getScaledMaximumFlingVelocity(); } public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) { @@ -215,14 +220,22 @@ public class SwipeDetector { } public boolean onTouchEvent(MotionEvent ev) { - switch (ev.getActionMasked()) { + 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 = 0; mDisplacement = 0; - mVelocity = 0; if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { setState(ScrollState.DRAGGING); @@ -247,8 +260,6 @@ public class SwipeDetector { break; } mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos); - computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos), - ev.getEventTime()); // handle state and listener calls. if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) { @@ -265,6 +276,8 @@ public class SwipeDetector { if (mState == ScrollState.DRAGGING) { setState(ScrollState.SETTLING); } + mVelocityTracker.recycle(); + mVelocityTracker = null; break; default: break; @@ -308,55 +321,24 @@ public class SwipeDetector { private boolean reportDragging() { if (mDisplacement != mLastDisplacement) { if (DBG) { - Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f", - mDisplacement, mVelocity)); + Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement)); } mLastDisplacement = mDisplacement; - return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity); + return mListener.onDrag(mDisplacement - mSubtractDisplacement); } return true; } private void reportDragEnd() { + mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); + float velocity = mDir.getVelocity(mVelocityTracker) / 1000; if (DBG) { Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f", - mDisplacement, mVelocity)); + mDisplacement, velocity)); } - mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS); - } - - /** - * Computes the damped velocity. - */ - public float computeVelocity(float delta, long currentMillis) { - long previousMillis = mCurrentMillis; - mCurrentMillis = currentMillis; - - float deltaTimeMillis = mCurrentMillis - previousMillis; - float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0; - if (Math.abs(mVelocity) < 0.001f) { - mVelocity = velocity; - } else { - float alpha = computeDampeningFactor(deltaTimeMillis); - mVelocity = interpolate(mVelocity, velocity, alpha); - } - return mVelocity; - } - - /** - * Returns a time-dependent dampening factor using delta time. - */ - private static float computeDampeningFactor(float deltaTime) { - return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime); - } - - /** - * Returns the linear interpolation between two values - */ - public static float interpolate(float from, float to, float alpha) { - return (1.0f - alpha) * from + alpha * to; + mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS); } public static long calculateDuration(float velocity, float progressNeeded) { diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java index 26c8f243b5..f948beb8d2 100644 --- a/src/com/android/launcher3/views/AbstractSlideInView.java +++ b/src/com/android/launcher3/views/AbstractSlideInView.java @@ -128,7 +128,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView public void onDragStart(boolean start) { } @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { float range = mContent.getHeight(); displacement = Utilities.boundToRange(displacement, 0, range); setTranslationShift(displacement / range); diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java index 73b6dafd1e..b600473d4b 100644 --- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java +++ b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java @@ -19,7 +19,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import android.util.Log; -import android.view.MotionEvent; import android.view.ViewConfiguration; import com.android.launcher3.testcomponent.TouchEventGenerator; @@ -32,6 +31,7 @@ import org.mockito.MockitoAnnotations; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyFloat; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -51,25 +51,28 @@ public class SwipeDetectorTest { @Mock private SwipeDetector.Listener mMockListener; + @Mock + private ViewConfiguration mMockConfig; + @Before public void setup() { MockitoAnnotations.initMocks(this); - mGenerator = new TouchEventGenerator(new TouchEventGenerator.Listener() { - @Override - public void onTouchEvent(MotionEvent event) { - mDetector.onTouchEvent(event); - } - }); + mGenerator = new TouchEventGenerator((ev) -> mDetector.onTouchEvent(ev)); + ViewConfiguration orgConfig = ViewConfiguration + .get(InstrumentationRegistry.getTargetContext()); + doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig) + .getScaledMaximumFlingVelocity(); - mDetector = new SwipeDetector(mTouchSlop, mMockListener, SwipeDetector.VERTICAL); + mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL); mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false); - mTouchSlop = ViewConfiguration.get(InstrumentationRegistry.getTargetContext()) - .getScaledTouchSlop(); + mTouchSlop = orgConfig.getScaledTouchSlop(); + doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop(); + L("mTouchSlop=", mTouchSlop); } @Test - public void testDragStart_vertical() throws Exception { + public void testDragStart_vertical() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); // TODO: actually calculate the following parameters and do exact value checks. @@ -77,7 +80,7 @@ public class SwipeDetectorTest { } @Test - public void testDragStart_failed() throws Exception { + public void testDragStart_failed() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100 + mTouchSlop, 100); // TODO: actually calculate the following parameters and do exact value checks. @@ -85,8 +88,8 @@ public class SwipeDetectorTest { } @Test - public void testDragStart_horizontal() throws Exception { - mDetector = new SwipeDetector(mTouchSlop, mMockListener, SwipeDetector.HORIZONTAL); + public void testDragStart_horizontal() { + mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL); mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false); mGenerator.put(0, 100, 100); @@ -96,15 +99,15 @@ public class SwipeDetectorTest { } @Test - public void testDrag() throws Exception { + public void testDrag() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener).onDrag(anyFloat(), anyFloat()); + verify(mMockListener).onDrag(anyFloat()); } @Test - public void testDragEnd() throws Exception { + public void testDragEnd() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); mGenerator.move(0, 100, 100 + mTouchSlop * 2);