mirror of
https://github.com/status-im/react-native.git
synced 2025-02-26 08:05:34 +00:00
ScrollView snapToStart/snapToEnd
Summary: Added `snapToStart` and `snapToEnd` props to ScrollView which work together with `snapToOffsets` and determine whether the beginning and end of the list automatically count as snap offsets or not. If not, the list is allowed to free-scroll between its start/end and the first/last snap offset. Reviewed By: sahrens Differential Revision: D9442386 fbshipit-source-id: 47a5fdb20f884542434b01b1f0a486ed2b478c6e
This commit is contained in:
parent
fd744dd56c
commit
5f48d28119
@ -474,6 +474,22 @@ export type Props = $ReadOnly<{|
|
||||
* Overrides less configurable `pagingEnabled` and `snapToInterval` props.
|
||||
*/
|
||||
snapToOffsets?: ?$ReadOnlyArray<number>,
|
||||
/**
|
||||
* Use in conjuction with `snapToOffsets`. By default, the beginning
|
||||
* of the list counts as a snap offset. Set `snapToStart` to false to disable
|
||||
* this behavior and allow the list to scroll freely between its start and
|
||||
* the first `snapToOffsets` offset.
|
||||
* The default value is true.
|
||||
*/
|
||||
snapToStart?: ?boolean,
|
||||
/**
|
||||
* Use in conjuction with `snapToOffsets`. By default, the end
|
||||
* of the list counts as a snap offset. Set `snapToEnd` to false to disable
|
||||
* this behavior and allow the list to scroll freely between its end and
|
||||
* the last `snapToOffsets` offset.
|
||||
* The default value is true.
|
||||
*/
|
||||
snapToEnd?: ?boolean,
|
||||
/**
|
||||
* Experimental: When true, offscreen child views (whose `overflow` value is
|
||||
* `hidden`) are removed from their native backing superview when offscreen.
|
||||
@ -921,6 +937,10 @@ const ScrollView = createReactClass({
|
||||
? true
|
||||
: false,
|
||||
DEPRECATED_sendUpdatedChildFrames,
|
||||
// default to true
|
||||
snapToStart: this.props.snapToStart !== false,
|
||||
// default to true
|
||||
snapToEnd: this.props.snapToEnd !== false,
|
||||
// pagingEnabled is overridden by snapToInterval / snapToOffsets
|
||||
pagingEnabled: Platform.select({
|
||||
// on iOS, pagingEnabled must be set to false to have snapToInterval / snapToOffsets work
|
||||
|
@ -46,6 +46,8 @@
|
||||
@property (nonatomic, copy) NSDictionary *maintainVisibleContentPosition;
|
||||
@property (nonatomic, assign) int snapToInterval;
|
||||
@property (nonatomic, copy) NSArray<NSNumber *> *snapToOffsets;
|
||||
@property (nonatomic, assign) BOOL snapToStart;
|
||||
@property (nonatomic, assign) BOOL snapToEnd;
|
||||
@property (nonatomic, copy) NSString *snapToAlignment;
|
||||
|
||||
// NOTE: currently these event props are only declared so we can export the
|
||||
|
@ -736,6 +736,8 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll)
|
||||
|
||||
// Find which axis to snap
|
||||
BOOL isHorizontal = [self isHorizontal:scrollView];
|
||||
CGFloat velocityAlongAxis = isHorizontal ? velocity.x : velocity.y;
|
||||
CGFloat offsetAlongAxis = isHorizontal ? _scrollView.contentOffset.x : _scrollView.contentOffset.y;
|
||||
|
||||
// Calculate maximum content offset
|
||||
CGSize viewportSize = [self _calculateViewportSize];
|
||||
@ -769,9 +771,26 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll)
|
||||
? smallerOffset
|
||||
: largerOffset;
|
||||
|
||||
// Chose the correct snap offset based on velocity
|
||||
CGFloat velocityAlongAxis = isHorizontal ? velocity.x : velocity.y;
|
||||
if (velocityAlongAxis > 0.0) {
|
||||
CGFloat firstOffset = [[self.snapToOffsets firstObject] floatValue];
|
||||
CGFloat lastOffset = [[self.snapToOffsets lastObject] floatValue];
|
||||
|
||||
// if scrolling after the last snap offset and snapping to the
|
||||
// end of the list is disabled, then we allow free scrolling
|
||||
if (!self.snapToEnd && targetOffset >= lastOffset) {
|
||||
if (offsetAlongAxis >= lastOffset) {
|
||||
// free scrolling
|
||||
} else {
|
||||
// snap to end
|
||||
targetOffset = lastOffset;
|
||||
}
|
||||
} else if (!self.snapToStart && targetOffset <= firstOffset) {
|
||||
if (offsetAlongAxis <= firstOffset) {
|
||||
// free scrolling
|
||||
} else {
|
||||
// snap to beginning
|
||||
targetOffset = firstOffset;
|
||||
}
|
||||
} else if (velocityAlongAxis > 0.0) {
|
||||
targetOffset = largerOffset;
|
||||
} else if (velocityAlongAxis < 0.0) {
|
||||
targetOffset = smallerOffset;
|
||||
|
@ -82,6 +82,8 @@ RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets)
|
||||
RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int)
|
||||
RCT_EXPORT_VIEW_PROPERTY(snapToOffsets, NSArray<NSNumber *>)
|
||||
RCT_EXPORT_VIEW_PROPERTY(snapToStart, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(snapToEnd, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString)
|
||||
RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onScrollBeginDrag, RCTDirectEventBlock)
|
||||
|
@ -68,6 +68,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
||||
private int mSnapInterval = 0;
|
||||
private float mDecelerationRate = 0.985f;
|
||||
private @Nullable List<Integer> mSnapOffsets;
|
||||
private boolean mSnapToStart = true;
|
||||
private boolean mSnapToEnd = true;
|
||||
private ReactViewBackgroundManager mReactBackgroundManager;
|
||||
|
||||
public ReactHorizontalScrollView(Context context) {
|
||||
@ -167,6 +169,14 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
||||
mSnapOffsets = snapOffsets;
|
||||
}
|
||||
|
||||
public void setSnapToStart(boolean snapToStart) {
|
||||
mSnapToStart = snapToStart;
|
||||
}
|
||||
|
||||
public void setSnapToEnd(boolean snapToEnd) {
|
||||
mSnapToEnd = snapToEnd;
|
||||
}
|
||||
|
||||
public void flashScrollIndicators() {
|
||||
awakenScrollBars();
|
||||
}
|
||||
@ -473,6 +483,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
||||
int targetOffset = 0;
|
||||
int smallerOffset = 0;
|
||||
int largerOffset = maximumOffset;
|
||||
int firstOffset = 0;
|
||||
int lastOffset = maximumOffset;
|
||||
|
||||
// ScrollView can *only* scroll for 250ms when using smoothScrollTo and there's
|
||||
// no way to customize the scroll duration. So, we create a temporary OverScroller
|
||||
@ -505,6 +517,9 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
||||
|
||||
// get the nearest snap points to the target offset
|
||||
if (mSnapOffsets != null) {
|
||||
firstOffset = mSnapOffsets.get(0);
|
||||
lastOffset = mSnapOffsets.get(mSnapOffsets.size() - 1);
|
||||
|
||||
for (int i = 0; i < mSnapOffsets.size(); i ++) {
|
||||
int offset = mSnapOffsets.get(i);
|
||||
|
||||
@ -532,10 +547,35 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements
|
||||
? smallerOffset
|
||||
: largerOffset;
|
||||
|
||||
// Chose the correct snap offset based on velocity
|
||||
if (velocityX > 0) {
|
||||
// if scrolling after the last snap offset and snapping to the
|
||||
// end of the list is disabled, then we allow free scrolling
|
||||
int currentOffset = getScrollX();
|
||||
if (isRTL) {
|
||||
currentOffset = maximumOffset - currentOffset;
|
||||
}
|
||||
if (!mSnapToEnd && targetOffset >= lastOffset) {
|
||||
if (currentOffset >= lastOffset) {
|
||||
// free scrolling
|
||||
} else {
|
||||
// snap to end
|
||||
targetOffset = lastOffset;
|
||||
}
|
||||
} else if (!mSnapToStart && targetOffset <= firstOffset) {
|
||||
if (currentOffset <= firstOffset) {
|
||||
// free scrolling
|
||||
} else {
|
||||
// snap to beginning
|
||||
targetOffset = firstOffset;
|
||||
}
|
||||
} else if (velocityX > 0) {
|
||||
// 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);
|
||||
|
||||
targetOffset = smallerOffset;
|
||||
} else {
|
||||
targetOffset = nearestOffset;
|
||||
|
@ -97,6 +97,16 @@ public class ReactHorizontalScrollViewManager
|
||||
view.setSnapOffsets(offsets);
|
||||
}
|
||||
|
||||
@ReactProp(name = "snapToStart")
|
||||
public void setSnapToStart(ReactHorizontalScrollView view, boolean snapToStart) {
|
||||
view.setSnapToStart(snapToStart);
|
||||
}
|
||||
|
||||
@ReactProp(name = "snapToEnd")
|
||||
public void setSnapToEnd(ReactHorizontalScrollView view, boolean snapToEnd) {
|
||||
view.setSnapToEnd(snapToEnd);
|
||||
}
|
||||
|
||||
@ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
|
||||
public void setRemoveClippedSubviews(ReactHorizontalScrollView view, boolean removeClippedSubviews) {
|
||||
view.setRemoveClippedSubviews(removeClippedSubviews);
|
||||
|
@ -67,6 +67,8 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
private int mSnapInterval = 0;
|
||||
private float mDecelerationRate = 0.985f;
|
||||
private @Nullable List<Integer> mSnapOffsets;
|
||||
private boolean mSnapToStart = true;
|
||||
private boolean mSnapToEnd = true;
|
||||
private View mContentView;
|
||||
private ReactViewBackgroundManager mReactBackgroundManager;
|
||||
|
||||
@ -155,6 +157,14 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
mSnapOffsets = snapOffsets;
|
||||
}
|
||||
|
||||
public void setSnapToStart(boolean snapToStart) {
|
||||
mSnapToStart = snapToStart;
|
||||
}
|
||||
|
||||
public void setSnapToEnd(boolean snapToEnd) {
|
||||
mSnapToEnd = snapToEnd;
|
||||
}
|
||||
|
||||
public void flashScrollIndicators() {
|
||||
awakenScrollBars();
|
||||
}
|
||||
@ -441,6 +451,8 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
int targetOffset = 0;
|
||||
int smallerOffset = 0;
|
||||
int largerOffset = maximumOffset;
|
||||
int firstOffset = 0;
|
||||
int lastOffset = maximumOffset;
|
||||
|
||||
// ScrollView can *only* scroll for 250ms when using smoothScrollTo and there's
|
||||
// no way to customize the scroll duration. So, we create a temporary OverScroller
|
||||
@ -466,6 +478,9 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
|
||||
// get the nearest snap points to the target offset
|
||||
if (mSnapOffsets != null) {
|
||||
firstOffset = mSnapOffsets.get(0);
|
||||
lastOffset = mSnapOffsets.get(mSnapOffsets.size() - 1);
|
||||
|
||||
for (int i = 0; i < mSnapOffsets.size(); i ++) {
|
||||
int offset = mSnapOffsets.get(i);
|
||||
|
||||
@ -493,10 +508,31 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
||||
? smallerOffset
|
||||
: largerOffset;
|
||||
|
||||
// Chose the correct snap offset based on velocity
|
||||
if (velocityY > 0) {
|
||||
// if scrolling after the last snap offset and snapping to the
|
||||
// end of the list is disabled, then we allow free scrolling
|
||||
if (!mSnapToEnd && targetOffset >= lastOffset) {
|
||||
if (getScrollY() >= lastOffset) {
|
||||
// free scrolling
|
||||
} else {
|
||||
// snap to end
|
||||
targetOffset = lastOffset;
|
||||
}
|
||||
} else if (!mSnapToStart && targetOffset <= firstOffset) {
|
||||
if (getScrollY() <= firstOffset) {
|
||||
// free scrolling
|
||||
} else {
|
||||
// snap to beginning
|
||||
targetOffset = firstOffset;
|
||||
}
|
||||
} else if (velocityY > 0) {
|
||||
// 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);
|
||||
|
||||
targetOffset = smallerOffset;
|
||||
} else {
|
||||
targetOffset = nearestOffset;
|
||||
|
@ -101,6 +101,16 @@ public class ReactScrollViewManager
|
||||
view.setSnapOffsets(offsets);
|
||||
}
|
||||
|
||||
@ReactProp(name = "snapToStart")
|
||||
public void setSnapToStart(ReactScrollView view, boolean snapToStart) {
|
||||
view.setSnapToStart(snapToStart);
|
||||
}
|
||||
|
||||
@ReactProp(name = "snapToEnd")
|
||||
public void setSnapToEnd(ReactScrollView view, boolean snapToEnd) {
|
||||
view.setSnapToEnd(snapToEnd);
|
||||
}
|
||||
|
||||
@ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
|
||||
public void setRemoveClippedSubviews(ReactScrollView view, boolean removeClippedSubviews) {
|
||||
view.setRemoveClippedSubviews(removeClippedSubviews);
|
||||
|
Loading…
x
Reference in New Issue
Block a user