Using velocity tracker for computing the velocity of motion events
Change-Id: I14f2f970825a2936f4bb285834405d67daf8667c
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user