Skip to content

Commit

Permalink
Refactor helper method for ScrollView to not detect scroll direction
Browse files Browse the repository at this point in the history
Summary:
The helper class for ScrollView should not need to detect the scroll direction. If it has to do so, that means the corresponding logic should live in the `ReactScrollView` or `ReactHorizontalScrollView` respectively.

This diff is to remove the scroll direction detection logic from the scroll view helper. Long term we should keep it this way as the shared code got moved to the helper class from the scroll view classes.

Changelog:
[Internal]

Reviewed By: javache

Differential Revision: D32514429

fbshipit-source-id: 2165f2eba90cc25d14834c39148fe8ce8805bea6
  • Loading branch information
ryancat authored and facebook-github-bot committed Nov 30, 2021
1 parent 8ab2cbb commit 508de3f
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 58 deletions.
Expand Up @@ -18,7 +18,6 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
Expand Down Expand Up @@ -48,7 +47,6 @@
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;
Expand Down Expand Up @@ -141,8 +139,7 @@ public void onInitializeAccessibilityNodeInfo(
new ReactScrollViewScrollState(
I18nUtil.getInstance().isRTL(context)
? ViewCompat.LAYOUT_DIRECTION_RTL
: ViewCompat.LAYOUT_DIRECTION_LTR,
ReactScrollViewScrollDirection.HORIZONTAL);
: ViewCompat.LAYOUT_DIRECTION_LTR);
}

@Nullable
Expand Down Expand Up @@ -814,10 +811,13 @@ private int predictFinalScrollPosition(int velocityX) {
// predict where a fling would end up so we can scroll to the nearest snap offset
int maximumOffset = Math.max(0, computeHorizontalScrollRange() - getWidth());
int width = getWidth() - ViewCompat.getPaddingStart(this) - ViewCompat.getPaddingEnd(this);
Point postAnimationScroll = ReactScrollViewHelper.getPostAnimationScroll(this, velocityX > 0);
scroller.fling(
postAnimationScroll.x, // startX
postAnimationScroll.y, // startY
ReactScrollViewHelper.getNextFlingStartValue(
this,
getScrollX(),
getReactScrollViewScrollState().getFinalAnimatedPositionScroll().x,
velocityX > 0), // startX
getScrollY(), // startY
velocityX, // velocityX
0, // velocityY
0, // minX
Expand All @@ -842,7 +842,12 @@ private void smoothScrollAndSnap(int velocity) {

double interval = (double) getSnapInterval();
double currentOffset =
(double) (ReactScrollViewHelper.getPostAnimationScroll(this, velocity > 0).x);
(double)
(ReactScrollViewHelper.getNextFlingStartValue(
this,
getScrollX(),
getReactScrollViewScrollState().getFinalAnimatedPositionScroll().x,
velocity > 0));
double targetOffset = (double) predictFinalScrollPosition(velocity);

int previousPage = (int) Math.floor(currentOffset / interval);
Expand Down
Expand Up @@ -18,7 +18,6 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
Expand All @@ -44,7 +43,6 @@
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;
Expand Down Expand Up @@ -102,8 +100,7 @@ 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, ReactScrollViewScrollDirection.VERTICAL);
new ReactScrollViewScrollState(ViewCompat.LAYOUT_DIRECTION_LTR);
private final ValueAnimator DEFAULT_FLING_ANIMATOR = ObjectAnimator.ofInt(this, "scrollY", 0, 0);

public ReactScrollView(Context context) {
Expand Down Expand Up @@ -603,10 +600,13 @@ private int predictFinalScrollPosition(int velocityY) {
// predict where a fling would end up so we can scroll to the nearest snap offset
int maximumOffset = getMaxScrollY();
int height = getHeight() - getPaddingBottom() - getPaddingTop();
Point postAnimationScroll = ReactScrollViewHelper.getPostAnimationScroll(this, velocityY > 0);
scroller.fling(
postAnimationScroll.x, // startX
postAnimationScroll.y, // startY
getScrollX(), // startX
ReactScrollViewHelper.getNextFlingStartValue(
this,
getScrollY(),
getReactScrollViewScrollState().getFinalAnimatedPositionScroll().y,
velocityY > 0), // startY
0, // velocityX
velocityY, // velocityY
0, // minX
Expand All @@ -631,7 +631,12 @@ private View getContentView() {
private void smoothScrollAndSnap(int velocity) {
double interval = (double) getSnapInterval();
double currentOffset =
(double) (ReactScrollViewHelper.getPostAnimationScroll(this, velocity > 0).y);
(double)
(ReactScrollViewHelper.getNextFlingStartValue(
this,
getScrollY(),
getReactScrollViewScrollState().getFinalAnimatedPositionScroll().y,
velocity > 0));
double targetOffset = (double) predictFinalScrollPosition(velocity);

int previousPage = (int) Math.floor(currentOffset / interval);
Expand Down
Expand Up @@ -215,24 +215,16 @@ public static void removeScrollListener(ScrollListener listener) {
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);
private boolean mIsCanceled = false;
private boolean mIsFinished = true;

public ReactScrollViewScrollState(
final int layoutDirection, final ReactScrollViewScrollDirection scrollDirection) {
public ReactScrollViewScrollState(final int layoutDirection) {
mLayoutDirection = layoutDirection;
mScrollDirection = scrollDirection;
}

/**
Expand All @@ -243,14 +235,6 @@ public int getLayoutDirection() {
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;
Expand Down Expand Up @@ -334,52 +318,41 @@ void smoothScrollTo(final T scrollView, final int x, final int y) {

final int scrollX = scrollView.getScrollX();
final int scrollY = scrollView.getScrollY();
final ReactScrollViewScrollDirection scrollDirection = scrollState.getScrollDirection();
if (scrollDirection == ReactScrollViewScrollDirection.HORIZONTAL && scrollX != x) {
// Only one fling animator will be started. For the horizontal scroll view, scrollY will always
// be the same to y. This is the same to the vertical scroll view.
if (scrollX != x) {
scrollView.startFlingAnimator(scrollX, x);
}
if (scrollDirection == ReactScrollViewScrollDirection.VERTICAL && scrollY != y) {
if (scrollY != y) {
scrollView.startFlingAnimator(scrollY, y);
}

updateFabricScrollState(scrollView, x, y);
}

/** Get current (x, y) position or position after current animation finishes, if any. */
/** Get current position or position after current animation finishes, if any. */
public static <
T extends
ViewGroup & FabricViewStateManager.HasFabricViewStateManager & HasScrollState
& HasFlingAnimator>
Point getPostAnimationScroll(final T scrollView, final boolean isPositiveVelocity) {
int getNextFlingStartValue(
final T scrollView,
final int currentValue,
final int postAnimationValue,
final boolean isPositiveVelocity) {
final ReactScrollViewScrollState scrollState = scrollView.getReactScrollViewScrollState();
final int velocityDirectionMask = isPositiveVelocity ? 1 : -1;
final Point animatedScrollPos = scrollState.getFinalAnimatedPositionScroll();
final Point currentScrollPos = new Point(scrollView.getScrollX(), scrollView.getScrollY());

boolean isMovingTowardsAnimatedValue = false;
switch (scrollState.getScrollDirection()) {
case HORIZONTAL:
isMovingTowardsAnimatedValue =
velocityDirectionMask * (animatedScrollPos.x - currentScrollPos.x) > 0;
break;

case VERTICAL:
isMovingTowardsAnimatedValue =
velocityDirectionMask * (animatedScrollPos.y - currentScrollPos.y) > 0;
break;

default:
throw new IllegalArgumentException("ScrollView has unexpected scroll direction.");
}
final boolean isMovingTowardsAnimatedValue =
velocityDirectionMask * (postAnimationValue - currentValue) > 0;

// When the fling animation is not finished, or it was canceled and now we are moving towards
// the final animated value, we will return the final animated value. This is because follow up
// animation should consider the "would be" animated location, so that previous quick small
// scrolls are still working.
return !scrollState.getIsFinished()
|| (scrollState.getIsCanceled() && isMovingTowardsAnimatedValue)
? animatedScrollPos
: currentScrollPos;
? postAnimationValue
: currentValue;
}

public static <
Expand Down

0 comments on commit 508de3f

Please sign in to comment.