mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Make fling animator customizable
Summary: This diff makes the fling animator cuztomizable, so that the subclasses can have their own animation for fling behavior. Before the diff, we rely on the `OverScroller` to `fling`, which has some issues with Spline interpolation being messed up due to the clamped fling distance (See more details in T105464095). This may not be a big issue for mobile when user touches screen, but in VR environment this shows up very clearly with joystick events. To fix that properly without affecting mobile behavior, I added a new interface `HasFlingAnimator` from the helper, and implemented with default fling animator in the OSS ScrollView. We should consider adopt a suitable animator for mobile platform, as the non-smooth fling effect is also happening in mobile. - Add interface `HasFlingAnimator` to `ReactScrollView` and `ReactHorizontalScrollView` - Add default fling animator - Depend on if the default animator is used, customize the flingAndSnap behavior Changelog: [Internal] Reviewed By: JoshuaGross Differential Revision: D32382806 fbshipit-source-id: 08f03350f6a9b9fc03414b4dcb9977b9f33603ba
This commit is contained in:
committed by
Facebook GitHub Bot
parent
0ff02f9a41
commit
3352b57a6f
+51
-13
@@ -13,6 +13,8 @@ import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNME
|
||||
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.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
@@ -44,7 +46,9 @@ import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
|
||||
import com.facebook.react.uimanager.ReactOverflowView;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewHelper.HasFlingAnimator;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewHelper.HasScrollState;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewHelper.ReactScrollViewScrollDirection;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewHelper.ReactScrollViewScrollState;
|
||||
import com.facebook.react.views.view.ReactViewBackgroundManager;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -56,7 +60,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
implements ReactClippingViewGroup,
|
||||
FabricViewStateManager.HasFabricViewStateManager,
|
||||
ReactOverflowView,
|
||||
HasScrollState {
|
||||
HasScrollState,
|
||||
HasFlingAnimator {
|
||||
|
||||
private static boolean DEBUG_MODE = false && ReactBuildConfig.DEBUG;
|
||||
private static String TAG = ReactHorizontalScrollView.class.getSimpleName();
|
||||
@@ -101,6 +106,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
private int pendingContentOffsetY = UNSET_CONTENT_OFFSET;
|
||||
private final FabricViewStateManager mFabricViewStateManager = new FabricViewStateManager();
|
||||
private final ReactScrollViewScrollState mReactScrollViewScrollState;
|
||||
private final ValueAnimator DEFAULT_FLING_ANIMATOR = ObjectAnimator.ofInt(this, "scrollX", 0, 0);
|
||||
|
||||
private final Rect mTempRect = new Rect();
|
||||
|
||||
@@ -135,7 +141,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
new ReactScrollViewScrollState(
|
||||
I18nUtil.getInstance().isRTL(context)
|
||||
? ViewCompat.LAYOUT_DIRECTION_RTL
|
||||
: ViewCompat.LAYOUT_DIRECTION_LTR);
|
||||
: ViewCompat.LAYOUT_DIRECTION_LTR,
|
||||
ReactScrollViewScrollDirection.HORIZONTAL);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -899,6 +906,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasCustomizedFlingAnimator = getFlingAnimator() != DEFAULT_FLING_ANIMATOR;
|
||||
int maximumOffset = Math.max(0, computeHorizontalScrollRange() - getWidth());
|
||||
int targetOffset = predictFinalScrollPosition(velocityX);
|
||||
if (mDisableIntervalMomentum) {
|
||||
@@ -1008,13 +1016,19 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
targetOffset = firstOffset;
|
||||
}
|
||||
} else if (velocityX > 0) {
|
||||
// when snapping velocity can feel sluggish for slow swipes
|
||||
velocityX += (int) ((largerOffset - targetOffset) * 10.0);
|
||||
if (!hasCustomizedFlingAnimator) {
|
||||
// The default animator requires boost on initial velocity as when snapping velocity can
|
||||
// feel sluggish for slow swipes
|
||||
velocityX += (int) ((largerOffset - targetOffset) * 10.0);
|
||||
}
|
||||
|
||||
targetOffset = largerOffset;
|
||||
} else if (velocityX < 0) {
|
||||
// when snapping velocity can feel sluggish for slow swipes
|
||||
velocityX -= (int) ((targetOffset - smallerOffset) * 10.0);
|
||||
if (!hasCustomizedFlingAnimator) {
|
||||
// The default animator requires boost on initial velocity as when snapping velocity can
|
||||
// feel sluggish for slow swipes
|
||||
velocityX -= (int) ((targetOffset - smallerOffset) * 10.0);
|
||||
}
|
||||
|
||||
targetOffset = smallerOffset;
|
||||
} else {
|
||||
@@ -1029,11 +1043,13 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
velocityX = -velocityX;
|
||||
}
|
||||
|
||||
// smoothScrollTo will always scroll over 250ms which is often *waaay*
|
||||
// too short and will cause the scrolling to feel almost instant
|
||||
// try to manually interact with OverScroller instead
|
||||
// if velocity is 0 however, fling() won't work, so we want to use smoothScrollTo
|
||||
if (mScroller != null) {
|
||||
if (hasCustomizedFlingAnimator || mScroller == null) {
|
||||
reactSmoothScrollTo(targetOffset, getScrollY());
|
||||
} else {
|
||||
// smoothScrollTo will always scroll over 250ms which is often *waaay*
|
||||
// too short and will cause the scrolling to feel almost instant
|
||||
// try to manually interact with OverScroller instead
|
||||
// if velocity is 0 however, fling() won't work, so we want to use smoothScrollTo
|
||||
mActivelyScrolling = true;
|
||||
|
||||
mScroller.fling(
|
||||
@@ -1055,8 +1071,6 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
);
|
||||
|
||||
postInvalidateOnAnimation();
|
||||
} else {
|
||||
reactSmoothScrollTo(targetOffset, getScrollY());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1196,4 +1210,28 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
public ReactScrollViewScrollState getReactScrollViewScrollState() {
|
||||
return mReactScrollViewScrollState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startFlingAnimator(int start, int end) {
|
||||
// Always cancel existing animator before starting the new one. `smoothScrollTo` contains some
|
||||
// logic that, if called multiple times in a short amount of time, will treat all calls as part
|
||||
// of the same animation and will not lengthen the duration of the animation. This means that,
|
||||
// for example, if the user is scrolling rapidly, multiple pages could be considered part of one
|
||||
// animation, causing some page animations to be animated very rapidly - looking like they're
|
||||
// not animated at all.
|
||||
DEFAULT_FLING_ANIMATOR.cancel();
|
||||
|
||||
// Update the fling animator with new values
|
||||
DEFAULT_FLING_ANIMATOR
|
||||
.setDuration(ReactScrollViewHelper.getDefaultScrollAnimationDuration(getContext()))
|
||||
.setIntValues(start, end);
|
||||
|
||||
// Start the animator
|
||||
DEFAULT_FLING_ANIMATOR.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueAnimator getFlingAnimator() {
|
||||
return DEFAULT_FLING_ANIMATOR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNME
|
||||
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.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
@@ -40,7 +42,9 @@ import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
|
||||
import com.facebook.react.uimanager.ReactOverflowView;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewHelper.HasFlingAnimator;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewHelper.HasScrollState;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewHelper.ReactScrollViewScrollDirection;
|
||||
import com.facebook.react.views.scroll.ReactScrollViewHelper.ReactScrollViewScrollState;
|
||||
import com.facebook.react.views.view.ReactViewBackgroundManager;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -59,7 +63,8 @@ public class ReactScrollView extends ScrollView
|
||||
View.OnLayoutChangeListener,
|
||||
FabricViewStateManager.HasFabricViewStateManager,
|
||||
ReactOverflowView,
|
||||
HasScrollState {
|
||||
HasScrollState,
|
||||
HasFlingAnimator {
|
||||
|
||||
private static @Nullable Field sScrollerField;
|
||||
private static boolean sTriedToGetScrollerField = false;
|
||||
@@ -97,7 +102,9 @@ public class ReactScrollView extends ScrollView
|
||||
private int pendingContentOffsetY = UNSET_CONTENT_OFFSET;
|
||||
private final FabricViewStateManager mFabricViewStateManager = new FabricViewStateManager();
|
||||
private final ReactScrollViewScrollState mReactScrollViewScrollState =
|
||||
new ReactScrollViewScrollState(ViewCompat.LAYOUT_DIRECTION_LTR);
|
||||
new ReactScrollViewScrollState(
|
||||
ViewCompat.LAYOUT_DIRECTION_LTR, ReactScrollViewScrollDirection.VERTICAL);
|
||||
private final ValueAnimator DEFAULT_FLING_ANIMATOR = ObjectAnimator.ofInt(this, "scrollY", 0, 0);
|
||||
|
||||
public ReactScrollView(Context context) {
|
||||
this(context, null);
|
||||
@@ -684,6 +691,7 @@ public class ReactScrollView extends ScrollView
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasCustomizedFlingAnimator = getFlingAnimator() != DEFAULT_FLING_ANIMATOR;
|
||||
int maximumOffset = getMaxScrollY();
|
||||
int targetOffset = predictFinalScrollPosition(velocityY);
|
||||
if (mDisableIntervalMomentum) {
|
||||
@@ -795,13 +803,19 @@ public class ReactScrollView extends ScrollView
|
||||
targetOffset = firstOffset;
|
||||
}
|
||||
} else if (velocityY > 0) {
|
||||
// when snapping velocity can feel sluggish for slow swipes
|
||||
velocityY += (int) ((largerOffset - targetOffset) * 10.0);
|
||||
if (!hasCustomizedFlingAnimator) {
|
||||
// The default animator requires boost on initial velocity as when snapping velocity can
|
||||
// feel sluggish for slow swipes
|
||||
velocityY += (int) ((largerOffset - targetOffset) * 10.0);
|
||||
}
|
||||
|
||||
targetOffset = largerOffset;
|
||||
} else if (velocityY < 0) {
|
||||
// when snapping velocity can feel sluggish for slow swipes
|
||||
velocityY -= (int) ((targetOffset - smallerOffset) * 10.0);
|
||||
if (!hasCustomizedFlingAnimator) {
|
||||
// The default animator requires boost on initial velocity as when snapping velocity can
|
||||
// feel sluggish for slow swipes
|
||||
velocityY -= (int) ((targetOffset - smallerOffset) * 10.0);
|
||||
}
|
||||
|
||||
targetOffset = smallerOffset;
|
||||
} else {
|
||||
@@ -811,11 +825,13 @@ public class ReactScrollView extends ScrollView
|
||||
// Make sure the new offset isn't out of bounds
|
||||
targetOffset = Math.min(Math.max(0, targetOffset), maximumOffset);
|
||||
|
||||
// smoothScrollTo will always scroll over 250ms which is often *waaay*
|
||||
// too short and will cause the scrolling to feel almost instant
|
||||
// try to manually interact with OverScroller instead
|
||||
// if velocity is 0 however, fling() won't work, so we want to use smoothScrollTo
|
||||
if (mScroller != null) {
|
||||
if (hasCustomizedFlingAnimator || mScroller == null) {
|
||||
reactSmoothScrollTo(getScrollX(), targetOffset);
|
||||
} else {
|
||||
// smoothScrollTo will always scroll over 250ms which is often *waaay*
|
||||
// too short and will cause the scrolling to feel almost instant
|
||||
// try to manually interact with OverScroller instead
|
||||
// if velocity is 0 however, fling() won't work, so we want to use smoothScrollTo
|
||||
mActivelyScrolling = true;
|
||||
|
||||
mScroller.fling(
|
||||
@@ -837,8 +853,6 @@ public class ReactScrollView extends ScrollView
|
||||
);
|
||||
|
||||
postInvalidateOnAnimation();
|
||||
} else {
|
||||
reactSmoothScrollTo(getScrollX(), targetOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1060,4 +1074,28 @@ public class ReactScrollView extends ScrollView
|
||||
public ReactScrollViewScrollState getReactScrollViewScrollState() {
|
||||
return mReactScrollViewScrollState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startFlingAnimator(int start, int end) {
|
||||
// Always cancel existing animator before starting the new one. `smoothScrollTo` contains some
|
||||
// logic that, if called multiple times in a short amount of time, will treat all calls as part
|
||||
// of the same animation and will not lengthen the duration of the animation. This means that,
|
||||
// for example, if the user is scrolling rapidly, multiple pages could be considered part of one
|
||||
// animation, causing some page animations to be animated very rapidly - looking like they're
|
||||
// not animated at all.
|
||||
DEFAULT_FLING_ANIMATOR.cancel();
|
||||
|
||||
// Update the fling animator with new values
|
||||
DEFAULT_FLING_ANIMATOR
|
||||
.setDuration(ReactScrollViewHelper.getDefaultScrollAnimationDuration(getContext()))
|
||||
.setIntValues(start, end);
|
||||
|
||||
// Start the animator
|
||||
DEFAULT_FLING_ANIMATOR.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueAnimator getFlingAnimator() {
|
||||
return DEFAULT_FLING_ANIMATOR;
|
||||
}
|
||||
}
|
||||
|
||||
+89
-50
@@ -8,8 +8,6 @@
|
||||
package com.facebook.react.views.scroll;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
@@ -50,8 +48,6 @@ public class ReactScrollViewHelper {
|
||||
public static final int SNAP_ALIGNMENT_CENTER = 2;
|
||||
public static final int SNAP_ALIGNMENT_END = 3;
|
||||
|
||||
private static @Nullable ValueAnimator sScrollAnimator;
|
||||
|
||||
public interface ScrollListener {
|
||||
void onScroll(
|
||||
ViewGroup scrollView, ScrollEventType scrollEventType, float xVelocity, float yVelocity);
|
||||
@@ -219,14 +215,22 @@ public class ReactScrollViewHelper {
|
||||
sScrollListeners.remove(listener);
|
||||
}
|
||||
|
||||
public enum ReactScrollViewScrollDirection {
|
||||
HORIZONTAL,
|
||||
VERTICAL,
|
||||
}
|
||||
|
||||
public static class ReactScrollViewScrollState {
|
||||
private final int mLayoutDirection;
|
||||
private final ReactScrollViewScrollDirection mScrollDirection;
|
||||
private final Point mFinalAnimatedPositionScroll = new Point();
|
||||
private int mScrollAwayPaddingTop = 0;
|
||||
private final Point mLastStateUpdateScroll = new Point(-1, -1);
|
||||
|
||||
public ReactScrollViewScrollState(final int layoutDirection) {
|
||||
public ReactScrollViewScrollState(
|
||||
final int layoutDirection, final ReactScrollViewScrollDirection scrollDirection) {
|
||||
mLayoutDirection = layoutDirection;
|
||||
mScrollDirection = scrollDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,6 +241,14 @@ public class ReactScrollViewHelper {
|
||||
return mLayoutDirection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scroll direction. Can be either ReactScrollViewScrollDirection.HORIZONTAL or
|
||||
* ReactScrollViewScrollDirection.VERTICAL.
|
||||
*/
|
||||
public ReactScrollViewScrollDirection getScrollDirection() {
|
||||
return mScrollDirection;
|
||||
}
|
||||
|
||||
/** Get the position after current animation is finished */
|
||||
public Point getFinalAnimatedPositionScroll() {
|
||||
return mFinalAnimatedPositionScroll;
|
||||
@@ -273,72 +285,58 @@ public class ReactScrollViewHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the given view to the location (x, y), with provided initial velocity. This method works
|
||||
* by calculate the "would be" initial velocity with internal friction to move to the point (x,
|
||||
* y), then apply that to the animator.
|
||||
*/
|
||||
public static <
|
||||
T extends ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState>
|
||||
T extends
|
||||
ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState
|
||||
& HasFlingAnimator>
|
||||
void smoothScrollTo(final T scrollView, final int x, final int y) {
|
||||
if (DEBUG_MODE) {
|
||||
FLog.i(TAG, "smoothScrollTo[%d] x %d y %d", scrollView.getId(), x, y);
|
||||
}
|
||||
|
||||
// `smoothScrollTo` contains some logic that, if called multiple times in a short amount of
|
||||
// time, will treat all calls as part of the same animation and will not lengthen the duration
|
||||
// of the animation. This means that, for example, if the user is scrolling rapidly, multiple
|
||||
// pages could be considered part of one animation, causing some page animations to be animated
|
||||
// very rapidly - looking like they're not animated at all.
|
||||
if (sScrollAnimator != null) {
|
||||
sScrollAnimator.cancel();
|
||||
// Register the listeners for the fling animator if there isn't any
|
||||
final ValueAnimator flingAnimator = scrollView.getFlingAnimator();
|
||||
if (flingAnimator.getListeners() == null || flingAnimator.getListeners().size() == 0) {
|
||||
registerFlingAnimator(scrollView);
|
||||
}
|
||||
|
||||
final ReactScrollViewScrollState scrollState = scrollView.getReactScrollViewScrollState();
|
||||
scrollState.setFinalAnimatedPositionScroll(x, y);
|
||||
PropertyValuesHolder scrollX =
|
||||
PropertyValuesHolder.ofInt("scrollX", scrollView.getScrollX(), x);
|
||||
PropertyValuesHolder scrollY =
|
||||
PropertyValuesHolder.ofInt("scrollY", scrollView.getScrollY(), y);
|
||||
sScrollAnimator = ObjectAnimator.ofPropertyValuesHolder(scrollX, scrollY);
|
||||
sScrollAnimator.setDuration(getDefaultScrollAnimationDuration(scrollView.getContext()));
|
||||
sScrollAnimator.addUpdateListener(
|
||||
new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
int scrollValueX = (Integer) valueAnimator.getAnimatedValue("scrollX");
|
||||
int scrollValueY = (Integer) valueAnimator.getAnimatedValue("scrollY");
|
||||
scrollView.scrollTo(scrollValueX, scrollValueY);
|
||||
}
|
||||
});
|
||||
sScrollAnimator.addListener(
|
||||
new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
scrollState.setFinalAnimatedPositionScroll(-1, -1);
|
||||
sScrollAnimator = null;
|
||||
updateStateOnScroll(scrollView);
|
||||
}
|
||||
final int scrollX = scrollView.getScrollX();
|
||||
final int scrollY = scrollView.getScrollY();
|
||||
final ReactScrollViewScrollDirection scrollDirection = scrollState.getScrollDirection();
|
||||
if (scrollDirection == ReactScrollViewScrollDirection.HORIZONTAL && scrollX != x) {
|
||||
scrollView.startFlingAnimator(scrollX, x);
|
||||
}
|
||||
if (scrollDirection == ReactScrollViewScrollDirection.VERTICAL && scrollY != y) {
|
||||
scrollView.startFlingAnimator(scrollY, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animator) {}
|
||||
});
|
||||
sScrollAnimator.start();
|
||||
updateStateOnScroll(scrollView, x, y);
|
||||
}
|
||||
|
||||
/** Get current (x, y) position or position after current animation finishes, if any. */
|
||||
public static <
|
||||
T extends ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState>
|
||||
T extends
|
||||
ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState
|
||||
& HasFlingAnimator>
|
||||
Point getPostAnimationScroll(final T scrollView) {
|
||||
return sScrollAnimator != null && sScrollAnimator.isRunning()
|
||||
final ValueAnimator flingAnimator = scrollView.getFlingAnimator();
|
||||
return flingAnimator != null && flingAnimator.isRunning()
|
||||
? scrollView.getReactScrollViewScrollState().getFinalAnimatedPositionScroll()
|
||||
: new Point(scrollView.getScrollX(), scrollView.getScrollY());
|
||||
}
|
||||
|
||||
public static <
|
||||
T extends ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState>
|
||||
T extends
|
||||
ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState
|
||||
& HasFlingAnimator>
|
||||
boolean updateStateOnScroll(final T scrollView) {
|
||||
return updateStateOnScroll(scrollView, scrollView.getScrollX(), scrollView.getScrollY());
|
||||
}
|
||||
@@ -347,7 +345,9 @@ public class ReactScrollViewHelper {
|
||||
* Called on any stabilized onScroll change to propagate content offset value to a Shadow Node.
|
||||
*/
|
||||
public static <
|
||||
T extends ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState>
|
||||
T extends
|
||||
ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState
|
||||
& HasFlingAnimator>
|
||||
boolean updateStateOnScroll(final T scrollView, final int scrollX, final int scrollY) {
|
||||
if (DEBUG_MODE) {
|
||||
FLog.i(
|
||||
@@ -374,7 +374,9 @@ public class ReactScrollViewHelper {
|
||||
}
|
||||
|
||||
public static <
|
||||
T extends ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState>
|
||||
T extends
|
||||
ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState
|
||||
& HasFlingAnimator>
|
||||
void forceUpdateState(final T scrollView) {
|
||||
final ReactScrollViewScrollState scrollState = scrollView.getReactScrollViewScrollState();
|
||||
final int scrollAwayPaddingTop = scrollState.getScrollAwayPaddingTop();
|
||||
@@ -420,8 +422,45 @@ public class ReactScrollViewHelper {
|
||||
});
|
||||
}
|
||||
|
||||
public static <
|
||||
T extends
|
||||
ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState
|
||||
& HasFlingAnimator>
|
||||
void registerFlingAnimator(final T scrollView) {
|
||||
scrollView
|
||||
.getFlingAnimator()
|
||||
.addListener(
|
||||
new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
scrollView.getReactScrollViewScrollState().setFinalAnimatedPositionScroll(-1, -1);
|
||||
ReactScrollViewHelper.updateStateOnScroll(scrollView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animator) {}
|
||||
});
|
||||
}
|
||||
|
||||
public interface HasScrollState {
|
||||
/** Get the scroll state for the current ScrollView */
|
||||
ReactScrollViewScrollState getReactScrollViewScrollState();
|
||||
}
|
||||
|
||||
public interface HasFlingAnimator {
|
||||
/**
|
||||
* Start the fling animator that the ScrollView has to go from the start position to end
|
||||
* position.
|
||||
*/
|
||||
void startFlingAnimator(int start, int end);
|
||||
|
||||
/** Get the fling animator that is reused for the ScrollView to handle fling animation. */
|
||||
ValueAnimator getFlingAnimator();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user