Definable distance pagination for ScrollView
Summary: This is an enhancement for ScrollView that adds the ability to paginate based on a width other than the width of the ScrollView itself. This is a fairly common pattern used on apps like Facebook, App Store, and Twitter to scroll through a horizontal set of cards or icons: ![img_8726 2](https://cloud.githubusercontent.com/assets/451050/8017899/39f9f47c-0bd2-11e5-9c1d-889452f20cf7.PNG) ![img_8727 2](https://cloud.githubusercontent.com/assets/451050/8017898/39f962dc-0bd2-11e5-98b4-461ac0f7f21b.PNG) ![img_8728 2](https://cloud.githubusercontent.com/assets/451050/8017900/39fd91a4-0bd2-11e5-8786-4cf0316295a0.PNG) After trying to accomplish this only with JS, it appears that attempting to take over an in-progress native scroll animation with JS is always going to result in some amount of jankiness and jumping. This pull request uses `scrollViewWillEndDragging` in RCTScrollView.m to adjust `targetContentOffset` based on two new optional props added to ScrollView. `snapToInterval` sets the multiple that the Closes https://github.com/facebook/react-native/pull/1532 Reviewed By: @svcscm, @trunkagent Differential Revision: D2443701 Pulled By: @vjeux
This commit is contained in:
parent
b9ef384f74
commit
dcf245a9a2
|
@ -246,6 +246,27 @@ var ScrollView = React.createClass({
|
||||||
*/
|
*/
|
||||||
stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number),
|
stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number),
|
||||||
style: StyleSheetPropType(ViewStylePropTypes),
|
style: StyleSheetPropType(ViewStylePropTypes),
|
||||||
|
/**
|
||||||
|
* When set, causes the scroll view to stop at multiples of the value of
|
||||||
|
* `snapToInterval`. This can be used for paginating through children
|
||||||
|
* that have lengths smaller than the scroll view. Used in combination
|
||||||
|
* with `snapToAlignment`.
|
||||||
|
* @platform ios
|
||||||
|
*/
|
||||||
|
snapToInterval: PropTypes.number,
|
||||||
|
/**
|
||||||
|
* When `snapToInterval` is set, `snapToAlignment` will define the relationship
|
||||||
|
* of the the snapping to the scroll view.
|
||||||
|
* - `start` (the default) will align the snap at the left (horizontal) or top (vertical)
|
||||||
|
* - `center` will align the snap in the center
|
||||||
|
* - `end` will align the snap at the right (horizontal) or bottom (vertical)
|
||||||
|
* @platform ios
|
||||||
|
*/
|
||||||
|
snapToAlignment: PropTypes.oneOf([
|
||||||
|
'start', // default
|
||||||
|
'center',
|
||||||
|
'end',
|
||||||
|
]),
|
||||||
/**
|
/**
|
||||||
* Experimental: When true, offscreen child views (whose `overflow` value is
|
* Experimental: When true, offscreen child views (whose `overflow` value is
|
||||||
* `hidden`) are removed from their native backing superview when offscreen.
|
* `hidden`) are removed from their native backing superview when offscreen.
|
||||||
|
@ -430,6 +451,8 @@ var validAttributes = {
|
||||||
scrollsToTop: true,
|
scrollsToTop: true,
|
||||||
showsHorizontalScrollIndicator: true,
|
showsHorizontalScrollIndicator: true,
|
||||||
showsVerticalScrollIndicator: true,
|
showsVerticalScrollIndicator: true,
|
||||||
|
snapToInterval: true,
|
||||||
|
snapToAlignment: true,
|
||||||
stickyHeaderIndices: {diff: deepDiffer},
|
stickyHeaderIndices: {diff: deepDiffer},
|
||||||
scrollEventThrottle: true,
|
scrollEventThrottle: true,
|
||||||
zoomScale: true,
|
zoomScale: true,
|
||||||
|
|
|
@ -44,6 +44,8 @@
|
||||||
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
|
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
|
||||||
@property (nonatomic, assign) NSTimeInterval scrollEventThrottle;
|
@property (nonatomic, assign) NSTimeInterval scrollEventThrottle;
|
||||||
@property (nonatomic, assign) BOOL centerContent;
|
@property (nonatomic, assign) BOOL centerContent;
|
||||||
|
@property (nonatomic, assign) int snapToInterval;
|
||||||
|
@property (nonatomic, copy) NSString *snapToAlignment;
|
||||||
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
|
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -620,6 +620,48 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
|
||||||
|
|
||||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
// snapToInterval
|
||||||
|
// An alternative to enablePaging which allows setting custom stopping intervals,
|
||||||
|
// smaller than a full page size. Often seen in apps which feature horizonally
|
||||||
|
// scrolling items. snapToInterval does not enforce scrolling one interval at a time
|
||||||
|
// but guarantees that the scroll will stop at an interval point.
|
||||||
|
if (self.snapToInterval) {
|
||||||
|
CGFloat snapToIntervalF = (CGFloat)self.snapToInterval;
|
||||||
|
|
||||||
|
// Find which axis to snap
|
||||||
|
BOOL isHorizontal = (scrollView.contentSize.width > self.frame.size.width);
|
||||||
|
|
||||||
|
// What is the current offset?
|
||||||
|
CGFloat targetContentOffsetAlongAxis = isHorizontal ? targetContentOffset->x : targetContentOffset->y;
|
||||||
|
|
||||||
|
// Which direction is the scroll travelling?
|
||||||
|
CGPoint translation = [scrollView.panGestureRecognizer translationInView:scrollView];
|
||||||
|
CGFloat translationAlongAxis = isHorizontal ? translation.x : translation.y;
|
||||||
|
|
||||||
|
// Offset based on desired alignment
|
||||||
|
CGFloat frameLength = isHorizontal ? self.frame.size.width : self.frame.size.height;
|
||||||
|
CGFloat alignmentOffset = 0.0f;
|
||||||
|
if ([self.snapToAlignment isEqualToString: @"center"]) {
|
||||||
|
alignmentOffset = (frameLength * 0.5f) + (snapToIntervalF * 0.5f);
|
||||||
|
} else if ([self.snapToAlignment isEqualToString: @"end"]) {
|
||||||
|
alignmentOffset = frameLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick snap point based on direction and proximity
|
||||||
|
NSInteger snapIndex = floor((targetContentOffsetAlongAxis + alignmentOffset) / snapToIntervalF);
|
||||||
|
snapIndex = (translationAlongAxis < 0) ? snapIndex + 1 : snapIndex;
|
||||||
|
CGFloat newTargetContentOffset = ( snapIndex * snapToIntervalF ) - alignmentOffset;
|
||||||
|
|
||||||
|
// Set new targetContentOffset
|
||||||
|
if (isHorizontal) {
|
||||||
|
targetContentOffset->x = newTargetContentOffset;
|
||||||
|
} else {
|
||||||
|
targetContentOffset->y = newTargetContentOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NSDictionary *userData = @{
|
NSDictionary *userData = @{
|
||||||
@"velocity": @{
|
@"velocity": @{
|
||||||
@"x": @(velocity.x),
|
@"x": @(velocity.x),
|
||||||
|
@ -631,6 +673,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeEnd reactTag:self.reactTag scrollView:scrollView userData:userData];
|
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeEnd reactTag:self.reactTag scrollView:scrollView userData:userData];
|
||||||
|
|
||||||
RCT_FORWARD_SCROLL_EVENT(scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset);
|
RCT_FORWARD_SCROLL_EVENT(scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,8 @@ RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
|
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets)
|
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets)
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int)
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString)
|
||||||
RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint)
|
RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint)
|
||||||
|
|
||||||
- (NSDictionary *)constantsToExport
|
- (NSDictionary *)constantsToExport
|
||||||
|
|
Loading…
Reference in New Issue