Skip to content

Adds scrollToInterval to vertical scrollviews in React Native 0.51 #18679

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified ReactAndroid/src/main/java/com/facebook/react/views/scroll/BUCK
100644 → 100755
Empty file.
Empty file.
6 changes: 6 additions & 0 deletions ReactAndroid/src/main/java/com/facebook/react/views/scroll/OnScrollDispatchHelper.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class OnScrollDispatchHelper {
private int mPrevY = Integer.MIN_VALUE;
private float mXFlingVelocity = 0;
private float mYFlingVelocity = 0;
private static final float THRESHOLD = 0.1f; // Threshold for end fling

private long mLastScrollEventTimeMs = -(MIN_EVENT_SEPARATION_MS + 1);

Expand All @@ -38,6 +39,11 @@ public boolean onScrollChanged(int x, int y) {
mPrevX != x ||
mPrevY != y;

// Skip the first calculation in each scroll
if(Math.abs(mXFlingVelocity) < THRESHOLD && Math.abs(mYFlingVelocity) < THRESHOLD) {
shouldDispatch = false;
}

if (eventTime - mLastScrollEventTimeMs != 0) {
mXFlingVelocity = (float) (x - mPrevX) / (eventTime - mLastScrollEventTimeMs);
mYFlingVelocity = (float) (y - mPrevY) / (eventTime - mLastScrollEventTimeMs);
Expand Down
Empty file.
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
private @Nullable Drawable mEndBackground;
private int mEndFillColor = Color.TRANSPARENT;
private ReactViewBackgroundManager mReactBackgroundManager;
private int mSnapInterval = 0;

public void setSnapInterval(int snapInterval) {
mSnapInterval = snapInterval;
}

public ReactHorizontalScrollView(Context context) {
this(context, null);
Expand Down Expand Up @@ -164,6 +169,7 @@ public boolean onTouchEvent(MotionEvent ev) {
}

return super.onTouchEvent(ev);

}

@Override
Expand Down Expand Up @@ -312,7 +318,7 @@ public void run() {
* scrolling.
*/
private void smoothScrollToPage(int velocity) {
int width = getWidth();
int width = mSnapInterval != 0 ? mSnapInterval : getWidth();
int currentX = getScrollX();
// TODO (t11123799) - Should we do anything beyond linear accounting of the velocity
int predictedX = currentX + velocity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.yoga.YogaConstants;
import javax.annotation.Nullable;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import android.util.DisplayMetrics;

/**
* View manager for {@link ReactHorizontalScrollView} components.
Expand Down Expand Up @@ -112,6 +114,12 @@ public void setPagingEnabled(ReactHorizontalScrollView view, boolean pagingEnabl
public void setOverScrollMode(ReactHorizontalScrollView view, String value) {
view.setOverScrollMode(ReactScrollViewHelper.parseOverScrollMode(value));
}
@ReactProp(name = "snapToInterval")
public void setSnapToInterval(ReactHorizontalScrollView view, int snapToInterval) {
view.setPagingEnabled(snapToInterval > 0);
DisplayMetrics screenDisplayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics();
view.setSnapInterval((int)(snapToInterval * screenDisplayMetrics.density));
}

@Override
public void receiveCommand(
Expand Down
183 changes: 137 additions & 46 deletions ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
import com.facebook.react.views.view.ReactViewBackgroundManager;
import java.lang.reflect.Field;
import javax.annotation.Nullable;
import android.annotation.TargetApi;
import android.graphics.drawable.LayerDrawable;
import android.view.ViewGroup;
import com.facebook.react.views.view.ReactViewBackgroundDrawable;

/**
* A simple subclass of ScrollView that doesn't dispatch measure and layout to its children and has
Expand All @@ -46,8 +50,9 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
private final OverScroller mScroller;
private final VelocityHelper mVelocityHelper = new VelocityHelper();

private @Nullable Runnable mPostTouchRunnable;
private @Nullable Rect mClippingRect;
private boolean mActivelyScrolling;
private boolean mDoneFlinging;
private boolean mDragging;
private boolean mFlinging;
Expand All @@ -61,6 +66,24 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
private View mContentView;
private ReactViewBackgroundManager mReactBackgroundManager;

private boolean mPagingEnabled = false;
private int mSnapInterval = 0; // add this line

public void setPagingEnabled(boolean pagingEnabled) {
mPagingEnabled = pagingEnabled;
}

public void setSnapInterval(int snapInterval) {
mSnapInterval = snapInterval;
}

private int getSnapInterval() {
if (mSnapInterval != 0) {
return mSnapInterval;
}
return getHeight();
}

public ReactScrollView(ReactContext context) {
this(context, null);
}
Expand Down Expand Up @@ -131,6 +154,17 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
MeasureSpec.getSize(heightMeasureSpec));
}

private void smoothScrollToPage(int velocity) {
int height = getSnapInterval();
int currentY = getScrollY();
// TODO (t11123799) - Should we do anything beyond linear accounting of the velocity
int predictedY = currentY + velocity;
int page = currentY / height;
if (predictedY > page * height + height / 2) {
page = page + 1;
}
smoothScrollTo(getScrollX(), page * height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Call with the present values in order to re-layout if necessary
Expand Down Expand Up @@ -246,54 +280,58 @@ public void getClippingRect(Rect outClippingRect) {

@Override
public void fling(int velocityY) {
if (mScroller != null) {
// FB SCROLLVIEW CHANGE

// We provide our own version of fling that uses a different call to the standard OverScroller
// which takes into account the possibility of adding new content while the ScrollView is
// animating. Because we give essentially no max Y for the fling, the fling will continue as long
// as there is content. See #onOverScrolled() to see the second part of this change which properly
// aborts the scroller animation when we get to the bottom of the ScrollView content.

int scrollWindowHeight = getHeight() - getPaddingBottom() - getPaddingTop();

mScroller.fling(
getScrollX(),
getScrollY(),
0,
velocityY,
0,
0,
0,
Integer.MAX_VALUE,
0,
scrollWindowHeight / 2);

postInvalidateOnAnimation();

// END FB SCROLLVIEW CHANGE
if (mPagingEnabled || mSnapInterval != 0) {
smoothScrollToPage(velocityY);
} else {
super.fling(velocityY);
}
if (mScroller != null) {
// FB SCROLLVIEW CHANGE

// We provide our own version of fling that uses a different call to the standard OverScroller
// which takes into account the possibility of adding new content while the ScrollView is
// animating. Because we give essentially no max Y for the fling, the fling will continue as long
// as there is content. See #onOverScrolled() to see the second part of this change which properly
// aborts the scroller animation when we get to the bottom of the ScrollView content.

int scrollWindowHeight = getHeight() - getPaddingBottom() - getPaddingTop();

mScroller.fling(
getScrollX(),
getScrollY(),
0,
velocityY,
0,
0,
0,
Integer.MAX_VALUE,
0,
scrollWindowHeight / 2);

postInvalidateOnAnimation();

// END FB SCROLLVIEW CHANGE
} else {
super.fling(velocityY);
}

if (mSendMomentumEvents || isScrollPerfLoggingEnabled()) {
mFlinging = true;
enableFpsListener();
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this);
Runnable r = new Runnable() {
@Override
public void run() {
if (mDoneFlinging) {
mFlinging = false;
disableFpsListener();
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this);
} else {
mDoneFlinging = true;
ReactScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
if (mSendMomentumEvents || isScrollPerfLoggingEnabled()) {
mFlinging = true;
enableFpsListener();
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this);
Runnable r = new Runnable() {
@Override
public void run() {
if (mDoneFlinging) {
mFlinging = false;
disableFpsListener();
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this);
} else {
mDoneFlinging = true;
ReactScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
}
}
}
};
postOnAnimationDelayed(r, ReactScrollViewHelper.MOMENTUM_DELAY);
};
postOnAnimationDelayed(r, ReactScrollViewHelper.MOMENTUM_DELAY);
}
}
}

Expand Down Expand Up @@ -419,4 +457,57 @@ public void setBorderStyle(@Nullable String style) {
mReactBackgroundManager.setBorderStyle(style);
}

@TargetApi(16)
private void handlePostTouchScrolling() {
// If we aren't going to do anything (send events or snap to page), we can early out.
if (!mSendMomentumEvents && !mPagingEnabled && !isScrollPerfLoggingEnabled()) {
return;
}

// Check if we are already handling this which may occur if this is called by both the touch up
// and a fling call
if (mPostTouchRunnable != null) {
return;
}

if (mSendMomentumEvents) {
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this);
}

mActivelyScrolling = false;
mPostTouchRunnable = new Runnable() {

private boolean mSnappingToPage = false;

@Override
public void run() {
if (mActivelyScrolling) {
// We are still scrolling so we just post to check again a frame later
mActivelyScrolling = false;
ReactScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
} else {
boolean doneWithAllScrolling = true;
if (mPagingEnabled && !mSnappingToPage) {
// Only if we have pagingEnabled and we have not snapped to the page do we
// need to continue checking for the scroll. And we cause that scroll by asking for it
mSnappingToPage = true;
smoothScrollToPage(0);
doneWithAllScrolling = false;
}
if (doneWithAllScrolling) {
if (mSendMomentumEvents) {
ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this);
}
ReactScrollView.this.mPostTouchRunnable = null;
disableFpsListener();
} else {
ReactScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY);
}
}
}

};
postOnAnimationDelayed(mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY);
}

}
Empty file.
Empty file.
9 changes: 9 additions & 0 deletions ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import com.facebook.yoga.YogaConstants;
import java.util.Map;
import javax.annotation.Nullable;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import android.util.DisplayMetrics;

/**
* View manager for {@link ReactScrollView} components.
Expand Down Expand Up @@ -62,6 +64,13 @@ public ReactScrollView createViewInstance(ThemedReactContext context) {
return new ReactScrollView(context, mFpsListener);
}


@ReactProp(name = "snapToInterval")
public void setSnapToInterval(ReactScrollView view, int snapToInterval) {
DisplayMetrics screenDisplayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics();
view.setSnapInterval((int)(snapToInterval * screenDisplayMetrics.density));
}

@ReactProp(name = "scrollEnabled", defaultBoolean = true)
public void setScrollEnabled(ReactScrollView view, boolean value) {
view.setScrollEnabled(value);
Expand Down
Empty file.
Empty file.
Empty file.
Binary file added android.zip
Binary file not shown.
Binary file added node_modules.zip
Binary file not shown.