diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/OnScrollDispatchHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/OnScrollDispatchHelper.java index a9078c420..26ac37435 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/OnScrollDispatchHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/OnScrollDispatchHelper.java @@ -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; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index 61e8a4d86..e9544926c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -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); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index 2a5d5e428..1336a6312 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -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); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java index 567cf9bb9..bc8151322 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java @@ -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(), diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java index 25606a489..7dc9c4feb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java @@ -32,6 +32,8 @@ public class ScrollEvent extends Event { 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 { 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 { scrollEventType, scrollX, scrollY, + xVelocity, + yVelocity, contentWidth, contentHeight, scrollViewWidth, @@ -76,6 +82,8 @@ public class ScrollEvent extends Event { 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 { 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 { 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); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/VelocityHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/VelocityHelper.java new file mode 100644 index 000000000..612daa862 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/VelocityHelper.java @@ -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; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index 8ad759a50..28f53a8dd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -836,11 +836,12 @@ public class ReactTextInputManager extends BaseViewManager