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 f544aafdb66db6..d2df684515c4a2 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 @@ -262,7 +262,7 @@ public boolean onTouchEvent(MotionEvent ev) { @Override public void fling(int velocityX) { if (mPagingEnabled) { - smoothScrollAndSnap(velocityX); + flingAndSnap(velocityX); } else if (mScroller != null) { // FB SCROLLVIEW CHANGE @@ -440,7 +440,7 @@ public void run() { // 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; - smoothScrollAndSnap(0); + flingAndSnap(0); ViewCompat.postOnAnimationDelayed(ReactHorizontalScrollView.this, this, ReactScrollViewHelper.MOMENTUM_DELAY); @@ -459,21 +459,7 @@ public void run() { ReactScrollViewHelper.MOMENTUM_DELAY); } - /** - * This will smooth scroll us to the nearest snap offset point - * It currently just looks at where the content is and slides to the nearest point. - * It is intended to be run after we are done scrolling, and handling any momentum scrolling. - */ - private void smoothScrollAndSnap(int velocityX) { - if (getChildCount() <= 0) { - return; - } - - int maximumOffset = Math.max(0, computeHorizontalScrollRange() - getWidth()); - int targetOffset = 0; - int smallerOffset = 0; - int largerOffset = maximumOffset; - + private int predictFinalScrollPosition(int velocityX) { // ScrollView can *only* scroll for 250ms when using smoothScrollTo and there's // no way to customize the scroll duration. So, we create a temporary OverScroller // so we can predict where a fling would land and snap to nearby that point. @@ -481,6 +467,7 @@ private void smoothScrollAndSnap(int velocityX) { scroller.setFriction(1.0f - mDecelerationRate); // predict where a fling would end up so we can scroll to the nearest snap offset + int maximumOffset = Math.max(0, computeHorizontalScrollRange() - getWidth()); int width = getWidth() - getPaddingStart() - getPaddingEnd(); scroller.fling( getScrollX(), // startX @@ -494,7 +481,76 @@ private void smoothScrollAndSnap(int velocityX) { width/2, // overX 0 // overY ); - targetOffset = scroller.getFinalX(); + return scroller.getFinalX(); + } + + /** + * This will smooth scroll us to the nearest snap offset point + * It currently just looks at where the content is and slides to the nearest point. + * It is intended to be run after we are done scrolling, and handling any momentum scrolling. + */ + private void smoothScrollAndSnap(int velocity) { + double interval = (double) getSnapInterval(); + double currentOffset = (double) getScrollX(); + double targetOffset = (double) predictFinalScrollPosition(velocity); + + int previousPage = (int) Math.floor(currentOffset / interval); + int nextPage = (int) Math.ceil(currentOffset / interval); + int currentPage = (int) Math.round(currentOffset / interval); + int targetPage = (int) Math.round(targetOffset / interval); + + if (velocity > 0 && nextPage == previousPage) { + nextPage ++; + } else if (velocity < 0 && previousPage == nextPage) { + previousPage --; + } + + if ( + // if scrolling towards next page + velocity > 0 && + // and the middle of the page hasn't been crossed already + currentPage < nextPage && + // and it would have been crossed after flinging + targetPage > previousPage + ) { + currentPage = nextPage; + } + else if ( + // if scrolling towards previous page + velocity < 0 && + // and the middle of the page hasn't been crossed already + currentPage > previousPage && + // and it would have been crossed after flinging + targetPage < nextPage + ) { + currentPage = previousPage; + } + + targetOffset = currentPage * interval; + if (targetOffset != currentOffset) { + mActivelyScrolling = true; + smoothScrollTo((int) targetOffset, getScrollY()); + } + } + + private void flingAndSnap(int velocityX) { + if (getChildCount() <= 0) { + return; + } + + // pagingEnabled only allows snapping one interval at a time + if (mSnapInterval == 0 && mSnapOffsets == null) { + smoothScrollAndSnap(velocityX); + return; + } + + int maximumOffset = Math.max(0, computeHorizontalScrollRange() - getWidth()); + int targetOffset = predictFinalScrollPosition(velocityX); + int smallerOffset = 0; + int largerOffset = maximumOffset; + int firstOffset = 0; + int lastOffset = maximumOffset; + int width = getWidth() - getPaddingStart() - getPaddingEnd(); // offsets are from the right edge in RTL layouts boolean isRTL = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL; 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 9df87e57e7404f..6b3536983776dd 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 @@ -293,7 +293,7 @@ public void getClippingRect(Rect outClippingRect) { @Override public void fling(int velocityY) { if (mPagingEnabled) { - smoothScrollAndSnap(velocityY); + flingAndSnap(velocityY); } else if (mScroller != null) { // FB SCROLLVIEW CHANGE @@ -408,7 +408,7 @@ public void run() { // 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; - smoothScrollAndSnap(0); + flingAndSnap(0); ViewCompat.postOnAnimationDelayed(ReactScrollView.this, this, ReactScrollViewHelper.MOMENTUM_DELAY); @@ -427,21 +427,7 @@ public void run() { ReactScrollViewHelper.MOMENTUM_DELAY); } - /** - * This will smooth scroll us to the nearest snap offset point - * It currently just looks at where the content is and slides to the nearest point. - * It is intended to be run after we are done scrolling, and handling any momentum scrolling. - */ - private void smoothScrollAndSnap(int velocityY) { - if (getChildCount() <= 0) { - return; - } - - int maximumOffset = getMaxScrollY(); - int targetOffset = 0; - int smallerOffset = 0; - int largerOffset = maximumOffset; - + private int predictFinalScrollPosition(int velocityY) { // ScrollView can *only* scroll for 250ms when using smoothScrollTo and there's // no way to customize the scroll duration. So, we create a temporary OverScroller // so we can predict where a fling would land and snap to nearby that point. @@ -449,6 +435,7 @@ private void smoothScrollAndSnap(int velocityY) { scroller.setFriction(1.0f - mDecelerationRate); // predict where a fling would end up so we can scroll to the nearest snap offset + int maximumOffset = getMaxScrollY(); int height = getHeight() - getPaddingBottom() - getPaddingTop(); scroller.fling( getScrollX(), // startX @@ -462,7 +449,76 @@ private void smoothScrollAndSnap(int velocityY) { 0, // overX height/2 // overY ); - targetOffset = scroller.getFinalY(); + return scroller.getFinalY(); + } + + /** + * This will smooth scroll us to the nearest snap offset point + * It currently just looks at where the content is and slides to the nearest point. + * It is intended to be run after we are done scrolling, and handling any momentum scrolling. + */ + private void smoothScrollAndSnap(int velocity) { + double interval = (double) getSnapInterval(); + double currentOffset = (double) getScrollY(); + double targetOffset = (double) predictFinalScrollPosition(velocity); + + int previousPage = (int) Math.floor(currentOffset / interval); + int nextPage = (int) Math.ceil(currentOffset / interval); + int currentPage = (int) Math.round(currentOffset / interval); + int targetPage = (int) Math.round(targetOffset / interval); + + if (velocity > 0 && nextPage == previousPage) { + nextPage ++; + } else if (velocity < 0 && previousPage == nextPage) { + previousPage --; + } + + if ( + // if scrolling towards next page + velocity > 0 && + // and the middle of the page hasn't been crossed already + currentPage < nextPage && + // and it would have been crossed after flinging + targetPage > previousPage + ) { + currentPage = nextPage; + } + else if ( + // if scrolling towards previous page + velocity < 0 && + // and the middle of the page hasn't been crossed already + currentPage > previousPage && + // and it would have been crossed after flinging + targetPage < nextPage + ) { + currentPage = previousPage; + } + + targetOffset = currentPage * interval; + if (targetOffset != currentOffset) { + mActivelyScrolling = true; + smoothScrollTo(getScrollX(), (int) targetOffset); + } + } + + private void flingAndSnap(int velocityY) { + if (getChildCount() <= 0) { + return; + } + + // pagingEnabled only allows snapping one interval at a time + if (mSnapInterval == 0 && mSnapOffsets == null) { + smoothScrollAndSnap(velocityY); + return; + } + + int maximumOffset = getMaxScrollY(); + int targetOffset = predictFinalScrollPosition(velocityY); + int smallerOffset = 0; + int largerOffset = maximumOffset; + int firstOffset = 0; + int lastOffset = maximumOffset; + int height = getHeight() - getPaddingBottom() - getPaddingTop(); // get the nearest snap points to the target offset if (mSnapOffsets != null) {