From e774c037bce40a4b48e78d2d0a1085a1e4f5a328 Mon Sep 17 00:00:00 2001 From: David Vacca Date: Wed, 29 Sep 2021 01:46:07 -0700 Subject: [PATCH] Implement snapToAlignment in vertical ScrollView Summary: This diff implements the SnapToAlignment functionality in ReactScrollView for RN Android. In order to use SnapToAlignment, the pagingEnabled prop should be set Based on the documentation the behavior implemented in this diff is more "advanced" than the one implemendted in RNiOS, because it let you snap without specifying any interval nor offset (it calculates the intervals in real time based on the size of its content) I still need to verify how different RNiOS and RN Android behaviors are changelog: [Android][Added] Implement SnapToAlignment in ReactScrollView Reviewed By: JoshuaGross Differential Revision: D31182786 fbshipit-source-id: a9b55d9c00326ae21ca9b89575e79c60bf9edcca --- .../scroll/ReactHorizontalScrollView.java | 2 +- .../react/views/scroll/ReactScrollView.java | 40 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) 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 5d52faf18130de..7f6e173f328c1e 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 @@ -975,7 +975,7 @@ private void flingAndSnap(int velocityX) { itemStartOffset = item.getLeft() - (width - item.getWidth()); break; default: - throw new IllegalStateException(""); + throw new IllegalStateException("Invalid SnapToAlignment value: " + mSnapToAlignment); } if (itemStartOffset <= targetOffset) { if (targetOffset - itemStartOffset < targetOffset - smallerOffset) { 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 38ba923d613262..06d928544b577b 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 @@ -7,7 +7,10 @@ package com.facebook.react.views.scroll; +import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNMENT_CENTER; import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNMENT_DISABLED; +import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNMENT_END; +import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNMENT_START; import android.animation.Animator; import android.animation.ObjectAnimator; @@ -641,6 +644,10 @@ private int predictFinalScrollPosition(int velocityY) { return scroller.getFinalY(); } + private View getContentView() { + return getChildAt(0); + } + /** * 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 @@ -697,7 +704,7 @@ private void flingAndSnap(int velocityY) { } // pagingEnabled only allows snapping one interval at a time - if (mSnapInterval == 0 && mSnapOffsets == null) { + if (mSnapInterval == 0 && mSnapOffsets == null && mSnapToAlignment == SNAP_ALIGNMENT_DISABLED) { smoothScrollAndSnap(velocityY); return; } @@ -734,6 +741,37 @@ private void flingAndSnap(int velocityY) { } } } + + } else if (mSnapToAlignment != SNAP_ALIGNMENT_DISABLED) { + ViewGroup contentView = (ViewGroup) getContentView(); + for (int i = 1; i < contentView.getChildCount(); i++) { + View item = contentView.getChildAt(i); + int itemStartOffset; + switch (mSnapToAlignment) { + case SNAP_ALIGNMENT_CENTER: + itemStartOffset = item.getTop() - (height - item.getHeight()) / 2; + break; + case SNAP_ALIGNMENT_START: + itemStartOffset = item.getTop(); + break; + case SNAP_ALIGNMENT_END: + itemStartOffset = item.getTop() - (height - item.getHeight()); + break; + default: + throw new IllegalStateException("Invalid SnapToAlignment value: " + mSnapToAlignment); + } + if (itemStartOffset <= targetOffset) { + if (targetOffset - itemStartOffset < targetOffset - smallerOffset) { + smallerOffset = itemStartOffset; + } + } + + if (itemStartOffset >= targetOffset) { + if (itemStartOffset - targetOffset < largerOffset - targetOffset) { + largerOffset = itemStartOffset; + } + } + } } else { double interval = (double) getSnapInterval(); double ratio = (double) targetOffset / interval;