Add velocity to onScrollEndDrag event

Reviewed By: achen1

Differential Revision: D5330215

fbshipit-source-id: e45a302b325c38294324f4f384a5604355dc05aa
This commit is contained in:
Wenjia Ma 2017-06-27 15:36:59 -07:00 committed by Facebook Github Bot
parent 75eb55096e
commit f954f3d9b6
7 changed files with 145 additions and 10 deletions

View File

@ -22,8 +22,13 @@ public class OnScrollDispatchHelper {
private int mPrevX = Integer.MIN_VALUE;
private int mPrevY = Integer.MIN_VALUE;
private float mXFlingVelocity = 0;
private float mYFlingVelocity = 0;
private long mLastScrollEventTimeMs = -(MIN_EVENT_SEPARATION_MS + 1);
private static final float THRESHOLD = 0.1f; // Threshold for end fling
/**
* Call from a ScrollView in onScrollChanged, returns true if this onScrollChanged is legit (not a
* duplicate) and should be dispatched.
@ -35,10 +40,28 @@ public class OnScrollDispatchHelper {
mPrevX != x ||
mPrevY != y;
// Skip the first calculation in each scroll
if (Math.abs(mXFlingVelocity) < THRESHOLD && Math.abs(mYFlingVelocity) < THRESHOLD) {
shouldDispatch = false;
}
if (eventTime - mLastScrollEventTimeMs != 0) {
mXFlingVelocity = (float) (x - mPrevX) / (eventTime - mLastScrollEventTimeMs);
mYFlingVelocity = (float) (y - mPrevY) / (eventTime - mLastScrollEventTimeMs);
}
mLastScrollEventTimeMs = eventTime;
mPrevX = x;
mPrevY = y;
return shouldDispatch;
}
public float getXFlingVelocity() {
return this.mXFlingVelocity;
}
public float getYFlingVelocity() {
return this.mYFlingVelocity;
}
}

View File

@ -37,6 +37,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
ReactClippingViewGroup {
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
private final VelocityHelper mVelocityHelper = new VelocityHelper();
private boolean mActivelyScrolling;
private @Nullable Rect mClippingRect;
@ -117,7 +118,10 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
mActivelyScrolling = true;
ReactScrollViewHelper.emitScrollEvent(this);
ReactScrollViewHelper.emitScrollEvent(
this,
mOnScrollDispatchHelper.getXFlingVelocity(),
mOnScrollDispatchHelper.getYFlingVelocity());
}
}
@ -144,14 +148,19 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
return false;
}
mVelocityHelper.calculateVelocity(ev);
int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_UP && mDragging) {
ReactScrollViewHelper.emitScrollEndDragEvent(this);
ReactScrollViewHelper.emitScrollEndDragEvent(
this,
mVelocityHelper.getXVelocity(),
mVelocityHelper.getYVelocity());
mDragging = false;
// After the touch finishes, we may need to do some scrolling afterwards either as a result
// of a fling or because we need to page align the content
handlePostTouchScrolling();
}
return super.onTouchEvent(ev);
}

View File

@ -49,6 +49,7 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
private final OverScroller mScroller;
private final VelocityHelper mVelocityHelper = new VelocityHelper();
private @Nullable Rect mClippingRect;
private boolean mDoneFlinging;
@ -164,7 +165,10 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
mDoneFlinging = false;
}
ReactScrollViewHelper.emitScrollEvent(this);
ReactScrollViewHelper.emitScrollEvent(
this,
mOnScrollDispatchHelper.getXFlingVelocity(),
mOnScrollDispatchHelper.getYFlingVelocity());
}
}
@ -191,12 +195,17 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
return false;
}
mVelocityHelper.calculateVelocity(ev);
int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_UP && mDragging) {
ReactScrollViewHelper.emitScrollEndDragEvent(this);
ReactScrollViewHelper.emitScrollEndDragEvent(
this,
mVelocityHelper.getXVelocity(),
mVelocityHelper.getYVelocity());
mDragging = false;
disableFpsListener();
}
return super.onTouchEvent(ev);
}

View File

@ -29,16 +29,19 @@ public class ReactScrollViewHelper {
/**
* Shared by {@link ReactScrollView} and {@link ReactHorizontalScrollView}.
*/
public static void emitScrollEvent(ViewGroup scrollView) {
emitScrollEvent(scrollView, ScrollEventType.SCROLL);
public static void emitScrollEvent(ViewGroup scrollView, float xVelocity, float yVelocity) {
emitScrollEvent(scrollView, ScrollEventType.SCROLL, xVelocity, yVelocity);
}
public static void emitScrollBeginDragEvent(ViewGroup scrollView) {
emitScrollEvent(scrollView, ScrollEventType.BEGIN_DRAG);
}
public static void emitScrollEndDragEvent(ViewGroup scrollView) {
emitScrollEvent(scrollView, ScrollEventType.END_DRAG);
public static void emitScrollEndDragEvent(
ViewGroup scrollView,
float xVelocity,
float yVelocity) {
emitScrollEvent(scrollView, ScrollEventType.END_DRAG, xVelocity, yVelocity);
}
public static void emitScrollMomentumBeginEvent(ViewGroup scrollView) {
@ -50,6 +53,14 @@ public class ReactScrollViewHelper {
}
private static void emitScrollEvent(ViewGroup scrollView, ScrollEventType scrollEventType) {
emitScrollEvent(scrollView, scrollEventType, 0, 0);
}
private static void emitScrollEvent(
ViewGroup scrollView,
ScrollEventType scrollEventType,
float xVelocity,
float yVelocity) {
View contentView = scrollView.getChildAt(0);
if (contentView == null) {
@ -63,6 +74,8 @@ public class ReactScrollViewHelper {
scrollEventType,
scrollView.getScrollX(),
scrollView.getScrollY(),
xVelocity,
yVelocity,
contentView.getWidth(),
contentView.getHeight(),
scrollView.getWidth(),

View File

@ -32,6 +32,8 @@ public class ScrollEvent extends Event<ScrollEvent> {
private int mScrollX;
private int mScrollY;
private double mXVelocity;
private double mYVelocity;
private int mContentWidth;
private int mContentHeight;
private int mScrollViewWidth;
@ -43,6 +45,8 @@ public class ScrollEvent extends Event<ScrollEvent> {
ScrollEventType scrollEventType,
int scrollX,
int scrollY,
float xVelocity,
float yVelocity,
int contentWidth,
int contentHeight,
int scrollViewWidth,
@ -56,6 +60,8 @@ public class ScrollEvent extends Event<ScrollEvent> {
scrollEventType,
scrollX,
scrollY,
xVelocity,
yVelocity,
contentWidth,
contentHeight,
scrollViewWidth,
@ -76,6 +82,8 @@ public class ScrollEvent extends Event<ScrollEvent> {
ScrollEventType scrollEventType,
int scrollX,
int scrollY,
float xVelocity,
float yVelocity,
int contentWidth,
int contentHeight,
int scrollViewWidth,
@ -84,6 +92,8 @@ public class ScrollEvent extends Event<ScrollEvent> {
mScrollEventType = scrollEventType;
mScrollX = scrollX;
mScrollY = scrollY;
mXVelocity = xVelocity;
mYVelocity = yVelocity;
mContentWidth = contentWidth;
mContentHeight = contentHeight;
mScrollViewWidth = scrollViewWidth;
@ -134,11 +144,16 @@ public class ScrollEvent extends Event<ScrollEvent> {
layoutMeasurement.putDouble("width", PixelUtil.toDIPFromPixel(mScrollViewWidth));
layoutMeasurement.putDouble("height", PixelUtil.toDIPFromPixel(mScrollViewHeight));
WritableMap velocity = Arguments.createMap();
velocity.putDouble("x", mXVelocity);
velocity.putDouble("y", mYVelocity);
WritableMap event = Arguments.createMap();
event.putMap("contentInset", contentInset);
event.putMap("contentOffset", contentOffset);
event.putMap("contentSize", contentSize);
event.putMap("layoutMeasurement", layoutMeasurement);
event.putMap("velocity", velocity);
event.putInt("target", getViewTag());
event.putBoolean("responderIgnoreScroll", true);

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.views.scroll;
import javax.annotation.Nullable;
import android.view.MotionEvent;
import android.view.VelocityTracker;
/**
* This Class helps to calculate the velocity for all ScrollView. The x and y velocity
* will later on send to ReactScrollViewHelper for further use.
*
*/
public class VelocityHelper {
private @Nullable VelocityTracker mVelocityTracker;
private float mXVelocity;
private float mYVelocity;
/**
* Call from a ScrollView in onTouchEvent.
* Calculating the velocity for END_DRAG movement and send them back to react ScrollResponder.js
* */
public void calculateVelocity(MotionEvent ev) {
int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
switch (action) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
// Calculate velocity on END_DRAG
mVelocityTracker.computeCurrentVelocity(1); // points/millisecond
mXVelocity = mVelocityTracker.getXVelocity();
mYVelocity = mVelocityTracker.getYVelocity();
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
}
}
/* Needs to call ACTION_UP/CANCEL to update the mXVelocity */
public float getXVelocity() {
return mXVelocity;
}
/* Needs to call ACTION_UP/CANCEL to update the mYVelocity */
public float getYVelocity() {
return mYVelocity;
}
}

View File

@ -836,11 +836,12 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
ScrollEventType.SCROLL,
horiz,
vert,
0f, // can't get x velocity
0f, // can't get y velocity
0, // can't get content width
0, // can't get content height
mReactEditText.getWidth(),
mReactEditText.getHeight()
);
mReactEditText.getHeight());
mEventDispatcher.dispatchEvent(event);