From b2087ba42c2731ba6783bbea1ae3ea6288799848 Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Tue, 22 Aug 2017 13:49:20 -0700 Subject: [PATCH] Android ScrollView: Add scrollBy method Currently, React Native allows you to scroll a ScrollView to a particular position using `scrollTo`. If instead you want to scroll by a delta, you could try to do this using `scrollTo` (add the delta to the current scroll position) but this doesn't always work out as intended due to the async nature of React Native (what you think is the current scroll position might be a stale value). This change introduces a `scrollBy` API to solve this problem. `scrollBy` takes a delta to scroll by and native takes care of doing the arithmetic because it knows the latest scroll position. --- Libraries/Components/ScrollResponder.js | 15 +++++++++++ Libraries/Components/ScrollView/ScrollView.js | 13 ++++++++++ .../ReactHorizontalScrollViewManager.java | 13 ++++++++++ .../scroll/ReactScrollViewCommandHelper.java | 26 ++++++++++++++++++- .../views/scroll/ReactScrollViewManager.java | 13 ++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index cb16e9b6e507..736404a05933 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -438,6 +438,21 @@ var ScrollResponderMixin = { ); }, + /** + * A helper function to scroll by a specific offset in the ScrollView. + * This is useful when you want to change the size of the content view and scroll + * position at the same time. Syntax: + * + * scrollResponderScrollBy(options: {deltaX: number = 0; deltaY: number = 0; animated: boolean = true}) + */ + scrollResponderScrollBy: function(options : {deltaX?: number, deltaY?: number, animated?: boolean}) { + UIManager.dispatchViewManagerCommand( + this.scrollResponderGetScrollableNode(), + UIManager.RCTScrollView.Commands.scrollBy, + [options.deltaX || 0, options.deltaY || 0, options.animated !== false], + ); + }, + /** * Deprecated, do not use. */ diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 980d5d734e0e..5beb3bbddc20 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -530,6 +530,19 @@ const ScrollView = createReactClass({ }); }, + /** + * Scrolls by a given offset, either immediately or with a smooth animation. + * + * Syntax: + * + * `scrollBy(options: {deltaX: number = 0; deltaY: number = 0; animated: boolean = true})` + */ + scrollBy: function(options: { deltaX?: number, deltaY?: number, animated?: boolean } ) { + const data = {deltaX: options.deltaX || 0, deltaY: options.deltaY || 0, + animated: options.animated !== false}; + this.getScrollResponder().scrollResponderScrollBy(data); + }, + /** * Deprecated, use `scrollTo` instead. */ 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 index 9d4075791949..51ff4fc5cfc2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java @@ -150,6 +150,19 @@ public void scrollToEnd( } } + @Override + public void scrollBy( + ReactHorizontalScrollView scrollView, + ReactScrollViewCommandHelper.ScrollByCommandData data) { + int x = scrollView.getScrollX() + data.mDeltaX; + int y = scrollView.getScrollY() + data.mDeltaY; + if (data.mAnimated) { + scrollView.smoothScrollTo(x, y); + } else { + scrollView.scrollTo(x, y); + } + } + /** * When set, fills the rest of the scrollview with a color to avoid setting a background and * creating unnecessary overdraw. 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 index 0e2df5918c15..d9152bb3d267 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewCommandHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewCommandHelper.java @@ -25,11 +25,13 @@ public class ReactScrollViewCommandHelper { public static final int COMMAND_SCROLL_TO = 1; public static final int COMMAND_SCROLL_TO_END = 2; public static final int COMMAND_FLASH_SCROLL_INDICATORS = 3; + public static final int COMMAND_SCROLL_BY = 4; public interface ScrollCommandHandler { void scrollTo(T scrollView, ScrollToCommandData data); void scrollToEnd(T scrollView, ScrollToEndCommandData data); void flashScrollIndicators(T scrollView); + void scrollBy(T scrollView, ScrollByCommandData data); } public static class ScrollToCommandData { @@ -53,6 +55,18 @@ public static class ScrollToEndCommandData { } } + public static class ScrollByCommandData { + + public final int mDeltaX, mDeltaY; + public final boolean mAnimated; + + ScrollByCommandData(int deltaX, int deltaY, boolean animated) { + mDeltaX = deltaX; + mDeltaY = deltaY; + mAnimated = animated; + } + } + public static Map getCommandsMap() { return MapBuilder.of( "scrollTo", @@ -60,7 +74,9 @@ public static Map getCommandsMap() { "scrollToEnd", COMMAND_SCROLL_TO_END, "flashScrollIndicators", - COMMAND_FLASH_SCROLL_INDICATORS); + COMMAND_FLASH_SCROLL_INDICATORS, + "scrollBy", + COMMAND_SCROLL_BY); } public static void receiveCommand( @@ -88,6 +104,14 @@ public static void receiveCommand( viewManager.flashScrollIndicators(scrollView); return; + case COMMAND_SCROLL_BY: { + int deltaX = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(0))); + int deltaY = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(1))); + boolean animated = args.getBoolean(2); + + viewManager.scrollBy(scrollView, new ScrollByCommandData(deltaX, deltaY, animated)); + return; + } default: throw new IllegalArgumentException(String.format( "Unsupported command %d received by %s.", 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 index a7d00cd52da6..2959cacd908f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java @@ -210,6 +210,19 @@ public void scrollToEnd( } } + @Override + public void scrollBy( + ReactScrollView scrollView, + ReactScrollViewCommandHelper.ScrollByCommandData data) { + int x = scrollView.getScrollX() + data.mDeltaX; + int y = scrollView.getScrollY() + data.mDeltaY; + if (data.mAnimated) { + scrollView.smoothScrollTo(x, y); + } else { + scrollView.scrollTo(x, y); + } + } + @Override public @Nullable Map getExportedCustomDirectEventTypeConstants() { return createExportedCustomDirectEventTypeConstants();