From 64e5e7c2a8c962e532481a85b14a67e0f8d3eaa0 Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Wed, 3 Jan 2018 14:21:53 -0800 Subject: [PATCH 01/18] cancel animations on touch; cancel previous animations --- Libraries/Components/ScrollResponder.js | 23 +++++---- Libraries/Components/ScrollView/ScrollView.js | 22 ++++++--- .../ReactHorizontalScrollViewManager.java | 18 +++++-- .../scroll/ReactScrollViewCommandHelper.java | 47 ++++++++++++++----- .../views/scroll/ReactScrollViewHelper.java | 28 +++++++++++ .../views/scroll/ReactScrollViewManager.java | 18 +++++-- 6 files changed, 122 insertions(+), 34 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index ef145a0d313a56..4958e8fe702bb2 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -411,46 +411,53 @@ var ScrollResponderMixin = { * This is currently used to help focus child TextViews, but can also * be used to quickly scroll to any element we want to focus. Syntax: * - * `scrollResponderScrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})` + * `scrollResponderScrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true, duration: number = 0})` * * Note: The weird argument signature is due to the fact that, for historical reasons, * the function also accepts separate arguments as as alternative to the options object. * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED. + * + * Also note "duration" is currently only supported for Android. */ scrollResponderScrollTo: function( - x?: number | { x?: number, y?: number, animated?: boolean }, + x?: number | { x?: number, y?: number, animated?: boolean, duration?: number }, y?: number, - animated?: boolean + animated?: boolean, + duration?: number ) { if (typeof x === 'number') { console.warn('`scrollResponderScrollTo(x, y, animated)` is deprecated. Use `scrollResponderScrollTo({x: 5, y: 5, animated: true})` instead.'); } else { - ({x, y, animated} = x || {}); + ({x, y, animated, duration} = x || {}); } UIManager.dispatchViewManagerCommand( nullthrows(this.scrollResponderGetScrollableNode()), UIManager.RCTScrollView.Commands.scrollTo, - [x || 0, y || 0, animated !== false], + [x || 0, y || 0, animated !== false, duration || 0], ); }, /** * Scrolls to the end of the ScrollView, either immediately or with a smooth - * animation. + * animation. For Android, you may specify a "duration" number instead of the + * "animated" boolean. * * Example: * * `scrollResponderScrollToEnd({animated: true})` + * or for Android, you can do: + * `scrollResponderScrollToEnd({duration: 500})` */ scrollResponderScrollToEnd: function( - options?: { animated?: boolean }, + options?: { animated?: boolean, duration?: boolean }, ) { // Default to true const animated = (options && options.animated) !== false; + const duration = options ? options.duration || 0 : 0; UIManager.dispatchViewManagerCommand( this.scrollResponderGetScrollableNode(), UIManager.RCTScrollView.Commands.scrollToEnd, - [animated], + [animated, duration], ); }, diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 74026c7e649663..ed673dbade8a86 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -489,29 +489,33 @@ const ScrollView = createReactClass({ }, /** - * Scrolls to a given x, y offset, either immediately or with a smooth animation. + * Scrolls to a given x, y offset, either immediately, with a smooth animation, or, + * for Android only, a custom animation duration time. * * Example: * - * `scrollTo({x: 0, y: 0, animated: true})` + * `scrollTo({x: 0, y: 0, animated: true, duration: 0})` * * Note: The weird function signature is due to the fact that, for historical reasons, * the function also accepts separate arguments as an alternative to the options object. * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED. + * + * Also note "duration" is currently only supported for Android. */ scrollTo: function( - y?: number | { x?: number, y?: number, animated?: boolean }, + y?: number | { x?: number, y?: number, animated?: boolean, duration?: number }, x?: number, - animated?: boolean + animated?: boolean, + duration?: number ) { if (typeof y === 'number') { console.warn('`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, ' + 'animated: true})` instead.'); } else { - ({x, y, animated} = y || {}); + ({x, y, animated, duration} = y || {}); } this.getScrollResponder().scrollResponderScrollTo( - {x: x || 0, y: y || 0, animated: animated !== false} + {x: x || 0, y: y || 0, animated: animated !== false, duration: duration || 0} ); }, @@ -521,15 +525,19 @@ const ScrollView = createReactClass({ * * Use `scrollToEnd({animated: true})` for smooth animated scrolling, * `scrollToEnd({animated: false})` for immediate scrolling. + * For Android, you may specify a duration, e.g. `scrollToEnd({duration: 500})` + * for a controlled duration scroll. * If no options are passed, `animated` defaults to true. */ scrollToEnd: function( - options?: { animated?: boolean }, + options?: { animated?: boolean, duration?: number }, ) { // Default to true const animated = (options && options.animated) !== false; + const duration = options ? options.duration || 0 : 0; this.getScrollResponder().scrollResponderScrollToEnd({ animated: animated, + duration: duration }); }, 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 9d40757919493c..9ab944860408d7 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 @@ -9,7 +9,9 @@ package com.facebook.react.views.scroll; +import android.animation.ObjectAnimator; import android.graphics.Color; + import com.facebook.react.bridge.ReadableArray; import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.uimanager.PixelUtil; @@ -41,6 +43,7 @@ public class ReactHorizontalScrollViewManager }; private @Nullable FpsListener mFpsListener = null; + private @Nullable ObjectAnimator mAnimator = null; public ReactHorizontalScrollViewManager() { this(null); @@ -129,8 +132,8 @@ public void flashScrollIndicators(ReactHorizontalScrollView scrollView) { @Override public void scrollTo( ReactHorizontalScrollView scrollView, ReactScrollViewCommandHelper.ScrollToCommandData data) { - if (data.mAnimated) { - scrollView.smoothScrollTo(data.mDestX, data.mDestY); + if (data.mDuration > 0) { + animateScroll(scrollView, data.mDestX, data.mDestY, data.mDuration); } else { scrollView.scrollTo(data.mDestX, data.mDestY); } @@ -143,8 +146,8 @@ public void scrollToEnd( // ScrollView always has one child - the scrollable area int right = scrollView.getChildAt(0).getWidth() + scrollView.getPaddingRight(); - if (data.mAnimated) { - scrollView.smoothScrollTo(right, scrollView.getScrollY()); + if (data.mDuration > 0) { + animateScroll(scrollView, right, scrollView.getScrollY(), data.mDuration); } else { scrollView.scrollTo(right, scrollView.getScrollY()); } @@ -208,4 +211,11 @@ public void setBorderColor(ReactHorizontalScrollView view, int index, Integer co float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int)color >>> 24); view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent); } + + private void animateScroll(ReactHorizontalScrollView view, int mDestX, int mDestY, int mDuration) { + if (mAnimator != null) { + mAnimator.cancel(); + } + mAnimator = ReactScrollViewHelper.animateScroll(view, mDestX, mDestY, mDuration); + } } 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 0e2df5918c15d8..0301fd1cca5f43 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 @@ -26,6 +26,15 @@ public class ReactScrollViewCommandHelper { public static final int COMMAND_SCROLL_TO_END = 2; public static final int COMMAND_FLASH_SCROLL_INDICATORS = 3; + /** + * Prior to users being able to specify a duration when calling "scrollTo", + * they could specify an "animate" boolean, which would use Android's + * "smoothScrollTo" method, which defaulted to a 250 millisecond + * animation: + * https://developer.android.com/reference/android/widget/Scroller.html#startScroll + */ + public static final int LEGACY_ANIMATION_DURATION = 250; + public interface ScrollCommandHandler { void scrollTo(T scrollView, ScrollToCommandData data); void scrollToEnd(T scrollView, ScrollToEndCommandData data); @@ -34,22 +43,21 @@ public interface ScrollCommandHandler { public static class ScrollToCommandData { - public final int mDestX, mDestY; - public final boolean mAnimated; + public final int mDestX, mDestY, mDuration; - ScrollToCommandData(int destX, int destY, boolean animated) { + ScrollToCommandData(int destX, int destY, int duration) { mDestX = destX; mDestY = destY; - mAnimated = animated; + mDuration = duration; } } public static class ScrollToEndCommandData { - public final boolean mAnimated; + public final int mDuration; - ScrollToEndCommandData(boolean animated) { - mAnimated = animated; + ScrollToEndCommandData(int duration) { + mDuration = duration; } } @@ -75,13 +83,30 @@ public static void receiveCommand( case COMMAND_SCROLL_TO: { int destX = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(0))); int destY = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(1))); - boolean animated = args.getBoolean(2); - viewManager.scrollTo(scrollView, new ScrollToCommandData(destX, destY, animated)); + + // TODO(dannycochran) If the duration argument is specified and non-zero, use it, + // otherwise use the legacy "animated" boolean. Eventually this can be removed + // in favor of just looking at "duration". + int duration = 0; + if (args.size() == 4 && args.getDouble(3) > 0) { + duration = (int) Math.round(args.getDouble(3)); + } else if (args.getBoolean(2)) { + duration = LEGACY_ANIMATION_DURATION; + } + viewManager.scrollTo(scrollView, new ScrollToCommandData(destX, destY, duration)); return; } case COMMAND_SCROLL_TO_END: { - boolean animated = args.getBoolean(0); - viewManager.scrollToEnd(scrollView, new ScrollToEndCommandData(animated)); + // TODO(dannycochran) If the duration argument is specified and non-zero, use it, + // otherwise use the legacy "animated" boolean. Eventually this can be removed + // in favor of just looking at "duration". + int duration = 0; + if (args.size() == 2 && args.getDouble(1) > 0) { + duration = (int) Math.round(args.getDouble(1)); + } else if (args.getBoolean(0)) { + duration = LEGACY_ANIMATION_DURATION; + } + viewManager.scrollToEnd(scrollView, new ScrollToEndCommandData(duration)); return; } case COMMAND_FLASH_SCROLL_INDICATORS: 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 index bc8151322ee145..a334990ce86fe3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java @@ -9,6 +9,10 @@ package com.facebook.react.views.scroll; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; + +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -93,4 +97,28 @@ public static int parseOverScrollMode(String jsOverScrollMode) { throw new JSApplicationIllegalArgumentException("wrong overScrollMode: " + jsOverScrollMode); } } + + /** + * Helper method for animating to a ScrollView position with a given duration, + * instead of using "smoothScrollTo", which does not expose a duration argument. + */ + public static ObjectAnimator animateScroll(final ViewGroup scrollView, int mDestX, int mDestY, int mDuration) { + PropertyValuesHolder scrollX = PropertyValuesHolder.ofInt("scrollX", mDestX); + PropertyValuesHolder scrollY = PropertyValuesHolder.ofInt("scrollY", mDestY); + + final ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(scrollView, scrollX, scrollY); + + // Cancel the animation if a user interacts with the ScrollView. + scrollView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + scrollView.setOnTouchListener(null); + animator.cancel(); + return false; + } + }); + + animator.setDuration(mDuration).start(); + return animator; + } } 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 a7d00cd52da60a..f531aae5708883 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 @@ -9,7 +9,9 @@ package com.facebook.react.views.scroll; +import android.animation.ObjectAnimator; import android.graphics.Color; + import com.facebook.react.bridge.ReadableArray; import com.facebook.react.common.MapBuilder; import com.facebook.react.module.annotations.ReactModule; @@ -43,6 +45,7 @@ public class ReactScrollViewManager }; private @Nullable FpsListener mFpsListener = null; + private @Nullable ObjectAnimator mAnimator = null; public ReactScrollViewManager() { this(null); @@ -142,8 +145,8 @@ public void flashScrollIndicators(ReactScrollView scrollView) { @Override public void scrollTo( ReactScrollView scrollView, ReactScrollViewCommandHelper.ScrollToCommandData data) { - if (data.mAnimated) { - scrollView.smoothScrollTo(data.mDestX, data.mDestY); + if (data.mDuration > 0) { + animateScroll(scrollView, data.mDestX, data.mDestY, data.mDuration); } else { scrollView.scrollTo(data.mDestX, data.mDestY); } @@ -203,8 +206,8 @@ public void scrollToEnd( // ScrollView always has one child - the scrollable area int bottom = scrollView.getChildAt(0).getHeight() + scrollView.getPaddingBottom(); - if (data.mAnimated) { - scrollView.smoothScrollTo(scrollView.getScrollX(), bottom); + if (data.mDuration > 0) { + animateScroll(scrollView, scrollView.getScrollX(), bottom, data.mDuration); } else { scrollView.scrollTo(scrollView.getScrollX(), bottom); } @@ -224,4 +227,11 @@ public static Map createExportedCustomDirectEventTypeConstants() { .put(ScrollEventType.MOMENTUM_END.getJSEventName(), MapBuilder.of("registrationName", "onMomentumScrollEnd")) .build(); } + + private void animateScroll(ReactScrollView view, int mDestX, int mDestY, int mDuration) { + if (mAnimator != null) { + mAnimator.cancel(); + } + mAnimator = ReactScrollViewHelper.animateScroll(view, mDestX, mDestY, mDuration); + } } From f153d4faa104d9a3b886ec48d090b91cd09ac80d Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Fri, 2 Feb 2018 19:08:21 -0800 Subject: [PATCH 02/18] handle unused duration argument --- React/Views/ScrollView/RCTScrollViewManager.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/React/Views/ScrollView/RCTScrollViewManager.m b/React/Views/ScrollView/RCTScrollViewManager.m index 3c7d11405a74dd..fec27342bb9b69 100644 --- a/React/Views/ScrollView/RCTScrollViewManager.m +++ b/React/Views/ScrollView/RCTScrollViewManager.m @@ -147,7 +147,9 @@ - (UIView *)view RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag offsetX:(CGFloat)x offsetY:(CGFloat)y - animated:(BOOL)animated) + animated:(BOOL)animated + // TODO(dannycochran) Use the duration here for a ScrollView. + duration:(CGFloat __unused)duration) { [self.bridge.uiManager addUIBlock: ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ @@ -162,7 +164,9 @@ - (UIView *)view } RCT_EXPORT_METHOD(scrollToEnd:(nonnull NSNumber *)reactTag - animated:(BOOL)animated) + animated:(BOOL)animated + // TODO(dannycochran) Use the duration here for a ScrollView. + duration:(CGFloat __unused)duration) { [self.bridge.uiManager addUIBlock: ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ From 25b5ec66bc65cb37adec8e1dd8cfbdc08abe843d Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Fri, 9 Feb 2018 09:29:07 -0800 Subject: [PATCH 03/18] use animated boolean --- .../react/views/scroll/ReactScrollViewCommandHelper.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 0301fd1cca5f43..c4473a121caec8 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 @@ -90,8 +90,8 @@ public static void receiveCommand( int duration = 0; if (args.size() == 4 && args.getDouble(3) > 0) { duration = (int) Math.round(args.getDouble(3)); - } else if (args.getBoolean(2)) { - duration = LEGACY_ANIMATION_DURATION; + } else { + duration = args.getBoolean(2) ? LEGACY_ANIMATION_DURATION : 0; } viewManager.scrollTo(scrollView, new ScrollToCommandData(destX, destY, duration)); return; @@ -103,8 +103,8 @@ public static void receiveCommand( int duration = 0; if (args.size() == 2 && args.getDouble(1) > 0) { duration = (int) Math.round(args.getDouble(1)); - } else if (args.getBoolean(0)) { - duration = LEGACY_ANIMATION_DURATION; + } else { + duration = args.getBoolean(2) ? LEGACY_ANIMATION_DURATION : 0; } viewManager.scrollToEnd(scrollView, new ScrollToEndCommandData(duration)); return; From 5399cb4793e1ed7bc4dc8e931b2eac42405ba260 Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Mon, 12 Mar 2018 10:20:11 -0700 Subject: [PATCH 04/18] duration can be undefined --- Libraries/Components/ScrollResponder.js | 5 ++--- Libraries/Components/ScrollView/ScrollView.js | 12 +++++++----- .../scroll/ReactScrollViewCommandHelper.java | 18 ++++++++++-------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 9d74b622ead98d..e998b27c91dc0f 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -427,7 +427,7 @@ const ScrollResponderMixin = { UIManager.dispatchViewManagerCommand( nullthrows(this.scrollResponderGetScrollableNode()), UIManager.RCTScrollView.Commands.scrollTo, - [x || 0, y || 0, animated !== false, duration || 0], + [x || 0, y || 0, animated !== false, duration], ); }, @@ -447,11 +447,10 @@ const ScrollResponderMixin = { ) { // Default to true const animated = (options && options.animated) !== false; - const duration = options ? options.duration || 0 : 0; UIManager.dispatchViewManagerCommand( this.scrollResponderGetScrollableNode(), UIManager.RCTScrollView.Commands.scrollToEnd, - [animated, duration], + [animated, options.duration], ); }, diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 89916b1d08721d..bba1ac51d253b3 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -531,13 +531,16 @@ const ScrollView = createReactClass({ * * Example: * - * `scrollTo({x: 0, y: 0, animated: true, duration: 0})` + * `scrollTo({x: 0, y: 0, animated: true})` + * + * Example with duration (Android only): + * + * `scrollTo({x: 0, y: 0, duration: 500})` * * Note: The weird function signature is due to the fact that, for historical reasons, * the function also accepts separate arguments as an alternative to the options object. * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED. * - * Also note "duration" is currently only supported for Android. */ scrollTo: function( y?: number | { x?: number, y?: number, animated?: boolean, duration?: number }, @@ -552,7 +555,7 @@ const ScrollView = createReactClass({ ({x, y, animated, duration} = y || {}); } this.getScrollResponder().scrollResponderScrollTo( - {x: x || 0, y: y || 0, animated: animated !== false, duration: duration || 0} + {x: x || 0, y: y || 0, animated: animated !== false, duration: duration} ); }, @@ -571,10 +574,9 @@ const ScrollView = createReactClass({ ) { // Default to true const animated = (options && options.animated) !== false; - const duration = options ? options.duration || 0 : 0; this.getScrollResponder().scrollResponderScrollToEnd({ animated: animated, - duration: duration + duration: options.duration }); }, 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 c4473a121caec8..0e1d9d739d17d9 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 @@ -84,11 +84,12 @@ public static void receiveCommand( int destX = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(0))); int destY = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(1))); - // TODO(dannycochran) If the duration argument is specified and non-zero, use it, - // otherwise use the legacy "animated" boolean. Eventually this can be removed - // in favor of just looking at "duration". + // Defer to the "duration" argument to determine if we should animate the + // scrollTo, otherwise use the legacy "animated" boolean. + // TODO(dannycochran) Eventually this can be removed in favor of just + // looking at "duration" once support also exists on iOS. int duration = 0; - if (args.size() == 4 && args.getDouble(3) > 0) { + if (args.size() == 4) { duration = (int) Math.round(args.getDouble(3)); } else { duration = args.getBoolean(2) ? LEGACY_ANIMATION_DURATION : 0; @@ -97,11 +98,12 @@ public static void receiveCommand( return; } case COMMAND_SCROLL_TO_END: { - // TODO(dannycochran) If the duration argument is specified and non-zero, use it, - // otherwise use the legacy "animated" boolean. Eventually this can be removed - // in favor of just looking at "duration". + // Defer to the "duration" argument to determine if we should animate the + // scrollTo, otherwise use the legacy "animated" boolean. + // TODO(dannycochran) Eventually this can be removed in favor of just + // looking at "duration" once support also exists on iOS. int duration = 0; - if (args.size() == 2 && args.getDouble(1) > 0) { + if (args.size() == 2) { duration = (int) Math.round(args.getDouble(1)); } else { duration = args.getBoolean(2) ? LEGACY_ANIMATION_DURATION : 0; From 4bf0dcedb3aeda66f002c92f3fabd3c012a3c21a Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Mon, 12 Mar 2018 11:04:17 -0700 Subject: [PATCH 05/18] use isNull --- .../react/views/scroll/ReactScrollViewCommandHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 0e1d9d739d17d9..f22fa6490bdbd7 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 @@ -89,7 +89,7 @@ public static void receiveCommand( // TODO(dannycochran) Eventually this can be removed in favor of just // looking at "duration" once support also exists on iOS. int duration = 0; - if (args.size() == 4) { + if (!args.isNull(3)) { duration = (int) Math.round(args.getDouble(3)); } else { duration = args.getBoolean(2) ? LEGACY_ANIMATION_DURATION : 0; @@ -103,7 +103,7 @@ public static void receiveCommand( // TODO(dannycochran) Eventually this can be removed in favor of just // looking at "duration" once support also exists on iOS. int duration = 0; - if (args.size() == 2) { + if (!args.isNull(3)) { duration = (int) Math.round(args.getDouble(1)); } else { duration = args.getBoolean(2) ? LEGACY_ANIMATION_DURATION : 0; From 7d37961eddf12e3829ddd777e242e5e614b12549 Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Mon, 12 Mar 2018 11:06:20 -0700 Subject: [PATCH 06/18] fix scrollToEnd --- .../react/views/scroll/ReactScrollViewCommandHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f22fa6490bdbd7..9811e7ed67581e 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 @@ -103,7 +103,7 @@ public static void receiveCommand( // TODO(dannycochran) Eventually this can be removed in favor of just // looking at "duration" once support also exists on iOS. int duration = 0; - if (!args.isNull(3)) { + if (!args.isNull(1)) { duration = (int) Math.round(args.getDouble(1)); } else { duration = args.getBoolean(2) ? LEGACY_ANIMATION_DURATION : 0; From 2896cf1c34858edb1882ca591c1312fa75f9aae3 Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Mon, 12 Mar 2018 13:26:11 -0700 Subject: [PATCH 07/18] use -1 instead of 0 --- Libraries/Components/ScrollResponder.js | 12 ++++++++++-- .../views/scroll/ReactScrollViewCommandHelper.java | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index e998b27c91dc0f..4a47cda6887fb9 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -125,6 +125,14 @@ function isTagInstanceOfTextInput(tag) { ); } +/** + * If a user has specified a duration, we will use it. Otherwise, + * set it to -1 as the bridge cannot handle undefined / null values. + */ +function getDuration(duration?: number): number { + return duration === undefined ? -1 : Math.max(duration, 0); +} + const ScrollResponderMixin = { mixins: [Subscribable.Mixin], scrollResponderMixinGetInitialState: function(): State { @@ -427,7 +435,7 @@ const ScrollResponderMixin = { UIManager.dispatchViewManagerCommand( nullthrows(this.scrollResponderGetScrollableNode()), UIManager.RCTScrollView.Commands.scrollTo, - [x || 0, y || 0, animated !== false, duration], + [x || 0, y || 0, animated !== false, getDuration(duration)], ); }, @@ -450,7 +458,7 @@ const ScrollResponderMixin = { UIManager.dispatchViewManagerCommand( this.scrollResponderGetScrollableNode(), UIManager.RCTScrollView.Commands.scrollToEnd, - [animated, options.duration], + [animated, getDuration(options.duration)], ); }, 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 9811e7ed67581e..e3ea30e8b1e31b 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 @@ -89,7 +89,7 @@ public static void receiveCommand( // TODO(dannycochran) Eventually this can be removed in favor of just // looking at "duration" once support also exists on iOS. int duration = 0; - if (!args.isNull(3)) { + if (args.size() == 4 && args.getDouble(3) >= 0) { duration = (int) Math.round(args.getDouble(3)); } else { duration = args.getBoolean(2) ? LEGACY_ANIMATION_DURATION : 0; @@ -103,10 +103,10 @@ public static void receiveCommand( // TODO(dannycochran) Eventually this can be removed in favor of just // looking at "duration" once support also exists on iOS. int duration = 0; - if (!args.isNull(1)) { + if (args.size() == 2 && args.getDouble(1) >= 0) { duration = (int) Math.round(args.getDouble(1)); } else { - duration = args.getBoolean(2) ? LEGACY_ANIMATION_DURATION : 0; + duration = args.getBoolean(0) ? LEGACY_ANIMATION_DURATION : 0; } viewManager.scrollToEnd(scrollView, new ScrollToEndCommandData(duration)); return; From 8dcc2e992087b4a4507ef8bc09bc438ec3c25416 Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Wed, 21 Mar 2018 16:38:21 -0700 Subject: [PATCH 08/18] fix flow errors --- Libraries/Components/ScrollResponder.js | 4 ++-- Libraries/Components/ScrollView/ScrollView.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 4a47cda6887fb9..24fec694cd00a4 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -451,14 +451,14 @@ const ScrollResponderMixin = { * `scrollResponderScrollToEnd({duration: 500})` */ scrollResponderScrollToEnd: function( - options?: { animated?: boolean, duration?: boolean }, + options?: { animated?: boolean, duration?: number }, ) { // Default to true const animated = (options && options.animated) !== false; UIManager.dispatchViewManagerCommand( this.scrollResponderGetScrollableNode(), UIManager.RCTScrollView.Commands.scrollToEnd, - [animated, getDuration(options.duration)], + [animated, getDuration(options && options.duration)], ); }, diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index bba1ac51d253b3..f0727dda16f0ef 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -576,7 +576,7 @@ const ScrollView = createReactClass({ const animated = (options && options.animated) !== false; this.getScrollResponder().scrollResponderScrollToEnd({ animated: animated, - duration: options.duration + duration: options && options.duration }); }, From 2f54dfc9d537afddaaa707b06807888aab879489 Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Fri, 23 Mar 2018 09:25:33 -0700 Subject: [PATCH 09/18] merge conflicts --- .../facebook/react/views/scroll/ReactScrollViewManager.java | 3 --- 1 file changed, 3 deletions(-) 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 b75dd7d975194d..f0bfe79f5ab18f 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 @@ -10,10 +10,7 @@ import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.graphics.Color; -<<<<<<< HEAD -======= import android.support.v4.view.ViewCompat; ->>>>>>> master import com.facebook.react.bridge.ReadableArray; import com.facebook.react.common.MapBuilder; From 6161dd879973481c6680a91a019ae6e21f8b51ce Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Fri, 23 Mar 2018 10:11:09 -0700 Subject: [PATCH 10/18] use scrollView onTouchEvent to cancel animator --- .../views/scroll/ReactHorizontalScrollView.java | 14 ++++++++++++++ .../scroll/ReactHorizontalScrollViewManager.java | 13 ++----------- .../react/views/scroll/ReactScrollView.java | 14 ++++++++++++++ .../react/views/scroll/ReactScrollViewHelper.java | 10 ---------- .../react/views/scroll/ReactScrollViewManager.java | 13 ++----------- 5 files changed, 32 insertions(+), 32 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 90b9d1fc4d3a9c..9e530aaff8f020 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 @@ -7,6 +7,7 @@ package com.facebook.react.views.scroll; +import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Canvas; @@ -40,6 +41,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements private final VelocityHelper mVelocityHelper = new VelocityHelper(); private boolean mActivelyScrolling; + private @Nullable ObjectAnimator mAnimator = null; private @Nullable Rect mClippingRect; private boolean mDragging; private boolean mPagingEnabled = false; @@ -102,6 +104,13 @@ public void flashScrollIndicators() { awakenScrollBars(); } + public void animateScroll(ReactHorizontalScrollView view, int mDestX, int mDestY, int mDuration) { + if (mAnimator != null) { + mAnimator.cancel(); + } + mAnimator = ReactScrollViewHelper.animateScroll(view, mDestX, mDestY, mDuration); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec); @@ -165,6 +174,11 @@ public boolean onTouchEvent(MotionEvent ev) { return false; } + if (mAnimator != null) { + mAnimator.cancel(); + mAnimator = null; + } + mVelocityHelper.calculateVelocity(ev); int action = ev.getAction() & MotionEvent.ACTION_MASK; if (action == MotionEvent.ACTION_UP && mDragging) { 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 9b611b14383309..70e33db8674c07 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 @@ -7,7 +7,6 @@ package com.facebook.react.views.scroll; -import android.animation.ObjectAnimator; import android.graphics.Color; import android.support.v4.view.ViewCompat; import android.util.DisplayMetrics; @@ -45,7 +44,6 @@ public class ReactHorizontalScrollViewManager }; private @Nullable FpsListener mFpsListener = null; - private @Nullable ObjectAnimator mAnimator = null; public ReactHorizontalScrollViewManager() { this(null); @@ -147,7 +145,7 @@ public void flashScrollIndicators(ReactHorizontalScrollView scrollView) { public void scrollTo( ReactHorizontalScrollView scrollView, ReactScrollViewCommandHelper.ScrollToCommandData data) { if (data.mDuration > 0) { - animateScroll(scrollView, data.mDestX, data.mDestY, data.mDuration); + scrollView.animateScroll(scrollView, data.mDestX, data.mDestY, data.mDuration); } else { scrollView.scrollTo(data.mDestX, data.mDestY); } @@ -161,7 +159,7 @@ public void scrollToEnd( int right = scrollView.getChildAt(0).getWidth() + scrollView.getPaddingRight(); if (data.mDuration > 0) { - animateScroll(scrollView, right, scrollView.getScrollY(), data.mDuration); + scrollView.animateScroll(scrollView, right, scrollView.getScrollY(), data.mDuration); } else { scrollView.scrollTo(right, scrollView.getScrollY()); } @@ -225,11 +223,4 @@ public void setBorderColor(ReactHorizontalScrollView view, int index, Integer co float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int)color >>> 24); view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent); } - - private void animateScroll(ReactHorizontalScrollView view, int mDestX, int mDestY, int mDuration) { - if (mAnimator != null) { - mAnimator.cancel(); - } - mAnimator = ReactScrollViewHelper.animateScroll(view, mDestX, mDestY, mDuration); - } } 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 acf3458ef5f023..96d8801ad0d75b 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,6 +7,7 @@ package com.facebook.react.views.scroll; +import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.graphics.Canvas; import android.graphics.Color; @@ -48,6 +49,7 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou private final @Nullable OverScroller mScroller; private final VelocityHelper mVelocityHelper = new VelocityHelper(); + private @Nullable ObjectAnimator mAnimator = null; private @Nullable Rect mClippingRect; private boolean mDoneFlinging; private boolean mDragging; @@ -131,6 +133,13 @@ public void flashScrollIndicators() { awakenScrollBars(); } + public void animateScroll(ReactScrollView view, int mDestX, int mDestY, int mDuration) { + if (mAnimator != null) { + mAnimator.cancel(); + } + mAnimator = ReactScrollViewHelper.animateScroll(view, mDestX, mDestY, mDuration); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec); @@ -212,6 +221,11 @@ public boolean onTouchEvent(MotionEvent ev) { return false; } + if (mAnimator != null) { + mAnimator.cancel(); + mAnimator = null; + } + mVelocityHelper.calculateVelocity(ev); int action = ev.getAction() & MotionEvent.ACTION_MASK; if (action == MotionEvent.ACTION_UP && mDragging) { 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 index a4c5be1cdc60b8..c12ac3a362ae25 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java @@ -109,16 +109,6 @@ public static ObjectAnimator animateScroll(final ViewGroup scrollView, int mDest final ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(scrollView, scrollX, scrollY); - // Cancel the animation if a user interacts with the ScrollView. - scrollView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - scrollView.setOnTouchListener(null); - animator.cancel(); - return false; - } - }); - animator.setDuration(mDuration).start(); return animator; } 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 f0bfe79f5ab18f..53f2153f4a44a9 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 @@ -7,7 +7,6 @@ package com.facebook.react.views.scroll; -import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.graphics.Color; import android.support.v4.view.ViewCompat; @@ -47,7 +46,6 @@ public class ReactScrollViewManager }; private @Nullable FpsListener mFpsListener = null; - private @Nullable ObjectAnimator mAnimator = null; public ReactScrollViewManager() { this(null); @@ -153,7 +151,7 @@ public void flashScrollIndicators(ReactScrollView scrollView) { public void scrollTo( ReactScrollView scrollView, ReactScrollViewCommandHelper.ScrollToCommandData data) { if (data.mDuration > 0) { - animateScroll(scrollView, data.mDestX, data.mDestY, data.mDuration); + scrollView.animateScroll(scrollView, data.mDestX, data.mDestY, data.mDuration); } else { scrollView.scrollTo(data.mDestX, data.mDestY); } @@ -214,7 +212,7 @@ public void scrollToEnd( int bottom = scrollView.getChildAt(0).getHeight() + scrollView.getPaddingBottom(); if (data.mDuration > 0) { - animateScroll(scrollView, scrollView.getScrollX(), bottom, data.mDuration); + scrollView.animateScroll(scrollView, scrollView.getScrollX(), bottom, data.mDuration); } else { scrollView.scrollTo(scrollView.getScrollX(), bottom); } @@ -234,11 +232,4 @@ public static Map createExportedCustomDirectEventTypeConstants() .put(ScrollEventType.MOMENTUM_END.getJSEventName(), MapBuilder.of("registrationName", "onMomentumScrollEnd")) .build(); } - - private void animateScroll(ReactScrollView view, int mDestX, int mDestY, int mDuration) { - if (mAnimator != null) { - mAnimator.cancel(); - } - mAnimator = ReactScrollViewHelper.animateScroll(view, mDestX, mDestY, mDuration); - } } From 1eb2cf6d0923d5ca4c952d1896f582ba007fb17a Mon Sep 17 00:00:00 2001 From: Daniel Cochran Date: Fri, 23 Mar 2018 10:26:30 -0700 Subject: [PATCH 11/18] remove MotionEvent import --- .../com/facebook/react/views/scroll/ReactScrollViewHelper.java | 1 - 1 file changed, 1 deletion(-) 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 index c12ac3a362ae25..c5b8af65ceac91 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java @@ -10,7 +10,6 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; From 8eda882b0d45658bdfa73afc919db787c12cce49 Mon Sep 17 00:00:00 2001 From: osdnk Date: Sun, 6 Jan 2019 15:48:17 +0100 Subject: [PATCH 12/18] Fix lint --- Libraries/Components/ScrollResponder.js | 13 ++++++++----- Libraries/Components/ScrollView/ScrollView.js | 6 ++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index b00bc1c171568b..5fe6212bc5fab5 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -441,10 +441,12 @@ const ScrollResponderMixin = { * Also note "duration" is currently only supported for Android. */ scrollResponderScrollTo: function( - x?: number | { x?: number, y?: number, animated?: boolean, duration?: number }, + x?: + | number + | {x?: number, y?: number, animated?: boolean, duration?: number}, y?: number, animated?: boolean, - duration?: number + duration?: number, ) { if (typeof x === 'number') { console.warn( @@ -471,9 +473,10 @@ const ScrollResponderMixin = { * or for Android, you can do: * `scrollResponderScrollToEnd({duration: 500})` */ - scrollResponderScrollToEnd: function( - options?: { animated?: boolean, duration?: number }, - ) { + scrollResponderScrollToEnd: function(options?: { + animated?: boolean, + duration?: number, + }) { // Default to true const animated = (options && options.animated) !== false; UIManager.dispatchViewManagerCommand( diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 82da2f234ab10f..22e2938df4841b 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -702,7 +702,9 @@ class ScrollView extends React.Component { * */ scrollTo( - y?: number | {x?: number, y?: number, animated?: boolean, duration?: number}, + y?: + | number + | {x?: number, y?: number, animated?: boolean, duration?: number}, x?: number, animated?: boolean, duration?: number, @@ -738,7 +740,7 @@ class ScrollView extends React.Component { const animated = (options && options.animated) !== false; this._scrollResponder.scrollResponderScrollToEnd({ animated: animated, - duration: options && options.duration + duration: options && options.duration, }); } From 42bdb1d7dd072c4a9c598080f2c17c8be61a62ed Mon Sep 17 00:00:00 2001 From: osdnk Date: Wed, 16 Jan 2019 01:52:07 +0100 Subject: [PATCH 13/18] Revert default animated scrollview behavior --- .../scroll/ReactHorizontalScrollView.java | 12 ++++- .../ReactHorizontalScrollViewManager.java | 16 +++++-- .../react/views/scroll/ReactScrollView.java | 12 ++++- .../scroll/ReactScrollViewCommandHelper.java | 45 +++++-------------- .../views/scroll/ReactScrollViewHelper.java | 14 ------ .../views/scroll/ReactScrollViewManager.java | 16 +++++-- 6 files changed, 56 insertions(+), 59 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 221cd0826cbbbe..4c7dad31977a93 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 @@ -8,6 +8,7 @@ package com.facebook.react.views.scroll; import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Canvas; @@ -185,11 +186,18 @@ public void flashScrollIndicators() { awakenScrollBars(); } - public void animateScroll(ReactHorizontalScrollView view, int mDestX, int mDestY, int mDuration) { + /** + * Method for animating to a ScrollView position with a given duration, + * instead of using "smoothScrollTo", which does not expose a duration argument. + */ + public void animateScroll(int mDestX, int mDestY, int mDuration) { if (mAnimator != null) { mAnimator.cancel(); } - mAnimator = ReactScrollViewHelper.animateScroll(view, mDestX, mDestY, mDuration); + PropertyValuesHolder scrollX = PropertyValuesHolder.ofInt("scrollX", mDestX); + PropertyValuesHolder scrollY = PropertyValuesHolder.ofInt("scrollY", mDestY); + mAnimator = ObjectAnimator.ofPropertyValuesHolder(this, scrollX, scrollY); + mAnimator.setDuration(mDuration).start(); } public void setOverflow(String overflow) { 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 262a958e4cbc0b..e865507332b906 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 @@ -171,8 +171,12 @@ public void flashScrollIndicators(ReactHorizontalScrollView scrollView) { @Override public void scrollTo( ReactHorizontalScrollView scrollView, ReactScrollViewCommandHelper.ScrollToCommandData data) { - if (data.mDuration > 0) { - scrollView.animateScroll(scrollView, data.mDestX, data.mDestY, data.mDuration); + if (data.mAnimated && data.mDuration !=0) { + if (data.mDuration > 0) { + scrollView.animateScroll(data.mDestX, data.mDestY, data.mDuration); + } else { + scrollView.smoothScrollTo(data.mDestX, data.mDestY); + } } else { scrollView.scrollTo(data.mDestX, data.mDestY); } @@ -185,8 +189,12 @@ public void scrollToEnd( // ScrollView always has one child - the scrollable area int right = scrollView.getChildAt(0).getWidth() + scrollView.getPaddingRight(); - if (data.mDuration > 0) { - scrollView.animateScroll(scrollView, right, scrollView.getScrollY(), data.mDuration); + if (data.mAnimated && data.mDuration !=0) { + if (data.mDuration > 0) { + scrollView.animateScroll(right, scrollView.getScrollY(), data.mDuration); + } else { + scrollView.smoothScrollTo(right, scrollView.getScrollY()); + } } else { scrollView.scrollTo(right, scrollView.getScrollY()); } 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 07db2c8971f589..74769e5e6a2103 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 @@ -8,6 +8,7 @@ package com.facebook.react.views.scroll; import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.annotation.TargetApi; import android.graphics.Canvas; import android.graphics.Color; @@ -173,11 +174,18 @@ public void flashScrollIndicators() { awakenScrollBars(); } - public void animateScroll(ReactScrollView view, int mDestX, int mDestY, int mDuration) { + /** + * Method for animating to a ScrollView position with a given duration, + * instead of using "smoothScrollTo", which does not expose a duration argument. + */ + public void animateScroll(int mDestX, int mDestY, int mDuration) { if (mAnimator != null) { mAnimator.cancel(); } - mAnimator = ReactScrollViewHelper.animateScroll(view, mDestX, mDestY, mDuration); + PropertyValuesHolder scrollX = PropertyValuesHolder.ofInt("scrollX", mDestX); + PropertyValuesHolder scrollY = PropertyValuesHolder.ofInt("scrollY", mDestY); + mAnimator = ObjectAnimator.ofPropertyValuesHolder(this, scrollX, scrollY); + mAnimator.setDuration(mDuration).start(); } public void setOverflow(String overflow) { 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 c8d4659aec60b3..c9fd3f39b4ee58 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 @@ -24,14 +24,6 @@ public class ReactScrollViewCommandHelper { public static final int COMMAND_SCROLL_TO_END = 2; public static final int COMMAND_FLASH_SCROLL_INDICATORS = 3; - /** - * Prior to users being able to specify a duration when calling "scrollTo", - * they could specify an "animate" boolean, which would use Android's - * "smoothScrollTo" method, which defaulted to a 250 millisecond - * animation: - * https://developer.android.com/reference/android/widget/Scroller.html#startScroll - */ - public static final int LEGACY_ANIMATION_DURATION = 250; public interface ScrollCommandHandler { void scrollTo(T scrollView, ScrollToCommandData data); @@ -42,10 +34,12 @@ public interface ScrollCommandHandler { public static class ScrollToCommandData { public final int mDestX, mDestY, mDuration; + public final boolean mAnimated; - ScrollToCommandData(int destX, int destY, int duration) { + ScrollToCommandData(int destX, int destY, boolean animated, int duration) { mDestX = destX; mDestY = destY; + mAnimated = animated; mDuration = duration; } } @@ -53,8 +47,10 @@ public static class ScrollToCommandData { public static class ScrollToEndCommandData { public final int mDuration; + public final boolean mAnimated; - ScrollToEndCommandData(int duration) { + ScrollToEndCommandData(boolean animated, int duration) { + mAnimated = animated; mDuration = duration; } } @@ -81,32 +77,15 @@ public static void receiveCommand( case COMMAND_SCROLL_TO: { int destX = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(0))); int destY = Math.round(PixelUtil.toPixelFromDIP(args.getDouble(1))); - - // Defer to the "duration" argument to determine if we should animate the - // scrollTo, otherwise use the legacy "animated" boolean. - // TODO(dannycochran) Eventually this can be removed in favor of just - // looking at "duration" once support also exists on iOS. - int duration = 0; - if (args.size() == 4 && args.getDouble(3) >= 0) { - duration = (int) Math.round(args.getDouble(3)); - } else { - duration = args.getBoolean(2) ? LEGACY_ANIMATION_DURATION : 0; - } - viewManager.scrollTo(scrollView, new ScrollToCommandData(destX, destY, duration)); + boolean animated = args.getBoolean(2); + int duration = (int) Math.round(args.getDouble(3)); + viewManager.scrollTo(scrollView, new ScrollToCommandData(destX, destY, animated, duration)); return; } case COMMAND_SCROLL_TO_END: { - // Defer to the "duration" argument to determine if we should animate the - // scrollTo, otherwise use the legacy "animated" boolean. - // TODO(dannycochran) Eventually this can be removed in favor of just - // looking at "duration" once support also exists on iOS. - int duration = 0; - if (args.size() == 2 && args.getDouble(1) >= 0) { - duration = (int) Math.round(args.getDouble(1)); - } else { - duration = args.getBoolean(0) ? LEGACY_ANIMATION_DURATION : 0; - } - viewManager.scrollToEnd(scrollView, new ScrollToEndCommandData(duration)); + boolean animated = args.getBoolean(0); + int duration = (int) Math.round(args.getDouble(1)); + viewManager.scrollToEnd(scrollView, new ScrollToEndCommandData(animated, duration)); return; } case COMMAND_FLASH_SCROLL_INDICATORS: 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 index 2fc20cce3eddf5..3a655cb12d72a5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java @@ -97,18 +97,4 @@ public static int parseOverScrollMode(String jsOverScrollMode) { throw new JSApplicationIllegalArgumentException("wrong overScrollMode: " + jsOverScrollMode); } } - - /** - * Helper method for animating to a ScrollView position with a given duration, - * instead of using "smoothScrollTo", which does not expose a duration argument. - */ - public static ObjectAnimator animateScroll(final ViewGroup scrollView, int mDestX, int mDestY, int mDuration) { - PropertyValuesHolder scrollX = PropertyValuesHolder.ofInt("scrollX", mDestX); - PropertyValuesHolder scrollY = PropertyValuesHolder.ofInt("scrollY", mDestY); - - final ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(scrollView, scrollX, scrollY); - - animator.setDuration(mDuration).start(); - return animator; - } } 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 941666dff07f53..12c5b2dab7439b 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 @@ -191,8 +191,12 @@ public void flashScrollIndicators(ReactScrollView scrollView) { @Override public void scrollTo( ReactScrollView scrollView, ReactScrollViewCommandHelper.ScrollToCommandData data) { - if (data.mDuration > 0) { - scrollView.animateScroll(scrollView, data.mDestX, data.mDestY, data.mDuration); + if (data.mAnimated && data.mDuration !=0) { + if (data.mDuration > 0) { + scrollView.animateScroll(data.mDestX, data.mDestY, data.mDuration); + } else { + scrollView.smoothScrollTo(data.mDestX, data.mDestY); + } } else { scrollView.scrollTo(data.mDestX, data.mDestY); } @@ -257,8 +261,12 @@ public void scrollToEnd( // ScrollView always has one child - the scrollable area int bottom = scrollView.getChildAt(0).getHeight() + scrollView.getPaddingBottom(); - if (data.mDuration > 0) { - scrollView.animateScroll(scrollView, scrollView.getScrollX(), bottom, data.mDuration); + if (data.mAnimated && data.mDuration !=0) { + if (data.mDuration > 0) { + scrollView.animateScroll(scrollView.getScrollX(), bottom, data.mDuration); + } else { + scrollView.smoothScrollTo(scrollView.getScrollX(), bottom); + } } else { scrollView.scrollTo(scrollView.getScrollX(), bottom); } From b9455d19ddd2a1f7fd3475ca1ca58226741815c4 Mon Sep 17 00:00:00 2001 From: osdnk Date: Wed, 16 Jan 2019 01:54:15 +0100 Subject: [PATCH 14/18] Fix spacing --- .../react/views/scroll/ReactHorizontalScrollViewManager.java | 4 ++-- .../facebook/react/views/scroll/ReactScrollViewManager.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 e865507332b906..b2657731b7243e 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 @@ -171,7 +171,7 @@ public void flashScrollIndicators(ReactHorizontalScrollView scrollView) { @Override public void scrollTo( ReactHorizontalScrollView scrollView, ReactScrollViewCommandHelper.ScrollToCommandData data) { - if (data.mAnimated && data.mDuration !=0) { + if (data.mAnimated && data.mDuration != 0) { if (data.mDuration > 0) { scrollView.animateScroll(data.mDestX, data.mDestY, data.mDuration); } else { @@ -189,7 +189,7 @@ public void scrollToEnd( // ScrollView always has one child - the scrollable area int right = scrollView.getChildAt(0).getWidth() + scrollView.getPaddingRight(); - if (data.mAnimated && data.mDuration !=0) { + if (data.mAnimated && data.mDuration != 0) { if (data.mDuration > 0) { scrollView.animateScroll(right, scrollView.getScrollY(), data.mDuration); } else { 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 12c5b2dab7439b..f77a3e23db5192 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 @@ -191,7 +191,7 @@ public void flashScrollIndicators(ReactScrollView scrollView) { @Override public void scrollTo( ReactScrollView scrollView, ReactScrollViewCommandHelper.ScrollToCommandData data) { - if (data.mAnimated && data.mDuration !=0) { + if (data.mAnimated && data.mDuration != 0) { if (data.mDuration > 0) { scrollView.animateScroll(data.mDestX, data.mDestY, data.mDuration); } else { @@ -261,7 +261,7 @@ public void scrollToEnd( // ScrollView always has one child - the scrollable area int bottom = scrollView.getChildAt(0).getHeight() + scrollView.getPaddingBottom(); - if (data.mAnimated && data.mDuration !=0) { + if (data.mAnimated && data.mDuration != 0) { if (data.mDuration > 0) { scrollView.animateScroll(scrollView.getScrollX(), bottom, data.mDuration); } else { From 94960ce98264804a12f279bb2d8e598c75ef97c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Osadnik?= Date: Wed, 16 Jan 2019 01:55:10 +0100 Subject: [PATCH 15/18] Remove whiteline --- .../react/views/scroll/ReactScrollViewCommandHelper.java | 1 - 1 file changed, 1 deletion(-) 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 c9fd3f39b4ee58..83abf0a60c4362 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 @@ -24,7 +24,6 @@ public class ReactScrollViewCommandHelper { public static final int COMMAND_SCROLL_TO_END = 2; public static final int COMMAND_FLASH_SCROLL_INDICATORS = 3; - public interface ScrollCommandHandler { void scrollTo(T scrollView, ScrollToCommandData data); void scrollToEnd(T scrollView, ScrollToEndCommandData data); From b00253ee788e89458a1cc3c5f827e805cf2fe418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Osadnik?= Date: Wed, 16 Jan 2019 01:55:27 +0100 Subject: [PATCH 16/18] Remove whiteline --- .../com/facebook/react/views/scroll/ReactScrollViewHelper.java | 1 - 1 file changed, 1 deletion(-) 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 index 3a655cb12d72a5..85a85085529b80 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.java @@ -9,7 +9,6 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; - import android.view.View; import android.view.ViewGroup; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; From acb2957778c9322d8b71dfa39c841e03c3a06377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Osadnik?= Date: Wed, 16 Jan 2019 09:45:47 +0100 Subject: [PATCH 17/18] Remove bad comment --- Libraries/Components/ScrollResponder.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js index 5fe6212bc5fab5..7b3256ad3e0bb3 100644 --- a/Libraries/Components/ScrollResponder.js +++ b/Libraries/Components/ScrollResponder.js @@ -437,8 +437,6 @@ const ScrollResponderMixin = { * Note: The weird argument signature is due to the fact that, for historical reasons, * the function also accepts separate arguments as as alternative to the options object. * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED. - * - * Also note "duration" is currently only supported for Android. */ scrollResponderScrollTo: function( x?: From a386ae4036b62b34a6f5ac15de10254585683daa Mon Sep 17 00:00:00 2001 From: osdnk Date: Thu, 17 Jan 2019 00:26:04 +0100 Subject: [PATCH 18/18] Add proper comments --- .../react/views/scroll/ReactHorizontalScrollViewManager.java | 2 ++ .../com/facebook/react/views/scroll/ReactScrollViewManager.java | 2 ++ 2 files changed, 4 insertions(+) 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 b2657731b7243e..254cae5bbd4ebe 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 @@ -173,6 +173,7 @@ public void scrollTo( ReactHorizontalScrollView scrollView, ReactScrollViewCommandHelper.ScrollToCommandData data) { if (data.mAnimated && data.mDuration != 0) { if (data.mDuration > 0) { + // data.mDuration set to -1 to fallbacks to default platform behavior scrollView.animateScroll(data.mDestX, data.mDestY, data.mDuration); } else { scrollView.smoothScrollTo(data.mDestX, data.mDestY); @@ -191,6 +192,7 @@ public void scrollToEnd( scrollView.getChildAt(0).getWidth() + scrollView.getPaddingRight(); if (data.mAnimated && data.mDuration != 0) { if (data.mDuration > 0) { + // data.mDuration set to -1 to fallbacks to default platform behavior scrollView.animateScroll(right, scrollView.getScrollY(), data.mDuration); } else { scrollView.smoothScrollTo(right, scrollView.getScrollY()); 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 f77a3e23db5192..48ceef3dae82f4 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 @@ -193,6 +193,7 @@ public void scrollTo( ReactScrollView scrollView, ReactScrollViewCommandHelper.ScrollToCommandData data) { if (data.mAnimated && data.mDuration != 0) { if (data.mDuration > 0) { + // data.mDuration set to -1 to fallbacks to default platform behavior scrollView.animateScroll(data.mDestX, data.mDestY, data.mDuration); } else { scrollView.smoothScrollTo(data.mDestX, data.mDestY); @@ -263,6 +264,7 @@ public void scrollToEnd( scrollView.getChildAt(0).getHeight() + scrollView.getPaddingBottom(); if (data.mAnimated && data.mDuration != 0) { if (data.mDuration > 0) { + // data.mDuration set to -1 to fallbacks to default platform behavior scrollView.animateScroll(scrollView.getScrollX(), bottom, data.mDuration); } else { scrollView.smoothScrollTo(scrollView.getScrollX(), bottom);