Skip to content

Commit

Permalink
disable momentum scrolling for horizontal ScrollView (#24045)
Browse files Browse the repository at this point in the history
Summary:
Would like feedback from the community as this may not be the best solution for all

I would like to restrict (or paginate) the fling of a horizontal ScrollView when `snapToInterval` is set. This is not currently possible with `pagingEnabled`, since the pagination works only when items are the entire width of the ScrollView.

This implementation simply restricts the predicted `targetOffset` found from the `x` velocity and replaces it with the offset when the pan gesture ended.

To get pagination working, I may paginate based on the interval by calculating the offset delta from the beginning of the gesture to current offset and restricting the scrolling behavior to the `snapToInterval`. If this is preferred, I can update this PR or make a new one, but wanted to start a discussion since it seems like there are many in the community that would like this feature  #21302 .

[General] [Added] - add prop `disableIntervalMomentum` to disable the predictive scrolling behavior of horizontal ScrollViews
Pull Request resolved: #24045

Differential Revision: D14939754

Pulled By: sahrens

fbshipit-source-id: 26be19c47dfb8eed4d7e6035df53a77451e23081
  • Loading branch information
Taylor123 authored and facebook-github-bot committed Apr 15, 2019
1 parent e28b6f3 commit faaa92b
Show file tree
Hide file tree
Showing 6 changed files with 29 additions and 1 deletion.
7 changes: 7 additions & 0 deletions Libraries/Components/ScrollView/ScrollView.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,13 @@ export type Props = $ReadOnly<{|
* ```
*/
contentContainerStyle?: ?ViewStyleProp,
/**
* When true, the scroll view stops on the next index (in relation to scroll
* position at release) regardless of how fast the gesture is. This can be
* used for horizontal pagination when the page is less than the width of
* the ScrollView. The default value is false.
*/
disableIntervalMomentum?: ?boolean,
/**
* A floating-point number that determines how quickly the scroll view
* decelerates after the user lifts their finger. You may also use string
Expand Down
1 change: 1 addition & 0 deletions React/Views/ScrollView/RCTScrollView.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
@property (nonatomic, copy) NSDictionary *maintainVisibleContentPosition;
@property (nonatomic, assign) BOOL scrollToOverflowEnabled;
@property (nonatomic, assign) int snapToInterval;
@property (nonatomic, assign) BOOL disableIntervalMomentum;
@property (nonatomic, copy) NSArray<NSNumber *> *snapToOffsets;
@property (nonatomic, assign) BOOL snapToStart;
@property (nonatomic, assign) BOOL snapToEnd;
Expand Down
7 changes: 6 additions & 1 deletion React/Views/ScrollView/RCTScrollView.m
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,11 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi

// What is the current offset?
CGFloat velocityAlongAxis = isHorizontal ? velocity.x : velocity.y;
CGFloat targetContentOffsetAlongAxis = isHorizontal ? targetContentOffset->x : targetContentOffset->y;
CGFloat targetContentOffsetAlongAxis = targetContentOffset->y;
if (isHorizontal) {
// Use current scroll offset to determine the next index to snap to when momentum disabled
targetContentOffsetAlongAxis = self.disableIntervalMomentum ? scrollView.contentOffset.x : targetContentOffset->x;
}

// Offset based on desired alignment
CGFloat frameLength = isHorizontal ? self.frame.size.width : self.frame.size.height;
Expand All @@ -868,6 +872,7 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi

// Pick snap point based on direction and proximity
CGFloat fractionalIndex = (targetContentOffsetAlongAxis + alignmentOffset) / snapToIntervalF;

NSInteger snapIndex =
velocityAlongAxis > 0.0 ?
ceil(fractionalIndex) :
Expand Down
1 change: 1 addition & 0 deletions React/Views/ScrollView/RCTScrollViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ - (UIView *)view
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(scrollToOverflowEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int)
RCT_EXPORT_VIEW_PROPERTY(disableIntervalMomentum, BOOL)
RCT_EXPORT_VIEW_PROPERTY(snapToOffsets, NSArray<NSNumber *>)
RCT_EXPORT_VIEW_PROPERTY(snapToStart, BOOL)
RCT_EXPORT_VIEW_PROPERTY(snapToEnd, BOOL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
private @Nullable String mScrollPerfTag;
private @Nullable Drawable mEndBackground;
private int mEndFillColor = Color.TRANSPARENT;
private boolean mDisableIntervalMomentum = false;
private int mSnapInterval = 0;
private float mDecelerationRate = 0.985f;
private @Nullable List<Integer> mSnapOffsets;
Expand Down Expand Up @@ -141,6 +142,10 @@ public boolean getRemoveClippedSubviews() {
return mRemoveClippedSubviews;
}

public void setDisableIntervalMomentum(boolean disableIntervalMomentum) {
mDisableIntervalMomentum = disableIntervalMomentum;
}

public void setSendMomentumEvents(boolean sendMomentumEvents) {
mSendMomentumEvents = sendMomentumEvents;
}
Expand Down Expand Up @@ -579,6 +584,10 @@ private void flingAndSnap(int velocityX) {

int maximumOffset = Math.max(0, computeHorizontalScrollRange() - getWidth());
int targetOffset = predictFinalScrollPosition(velocityX);
if (mDisableIntervalMomentum) {
targetOffset = getScrollX();
}

int smallerOffset = 0;
int largerOffset = maximumOffset;
int firstOffset = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ public void setDecelerationRate(ReactHorizontalScrollView view, float decelerati
view.setDecelerationRate(decelerationRate);
}

@ReactProp(name = "disableIntervalMomentum")
public void setDisableIntervalMomentum(ReactHorizontalScrollView view, boolean disbaleIntervalMomentum) {
view.setDisableIntervalMomentum(disbaleIntervalMomentum);
}

@ReactProp(name = "snapToInterval")
public void setSnapToInterval(ReactHorizontalScrollView view, float snapToInterval) {
// snapToInterval needs to be exposed as a float because of the Javascript interface.
Expand Down

0 comments on commit faaa92b

Please sign in to comment.