Using velocity tracker for computing the velocity of motion events

Change-Id: I14f2f970825a2936f4bb285834405d67daf8667c
This commit is contained in:
Sunny Goyal
2018-09-12 15:47:06 -07:00
parent 05b351dd42
commit 4d8ec15fb5
6 changed files with 64 additions and 79 deletions
@@ -220,7 +220,7 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
}
@Override
public boolean onDrag(float displacement, float velocity) {
public boolean onDrag(float displacement) {
float totalDisplacement = displacement + mDisplacementShift;
boolean isGoingUp =
totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
@@ -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();
@@ -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);
@@ -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) {
@@ -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);
@@ -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);