From 04184ef851c71141009c523ba59838ae6af19ba5 Mon Sep 17 00:00:00 2001 From: David Vacca Date: Wed, 29 Sep 2021 01:46:07 -0700 Subject: [PATCH] Extend ScrollView.snapToAlignments in RN Android to reach feature parity with RN iOS Summary: This diff extends the current implementation of ScrollView.snapToAlignments from RN Android to reach feature parity with RNiOS changelog: [Android][Changed] Implement ScrollView.snapToAlignments in RN Android Reviewed By: javache Differential Revision: D31206398 fbshipit-source-id: b6534965c476a0a4745ac98b419cbe05dc5c746e --- .../scroll/ReactHorizontalScrollView.java | 77 ++++++++++------ .../react/views/scroll/ReactScrollView.java | 87 ++++++++++++++----- 2 files changed, 115 insertions(+), 49 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 7f6e173f328c1e..69c2a633a429eb 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 @@ -960,37 +960,45 @@ private void flingAndSnap(int velocityX) { } } } 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.getLeft() - (width - item.getWidth()) / 2; - break; - case SNAP_ALIGNMENT_START: - itemStartOffset = item.getLeft(); - break; - case SNAP_ALIGNMENT_END: - itemStartOffset = item.getLeft() - (width - item.getWidth()); - break; - default: - throw new IllegalStateException("Invalid SnapToAlignment value: " + mSnapToAlignment); - } - if (itemStartOffset <= targetOffset) { - if (targetOffset - itemStartOffset < targetOffset - smallerOffset) { - smallerOffset = itemStartOffset; + if (mSnapInterval > 0) { + double ratio = (double) targetOffset / mSnapInterval; + smallerOffset = + Math.max( + getItemStartOffset( + mSnapToAlignment, + (int) (Math.floor(ratio) * mSnapInterval), + mSnapInterval, + width), + 0); + largerOffset = + Math.min( + getItemStartOffset( + mSnapToAlignment, + (int) (Math.ceil(ratio) * mSnapInterval), + mSnapInterval, + width), + maximumOffset); + } else { + ViewGroup contentView = (ViewGroup) getContentView(); + for (int i = 1; i < contentView.getChildCount(); i++) { + View item = contentView.getChildAt(i); + int itemStartOffset = + getItemStartOffset(mSnapToAlignment, item.getLeft(), item.getWidth(), width); + if (itemStartOffset <= targetOffset) { + if (targetOffset - itemStartOffset < targetOffset - smallerOffset) { + smallerOffset = itemStartOffset; + } } - } - if (itemStartOffset >= targetOffset) { - if (itemStartOffset - targetOffset < largerOffset - targetOffset) { - largerOffset = itemStartOffset; + if (itemStartOffset >= targetOffset) { + if (itemStartOffset - targetOffset < largerOffset - targetOffset) { + largerOffset = itemStartOffset; + } } } } } else { - double interval = (double) getSnapInterval(); + double interval = getSnapInterval(); double ratio = (double) targetOffset / interval; smallerOffset = (int) (Math.floor(ratio) * interval); largerOffset = Math.min((int) (Math.ceil(ratio) * interval), maximumOffset); @@ -1073,6 +1081,25 @@ private void flingAndSnap(int velocityX) { } } + private int getItemStartOffset( + int snapToAlignment, int itemStartPosition, int itemWidth, int viewPortWidth) { + int itemStartOffset; + switch (snapToAlignment) { + case SNAP_ALIGNMENT_CENTER: + itemStartOffset = itemStartPosition - (viewPortWidth - itemWidth) / 2; + break; + case SNAP_ALIGNMENT_START: + itemStartOffset = itemStartPosition; + break; + case SNAP_ALIGNMENT_END: + itemStartOffset = itemStartPosition - (viewPortWidth - itemWidth); + break; + default: + throw new IllegalStateException("Invalid SnapToAlignment value: " + mSnapToAlignment); + } + return itemStartOffset; + } + private void smoothScrollToNextPage(int direction) { if (DEBUG_MODE) { FLog.i(TAG, "smoothScrollToNextPage[%d] direction %d", getId(), direction); 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 06d928544b577b..bb9dc676499240 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 @@ -743,32 +743,52 @@ 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 (mSnapInterval > 0) { + double ratio = (double) targetOffset / mSnapInterval; + smallerOffset = + Math.max( + getItemStartOffset( + mSnapToAlignment, + (int) (Math.floor(ratio) * mSnapInterval), + mSnapInterval, + height), + 0); + largerOffset = + Math.min( + getItemStartOffset( + mSnapToAlignment, + (int) (Math.ceil(ratio) * mSnapInterval), + mSnapInterval, + height), + maximumOffset); + } else { + 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; + if (itemStartOffset >= targetOffset) { + if (itemStartOffset - targetOffset < largerOffset - targetOffset) { + largerOffset = itemStartOffset; + } } } } @@ -847,6 +867,25 @@ private void flingAndSnap(int velocityY) { } } + private int getItemStartOffset( + int snapToAlignment, int itemStartPosition, int itemHeight, int viewPortHeight) { + int itemStartOffset; + switch (snapToAlignment) { + case SNAP_ALIGNMENT_CENTER: + itemStartOffset = itemStartPosition - (viewPortHeight - itemHeight) / 2; + break; + case SNAP_ALIGNMENT_START: + itemStartOffset = itemStartPosition; + break; + case SNAP_ALIGNMENT_END: + itemStartOffset = itemStartPosition - (viewPortHeight - itemHeight); + break; + default: + throw new IllegalStateException("Invalid SnapToAlignment value: " + mSnapToAlignment); + } + return itemStartOffset; + } + private int getSnapInterval() { if (mSnapInterval != 0) { return mSnapInterval;