diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK old mode 100644 new mode 100755 diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/FpsListener.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/FpsListener.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 index c0014311f9551c..be6a16564fce83 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/OnScrollDispatchHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/OnScrollDispatchHelper.java @@ -24,6 +24,7 @@ public class OnScrollDispatchHelper { private int mPrevY = Integer.MIN_VALUE; private float mXFlingVelocity = 0; private float mYFlingVelocity = 0; + private static final float THRESHOLD = 0.1f; // Threshold for end fling private long mLastScrollEventTimeMs = -(MIN_EVENT_SEPARATION_MS + 1); @@ -38,6 +39,11 @@ public boolean onScrollChanged(int x, int y) { 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); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollContainerView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollContainerView.java old mode 100644 new mode 100755 diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollContainerViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollContainerViewManager.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 index b64415bfadbce6..9192c34dffb30c --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -49,6 +49,11 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements private @Nullable Drawable mEndBackground; private int mEndFillColor = Color.TRANSPARENT; private ReactViewBackgroundManager mReactBackgroundManager; + private int mSnapInterval = 0; + + public void setSnapInterval(int snapInterval) { + mSnapInterval = snapInterval; + } public ReactHorizontalScrollView(Context context) { this(context, null); @@ -164,6 +169,7 @@ public boolean onTouchEvent(MotionEvent ev) { } return super.onTouchEvent(ev); + } @Override @@ -312,7 +318,7 @@ public void run() { * scrolling. */ private void smoothScrollToPage(int velocity) { - int width = getWidth(); + int width = mSnapInterval != 0 ? mSnapInterval : getWidth(); int currentX = getScrollX(); // TODO (t11123799) - Should we do anything beyond linear accounting of the velocity int predictedX = currentX + velocity; diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java old mode 100644 new mode 100755 index 9d40757919493c..3b6d87bb9067fe --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java @@ -22,6 +22,8 @@ import com.facebook.react.uimanager.annotations.ReactPropGroup; import com.facebook.yoga.YogaConstants; import javax.annotation.Nullable; +import com.facebook.react.uimanager.DisplayMetricsHolder; +import android.util.DisplayMetrics; /** * View manager for {@link ReactHorizontalScrollView} components. @@ -112,6 +114,12 @@ public void setPagingEnabled(ReactHorizontalScrollView view, boolean pagingEnabl public void setOverScrollMode(ReactHorizontalScrollView view, String value) { view.setOverScrollMode(ReactScrollViewHelper.parseOverScrollMode(value)); } + @ReactProp(name = "snapToInterval") + public void setSnapToInterval(ReactHorizontalScrollView view, int snapToInterval) { + view.setPagingEnabled(snapToInterval > 0); + DisplayMetrics screenDisplayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics(); + view.setSnapInterval((int)(snapToInterval * screenDisplayMetrics.density)); + } @Override public void receiveCommand( 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 old mode 100644 new mode 100755 index cafa7bff0e93bd..0cb36241d28f71 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -30,6 +30,10 @@ import com.facebook.react.views.view.ReactViewBackgroundManager; import java.lang.reflect.Field; import javax.annotation.Nullable; +import android.annotation.TargetApi; +import android.graphics.drawable.LayerDrawable; +import android.view.ViewGroup; +import com.facebook.react.views.view.ReactViewBackgroundDrawable; /** * A simple subclass of ScrollView that doesn't dispatch measure and layout to its children and has @@ -46,8 +50,9 @@ 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 Runnable mPostTouchRunnable; private @Nullable Rect mClippingRect; + private boolean mActivelyScrolling; private boolean mDoneFlinging; private boolean mDragging; private boolean mFlinging; @@ -61,6 +66,24 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou private View mContentView; private ReactViewBackgroundManager mReactBackgroundManager; + private boolean mPagingEnabled = false; + private int mSnapInterval = 0; // add this line + + public void setPagingEnabled(boolean pagingEnabled) { + mPagingEnabled = pagingEnabled; + } + + public void setSnapInterval(int snapInterval) { + mSnapInterval = snapInterval; + } + + private int getSnapInterval() { + if (mSnapInterval != 0) { + return mSnapInterval; + } + return getHeight(); + } + public ReactScrollView(ReactContext context) { this(context, null); } @@ -131,6 +154,17 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { MeasureSpec.getSize(heightMeasureSpec)); } + private void smoothScrollToPage(int velocity) { + int height = getSnapInterval(); + int currentY = getScrollY(); + // TODO (t11123799) - Should we do anything beyond linear accounting of the velocity + int predictedY = currentY + velocity; + int page = currentY / height; + if (predictedY > page * height + height / 2) { + page = page + 1; + } + smoothScrollTo(getScrollX(), page * height); + } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // Call with the present values in order to re-layout if necessary @@ -246,54 +280,58 @@ public void getClippingRect(Rect outClippingRect) { @Override public void fling(int velocityY) { - if (mScroller != null) { - // FB SCROLLVIEW CHANGE - - // We provide our own version of fling that uses a different call to the standard OverScroller - // which takes into account the possibility of adding new content while the ScrollView is - // animating. Because we give essentially no max Y for the fling, the fling will continue as long - // as there is content. See #onOverScrolled() to see the second part of this change which properly - // aborts the scroller animation when we get to the bottom of the ScrollView content. - - int scrollWindowHeight = getHeight() - getPaddingBottom() - getPaddingTop(); - - mScroller.fling( - getScrollX(), - getScrollY(), - 0, - velocityY, - 0, - 0, - 0, - Integer.MAX_VALUE, - 0, - scrollWindowHeight / 2); - - postInvalidateOnAnimation(); - - // END FB SCROLLVIEW CHANGE + if (mPagingEnabled || mSnapInterval != 0) { + smoothScrollToPage(velocityY); } else { - super.fling(velocityY); - } + if (mScroller != null) { + // FB SCROLLVIEW CHANGE + + // We provide our own version of fling that uses a different call to the standard OverScroller + // which takes into account the possibility of adding new content while the ScrollView is + // animating. Because we give essentially no max Y for the fling, the fling will continue as long + // as there is content. See #onOverScrolled() to see the second part of this change which properly + // aborts the scroller animation when we get to the bottom of the ScrollView content. + + int scrollWindowHeight = getHeight() - getPaddingBottom() - getPaddingTop(); + + mScroller.fling( + getScrollX(), + getScrollY(), + 0, + velocityY, + 0, + 0, + 0, + Integer.MAX_VALUE, + 0, + scrollWindowHeight / 2); + + postInvalidateOnAnimation(); + + // END FB SCROLLVIEW CHANGE + } else { + super.fling(velocityY); + } - if (mSendMomentumEvents || isScrollPerfLoggingEnabled()) { - mFlinging = true; - enableFpsListener(); - ReactScrollViewHelper.emitScrollMomentumBeginEvent(this); - Runnable r = new Runnable() { - @Override - public void run() { - if (mDoneFlinging) { - mFlinging = false; - disableFpsListener(); - ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this); - } else { - mDoneFlinging = true; - ReactScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY); + if (mSendMomentumEvents || isScrollPerfLoggingEnabled()) { + mFlinging = true; + enableFpsListener(); + ReactScrollViewHelper.emitScrollMomentumBeginEvent(this); + Runnable r = new Runnable() { + @Override + public void run() { + if (mDoneFlinging) { + mFlinging = false; + disableFpsListener(); + ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this); + } else { + mDoneFlinging = true; + ReactScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY); + } } - } - }; - postOnAnimationDelayed(r, ReactScrollViewHelper.MOMENTUM_DELAY); + }; + postOnAnimationDelayed(r, ReactScrollViewHelper.MOMENTUM_DELAY); + } } } @@ -419,4 +457,57 @@ public void setBorderStyle(@Nullable String style) { mReactBackgroundManager.setBorderStyle(style); } + @TargetApi(16) + private void handlePostTouchScrolling() { + // If we aren't going to do anything (send events or snap to page), we can early out. + if (!mSendMomentumEvents && !mPagingEnabled && !isScrollPerfLoggingEnabled()) { + return; + } + + // Check if we are already handling this which may occur if this is called by both the touch up + // and a fling call + if (mPostTouchRunnable != null) { + return; + } + + if (mSendMomentumEvents) { + ReactScrollViewHelper.emitScrollMomentumBeginEvent(this); + } + + mActivelyScrolling = false; + mPostTouchRunnable = new Runnable() { + + private boolean mSnappingToPage = false; + + @Override + public void run() { + if (mActivelyScrolling) { + // We are still scrolling so we just post to check again a frame later + mActivelyScrolling = false; + ReactScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY); + } else { + boolean doneWithAllScrolling = true; + if (mPagingEnabled && !mSnappingToPage) { + // Only if we have pagingEnabled and we have not snapped to the page do we + // need to continue checking for the scroll. And we cause that scroll by asking for it + mSnappingToPage = true; + smoothScrollToPage(0); + doneWithAllScrolling = false; + } + if (doneWithAllScrolling) { + if (mSendMomentumEvents) { + ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this); + } + ReactScrollView.this.mPostTouchRunnable = null; + disableFpsListener(); + } else { + ReactScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY); + } + } + } + + }; + postOnAnimationDelayed(mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY); + } + } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewCommandHelper.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewCommandHelper.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java old mode 100644 new mode 100755 index a7d00cd52da60a..ad3e3b309a8aaa --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java @@ -24,6 +24,8 @@ import com.facebook.yoga.YogaConstants; import java.util.Map; import javax.annotation.Nullable; +import com.facebook.react.uimanager.DisplayMetricsHolder; +import android.util.DisplayMetrics; /** * View manager for {@link ReactScrollView} components. @@ -62,6 +64,13 @@ public ReactScrollView createViewInstance(ThemedReactContext context) { return new ReactScrollView(context, mFpsListener); } + + @ReactProp(name = "snapToInterval") + public void setSnapToInterval(ReactScrollView view, int snapToInterval) { + DisplayMetrics screenDisplayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics(); + view.setSnapInterval((int)(snapToInterval * screenDisplayMetrics.density)); + } + @ReactProp(name = "scrollEnabled", defaultBoolean = true) public void setScrollEnabled(ReactScrollView view, boolean value) { view.setScrollEnabled(value); 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 old mode 100644 new mode 100755 diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEventType.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEventType.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 diff --git a/android.zip b/android.zip new file mode 100644 index 00000000000000..1ad34a0b0f6e87 Binary files /dev/null and b/android.zip differ diff --git a/node_modules.zip b/node_modules.zip new file mode 100644 index 00000000000000..1eb6a79700814b Binary files /dev/null and b/node_modules.zip differ