2015-02-20 04:10:52 +00:00
|
|
|
/**
|
2015-03-23 20:35:08 +00:00
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
2015-02-20 04:10:52 +00:00
|
|
|
*
|
|
|
|
* @providesModule ScrollView
|
2015-03-25 19:55:10 +00:00
|
|
|
* @flow
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
2015-03-09 16:28:58 +00:00
|
|
|
var EdgeInsetsPropType = require('EdgeInsetsPropType');
|
2015-03-09 16:28:54 +00:00
|
|
|
var Platform = require('Platform');
|
2015-03-09 16:28:58 +00:00
|
|
|
var PointPropType = require('PointPropType');
|
2015-03-18 05:22:03 +00:00
|
|
|
var RCTScrollView = require('NativeModules').UIManager.RCTScrollView;
|
2015-11-19 19:13:42 +00:00
|
|
|
var RCTScrollViewManager = require('NativeModules').ScrollViewManager;
|
2015-02-20 04:10:52 +00:00
|
|
|
var React = require('React');
|
2015-05-08 16:45:43 +00:00
|
|
|
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
2015-02-20 04:10:52 +00:00
|
|
|
var ScrollResponder = require('ScrollResponder');
|
|
|
|
var StyleSheet = require('StyleSheet');
|
2015-03-09 16:28:54 +00:00
|
|
|
var StyleSheetPropType = require('StyleSheetPropType');
|
2015-02-20 04:10:52 +00:00
|
|
|
var View = require('View');
|
|
|
|
var ViewStylePropTypes = require('ViewStylePropTypes');
|
|
|
|
|
2015-03-09 16:29:02 +00:00
|
|
|
var deepDiffer = require('deepDiffer');
|
2016-01-29 10:04:44 +00:00
|
|
|
var deprecatedPropType = require('deprecatedPropType');
|
2015-08-14 09:36:00 +00:00
|
|
|
var dismissKeyboard = require('dismissKeyboard');
|
2015-02-20 04:10:52 +00:00
|
|
|
var flattenStyle = require('flattenStyle');
|
2015-03-09 16:29:02 +00:00
|
|
|
var insetsDiffer = require('insetsDiffer');
|
2015-02-20 04:10:52 +00:00
|
|
|
var invariant = require('invariant');
|
2015-03-09 16:29:02 +00:00
|
|
|
var pointsDiffer = require('pointsDiffer');
|
2015-04-17 01:17:19 +00:00
|
|
|
var requireNativeComponent = require('requireNativeComponent');
|
2016-01-04 15:59:10 +00:00
|
|
|
var processColor = require('processColor');
|
2016-01-28 13:35:14 +00:00
|
|
|
var processDecelerationRate = require('processDecelerationRate');
|
2015-02-20 04:10:52 +00:00
|
|
|
var PropTypes = React.PropTypes;
|
|
|
|
|
|
|
|
var SCROLLVIEW = 'ScrollView';
|
|
|
|
var INNERVIEW = 'InnerScrollView';
|
|
|
|
|
|
|
|
/**
|
2015-03-09 16:28:51 +00:00
|
|
|
* Component that wraps platform ScrollView while providing
|
2015-02-20 04:10:52 +00:00
|
|
|
* integration with touch locking "responder" system.
|
|
|
|
*
|
2015-06-16 22:40:52 +00:00
|
|
|
* Keep in mind that ScrollViews must have a bounded height in order to work,
|
|
|
|
* since they contain unbounded-height children into a bounded container (via
|
|
|
|
* a scroll interaction). In order to bound the height of a ScrollView, either
|
|
|
|
* set the height of the view directly (discouraged) or make sure all parent
|
|
|
|
* views have bounded height. Forgetting to transfer `{flex: 1}` down the
|
|
|
|
* view stack can lead to errors here, which the element inspector makes
|
|
|
|
* easy to debug.
|
|
|
|
*
|
2015-02-20 04:10:52 +00:00
|
|
|
* Doesn't yet support other contained responders from blocking this scroll
|
|
|
|
* view from becoming the responder.
|
|
|
|
*/
|
2015-03-04 16:02:34 +00:00
|
|
|
var ScrollView = React.createClass({
|
|
|
|
propTypes: {
|
2015-11-18 16:24:26 +00:00
|
|
|
...View.propTypes,
|
2015-07-23 13:36:37 +00:00
|
|
|
/**
|
|
|
|
* Controls whether iOS should automatically adjust the content inset
|
|
|
|
* for scroll views that are placed behind a navigation bar or
|
|
|
|
* tab bar/ toolbar. The default value is true.
|
|
|
|
* @platform ios
|
|
|
|
*/
|
|
|
|
automaticallyAdjustContentInsets: PropTypes.bool,
|
|
|
|
/**
|
|
|
|
* The amount by which the scroll view content is inset from the edges
|
|
|
|
* of the scroll view. Defaults to `{0, 0, 0, 0}`.
|
|
|
|
* @platform ios
|
|
|
|
*/
|
|
|
|
contentInset: EdgeInsetsPropType,
|
|
|
|
/**
|
|
|
|
* Used to manually set the starting scroll offset.
|
|
|
|
* The default value is `{x: 0, y: 0}`.
|
|
|
|
* @platform ios
|
|
|
|
*/
|
|
|
|
contentOffset: PointPropType,
|
2015-03-31 21:59:14 +00:00
|
|
|
/**
|
|
|
|
* When true, the scroll view bounces when it reaches the end of the
|
|
|
|
* content if the content is larger then the scroll view along the axis of
|
|
|
|
* the scroll direction. When false, it disables all bouncing even if
|
|
|
|
* the `alwaysBounce*` props are true. The default value is true.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-03-31 21:59:14 +00:00
|
|
|
*/
|
|
|
|
bounces: PropTypes.bool,
|
2015-04-17 01:17:19 +00:00
|
|
|
/**
|
|
|
|
* When true, gestures can drive zoom past min/max and the zoom will animate
|
|
|
|
* to the min/max value at gesture end, otherwise the zoom will not exceed
|
|
|
|
* the limits.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-04-17 01:17:19 +00:00
|
|
|
*/
|
|
|
|
bouncesZoom: PropTypes.bool,
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* When true, the scroll view bounces horizontally when it reaches the end
|
|
|
|
* even if the content is smaller than the scroll view itself. The default
|
|
|
|
* value is true when `horizontal={true}` and false otherwise.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-03-09 16:29:02 +00:00
|
|
|
alwaysBounceHorizontal: PropTypes.bool,
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* When true, the scroll view bounces vertically when it reaches the end
|
|
|
|
* even if the content is smaller than the scroll view itself. The default
|
|
|
|
* value is false when `horizontal={true}` and true otherwise.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-03-09 16:29:02 +00:00
|
|
|
alwaysBounceVertical: PropTypes.bool,
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* When true, the scroll view automatically centers the content when the
|
|
|
|
* content is smaller than the scroll view bounds; when the content is
|
|
|
|
* larger than the scroll view, this property has no effect. The default
|
|
|
|
* value is false.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-03-09 16:29:02 +00:00
|
|
|
centerContent: PropTypes.bool,
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* These styles will be applied to the scroll view content container which
|
|
|
|
* wraps all of the child views. Example:
|
|
|
|
*
|
|
|
|
* return (
|
|
|
|
* <ScrollView contentContainerStyle={styles.contentContainer}>
|
|
|
|
* </ScrollView>
|
|
|
|
* );
|
|
|
|
* ...
|
|
|
|
* var styles = StyleSheet.create({
|
|
|
|
* contentContainer: {
|
|
|
|
* paddingVertical: 20
|
|
|
|
* }
|
|
|
|
* });
|
|
|
|
*/
|
|
|
|
contentContainerStyle: StyleSheetPropType(ViewStylePropTypes),
|
|
|
|
/**
|
|
|
|
* A floating-point number that determines how quickly the scroll view
|
2016-01-28 13:35:14 +00:00
|
|
|
* decelerates after the user lifts their finger. You may also use string
|
|
|
|
* shortcuts `"normal"` and `"fast"` which match the underlying iOS settings
|
|
|
|
* for `UIScrollViewDecelerationRateNormal` and
|
|
|
|
* `UIScrollViewDecelerationRateFast` respectively.
|
2015-03-03 18:22:29 +00:00
|
|
|
* - Normal: 0.998 (the default)
|
|
|
|
* - Fast: 0.9
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2016-01-28 13:35:14 +00:00
|
|
|
decelerationRate: PropTypes.oneOfType([
|
|
|
|
PropTypes.oneOf(['fast', 'normal']),
|
|
|
|
PropTypes.number,
|
|
|
|
]),
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* When true, the scroll view's children are arranged horizontally in a row
|
|
|
|
* instead of vertically in a column. The default value is false.
|
|
|
|
*/
|
|
|
|
horizontal: PropTypes.bool,
|
2016-01-27 18:16:10 +00:00
|
|
|
/**
|
|
|
|
* The style of the scroll indicators.
|
|
|
|
* - `default` (the default), same as `black`.
|
|
|
|
* - `black`, scroll indicator is black. This style is good against a white content background.
|
|
|
|
* - `white`, scroll indicator is white. This style is good against a black content background.
|
|
|
|
* @platform ios
|
|
|
|
*/
|
|
|
|
indicatorStyle: PropTypes.oneOf([
|
|
|
|
'default', // default
|
|
|
|
'black',
|
|
|
|
'white',
|
|
|
|
]),
|
2015-04-17 01:17:19 +00:00
|
|
|
/**
|
|
|
|
* When true, the ScrollView will try to lock to only vertical or horizontal
|
|
|
|
* scrolling while dragging. The default value is false.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-04-17 01:17:19 +00:00
|
|
|
*/
|
|
|
|
directionalLockEnabled: PropTypes.bool,
|
|
|
|
/**
|
|
|
|
* When false, once tracking starts, won't try to drag if the touch moves.
|
|
|
|
* The default value is true.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-04-17 01:17:19 +00:00
|
|
|
*/
|
|
|
|
canCancelContentTouches: PropTypes.bool,
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* Determines whether the keyboard gets dismissed in response to a drag.
|
2015-03-03 18:22:29 +00:00
|
|
|
* - 'none' (the default), drags do not dismiss the keyboard.
|
2015-07-23 13:36:37 +00:00
|
|
|
* - 'on-drag', the keyboard is dismissed when a drag begins.
|
2015-08-14 09:36:00 +00:00
|
|
|
* - 'interactive', the keyboard is dismissed interactively with the drag and moves in
|
|
|
|
* synchrony with the touch; dragging upwards cancels the dismissal.
|
|
|
|
* On android this is not supported and it will have the same behavior as 'none'.
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-03-03 18:22:29 +00:00
|
|
|
keyboardDismissMode: PropTypes.oneOf([
|
|
|
|
'none', // default
|
|
|
|
'interactive',
|
2015-06-05 15:46:17 +00:00
|
|
|
'on-drag',
|
2015-03-03 18:22:29 +00:00
|
|
|
]),
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* When false, tapping outside of the focused text input when the keyboard
|
|
|
|
* is up dismisses the keyboard. When true, the scroll view will not catch
|
|
|
|
* taps, and the keyboard will not dismiss automatically. The default value
|
|
|
|
* is false.
|
|
|
|
*/
|
2015-03-09 16:29:02 +00:00
|
|
|
keyboardShouldPersistTaps: PropTypes.bool,
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* The maximum allowed zoom scale. The default value is 1.0.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-03-09 16:29:02 +00:00
|
|
|
maximumZoomScale: PropTypes.number,
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* The minimum allowed zoom scale. The default value is 1.0.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-03-09 16:29:02 +00:00
|
|
|
minimumZoomScale: PropTypes.number,
|
2015-07-23 13:36:37 +00:00
|
|
|
/**
|
|
|
|
* Fires at most once per frame during scrolling. The frequency of the
|
2015-12-15 17:08:39 +00:00
|
|
|
* events can be controlled using the `scrollEventThrottle` prop.
|
2015-07-23 13:36:37 +00:00
|
|
|
*/
|
|
|
|
onScroll: PropTypes.func,
|
|
|
|
/**
|
|
|
|
* Called when a scrolling animation ends.
|
|
|
|
* @platform ios
|
|
|
|
*/
|
|
|
|
onScrollAnimationEnd: PropTypes.func,
|
2015-11-20 15:36:23 +00:00
|
|
|
/**
|
|
|
|
* Called when scrollable content view of the ScrollView changes. It's
|
|
|
|
* implemented using onLayout handler attached to the content container
|
|
|
|
* which this ScrollView renders.
|
|
|
|
*/
|
|
|
|
onContentSizeChange: PropTypes.func,
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* When true, the scroll view stops on multiples of the scroll view's size
|
|
|
|
* when scrolling. This can be used for horizontal pagination. The default
|
|
|
|
* value is false.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-03-09 16:29:02 +00:00
|
|
|
pagingEnabled: PropTypes.bool,
|
2015-07-23 13:36:37 +00:00
|
|
|
/**
|
|
|
|
* When false, the content does not scroll.
|
|
|
|
* The default value is true.
|
|
|
|
* @platform ios
|
|
|
|
*/
|
|
|
|
scrollEnabled: PropTypes.bool,
|
|
|
|
/**
|
|
|
|
* This controls how often the scroll event will be fired while scrolling
|
|
|
|
* (in events per seconds). A higher number yields better accuracy for code
|
|
|
|
* that is tracking the scroll position, but can lead to scroll performance
|
|
|
|
* problems due to the volume of information being send over the bridge.
|
|
|
|
* The default value is zero, which means the scroll event will be sent
|
|
|
|
* only once each time the view is scrolled.
|
|
|
|
* @platform ios
|
|
|
|
*/
|
|
|
|
scrollEventThrottle: PropTypes.number,
|
|
|
|
/**
|
|
|
|
* The amount by which the scroll view indicators are inset from the edges
|
|
|
|
* of the scroll view. This should normally be set to the same value as
|
|
|
|
* the `contentInset`. Defaults to `{0, 0, 0, 0}`.
|
|
|
|
* @platform ios
|
|
|
|
*/
|
|
|
|
scrollIndicatorInsets: EdgeInsetsPropType,
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* When true, the scroll view scrolls to top when the status bar is tapped.
|
|
|
|
* The default value is true.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-03-09 16:29:02 +00:00
|
|
|
scrollsToTop: PropTypes.bool,
|
2015-12-16 08:47:43 +00:00
|
|
|
/**
|
|
|
|
* When true, momentum events will be sent from Android
|
|
|
|
* This is internal and set automatically by the framework if you have
|
|
|
|
* onMomentumScrollBegin or onMomentumScrollEnd set on your ScrollView
|
|
|
|
* @platform android
|
|
|
|
*/
|
|
|
|
sendMomentumEvents: PropTypes.bool,
|
2015-07-23 13:36:37 +00:00
|
|
|
/**
|
|
|
|
* When true, shows a horizontal scroll indicator.
|
|
|
|
*/
|
|
|
|
showsHorizontalScrollIndicator: PropTypes.bool,
|
|
|
|
/**
|
|
|
|
* When true, shows a vertical scroll indicator.
|
|
|
|
*/
|
|
|
|
showsVerticalScrollIndicator: PropTypes.bool,
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* An array of child indices determining which children get docked to the
|
|
|
|
* top of the screen when scrolling. For example, passing
|
|
|
|
* `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the
|
|
|
|
* top of the scroll view. This property is not supported in conjunction
|
|
|
|
* with `horizontal={true}`.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-03-09 16:29:02 +00:00
|
|
|
stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number),
|
2015-07-23 13:36:37 +00:00
|
|
|
style: StyleSheetPropType(ViewStylePropTypes),
|
2015-09-23 18:43:57 +00:00
|
|
|
/**
|
|
|
|
* 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',
|
|
|
|
]),
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* Experimental: When true, offscreen child views (whose `overflow` value is
|
|
|
|
* `hidden`) are removed from their native backing superview when offscreen.
|
2015-05-03 02:51:18 +00:00
|
|
|
* This can improve scrolling performance on long lists. The default value is
|
2015-11-24 15:04:35 +00:00
|
|
|
* true.
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
|
|
|
removeClippedSubviews: PropTypes.bool,
|
|
|
|
/**
|
|
|
|
* The current scale of the scroll view content. The default value is 1.0.
|
2015-07-23 13:36:37 +00:00
|
|
|
* @platform ios
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-03-09 16:29:02 +00:00
|
|
|
zoomScale: PropTypes.number,
|
2015-11-19 19:13:42 +00:00
|
|
|
|
|
|
|
/**
|
2016-01-04 15:59:10 +00:00
|
|
|
* A RefreshControl component, used to provide pull-to-refresh
|
|
|
|
* functionality for the ScrollView.
|
2016-01-13 19:33:37 +00:00
|
|
|
*
|
|
|
|
* See [RefreshControl](http://facebook.github.io/react-native/docs/refreshcontrol.html).
|
2016-01-04 15:59:10 +00:00
|
|
|
*/
|
|
|
|
refreshControl: PropTypes.element,
|
|
|
|
|
|
|
|
/**
|
2015-11-19 19:13:42 +00:00
|
|
|
* @platform ios
|
|
|
|
*/
|
2016-01-29 10:04:44 +00:00
|
|
|
onRefreshStart: deprecatedPropType(
|
|
|
|
PropTypes.func,
|
|
|
|
'Use the `refreshControl` prop instead.'
|
|
|
|
),
|
2015-02-20 04:10:52 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
mixins: [ScrollResponder.Mixin],
|
|
|
|
|
|
|
|
getInitialState: function() {
|
|
|
|
return this.scrollResponderMixinGetInitialState();
|
|
|
|
},
|
|
|
|
|
2015-03-25 19:55:10 +00:00
|
|
|
setNativeProps: function(props: Object) {
|
2015-02-20 04:10:52 +00:00
|
|
|
this.refs[SCROLLVIEW].setNativeProps(props);
|
|
|
|
},
|
|
|
|
|
2015-11-19 19:13:42 +00:00
|
|
|
endRefreshing: function() {
|
|
|
|
RCTScrollViewManager.endRefreshing(
|
|
|
|
React.findNodeHandle(this)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2015-06-12 18:17:46 +00:00
|
|
|
/**
|
|
|
|
* Returns a reference to the underlying scroll responder, which supports
|
|
|
|
* operations like `scrollTo`. All ScrollView-like components should
|
|
|
|
* implement this method so that they can be composed while providing access
|
|
|
|
* to the underlying scroll responder's methods.
|
|
|
|
*/
|
|
|
|
getScrollResponder: function(): ReactComponent {
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
2015-03-25 19:55:10 +00:00
|
|
|
getInnerViewNode: function(): any {
|
2015-05-13 01:23:33 +00:00
|
|
|
return React.findNodeHandle(this.refs[INNERVIEW]);
|
2015-02-20 04:10:52 +00:00
|
|
|
},
|
|
|
|
|
2016-01-14 15:41:24 +00:00
|
|
|
scrollTo: function(destY: number = 0, destX: number = 0, animated: boolean = true) {
|
2015-07-24 00:57:42 +00:00
|
|
|
// $FlowFixMe - Don't know how to pass Mixin correctly. Postpone for now
|
2016-01-14 15:41:24 +00:00
|
|
|
this.getScrollResponder().scrollResponderScrollTo(destX, destY, animated);
|
2015-02-20 04:10:52 +00:00
|
|
|
},
|
|
|
|
|
2016-01-14 15:41:24 +00:00
|
|
|
/**
|
|
|
|
* Deprecated, do not use.
|
|
|
|
*/
|
|
|
|
scrollWithoutAnimationTo: function(destY: number = 0, destX: number = 0) {
|
|
|
|
console.warn('`scrollWithoutAnimationTo` is deprecated. Use `scrollTo` instead');
|
|
|
|
this.scrollTo(destX, destY, false);
|
2015-03-31 23:04:48 +00:00
|
|
|
},
|
|
|
|
|
2015-11-20 17:51:58 +00:00
|
|
|
handleScroll: function(e: Object) {
|
2015-07-24 00:50:16 +00:00
|
|
|
if (__DEV__) {
|
|
|
|
if (this.props.onScroll && !this.props.scrollEventThrottle) {
|
|
|
|
console.log(
|
|
|
|
'You specified `onScroll` on a <ScrollView> but not ' +
|
|
|
|
'`scrollEventThrottle`. You will only receive one event. ' +
|
|
|
|
'Using `16` you get all the events but be aware that it may ' +
|
|
|
|
'cause frame drops, use a bigger number if you don\'t need as ' +
|
|
|
|
'much precision.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2015-08-14 09:36:00 +00:00
|
|
|
if (Platform.OS === 'android') {
|
|
|
|
if (this.props.keyboardDismissMode === 'on-drag') {
|
|
|
|
dismissKeyboard();
|
|
|
|
}
|
|
|
|
}
|
2015-07-24 00:50:16 +00:00
|
|
|
this.scrollResponderHandleScroll(e);
|
|
|
|
},
|
|
|
|
|
2015-11-20 17:51:58 +00:00
|
|
|
_handleContentOnLayout: function(e: Object) {
|
|
|
|
var {width, height} = e.nativeEvent.layout;
|
2015-11-20 15:36:23 +00:00
|
|
|
this.props.onContentSizeChange && this.props.onContentSizeChange(width, height);
|
|
|
|
},
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
render: function() {
|
|
|
|
var contentContainerStyle = [
|
|
|
|
this.props.horizontal && styles.contentContainerHorizontal,
|
|
|
|
this.props.contentContainerStyle,
|
|
|
|
];
|
|
|
|
if (__DEV__ && this.props.style) {
|
|
|
|
var style = flattenStyle(this.props.style);
|
|
|
|
var childLayoutProps = ['alignItems', 'justifyContent']
|
2015-03-25 19:55:10 +00:00
|
|
|
.filter((prop) => style && style[prop] !== undefined);
|
2015-02-20 04:10:52 +00:00
|
|
|
invariant(
|
|
|
|
childLayoutProps.length === 0,
|
|
|
|
'ScrollView child layout (' + JSON.stringify(childLayoutProps) +
|
|
|
|
') must by applied through the contentContainerStyle prop.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-11-20 15:36:23 +00:00
|
|
|
var contentSizeChangeProps = {};
|
|
|
|
if (this.props.onContentSizeChange) {
|
|
|
|
contentSizeChangeProps = {
|
|
|
|
onLayout: this._handleContentOnLayout,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
var contentContainer =
|
|
|
|
<View
|
2015-11-20 15:36:23 +00:00
|
|
|
{...contentSizeChangeProps}
|
2015-02-20 04:10:52 +00:00
|
|
|
ref={INNERVIEW}
|
|
|
|
style={contentContainerStyle}
|
2015-08-01 05:03:05 +00:00
|
|
|
removeClippedSubviews={this.props.removeClippedSubviews}
|
|
|
|
collapsable={false}>
|
2015-07-16 23:07:19 +00:00
|
|
|
{this.props.children}
|
2015-02-20 04:10:52 +00:00
|
|
|
</View>;
|
|
|
|
|
|
|
|
var alwaysBounceHorizontal =
|
|
|
|
this.props.alwaysBounceHorizontal !== undefined ?
|
|
|
|
this.props.alwaysBounceHorizontal :
|
|
|
|
this.props.horizontal;
|
|
|
|
|
|
|
|
var alwaysBounceVertical =
|
|
|
|
this.props.alwaysBounceVertical !== undefined ?
|
|
|
|
this.props.alwaysBounceVertical :
|
|
|
|
!this.props.horizontal;
|
|
|
|
|
2015-03-09 16:29:02 +00:00
|
|
|
var props = {
|
|
|
|
...this.props,
|
|
|
|
alwaysBounceHorizontal,
|
|
|
|
alwaysBounceVertical,
|
2015-03-25 19:55:10 +00:00
|
|
|
style: ([styles.base, this.props.style]: ?Array<any>),
|
2015-03-09 16:29:02 +00:00
|
|
|
onTouchStart: this.scrollResponderHandleTouchStart,
|
|
|
|
onTouchMove: this.scrollResponderHandleTouchMove,
|
|
|
|
onTouchEnd: this.scrollResponderHandleTouchEnd,
|
|
|
|
onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag,
|
|
|
|
onScrollEndDrag: this.scrollResponderHandleScrollEndDrag,
|
|
|
|
onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin,
|
|
|
|
onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd,
|
|
|
|
onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder,
|
|
|
|
onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture,
|
|
|
|
onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder,
|
2015-07-24 00:50:16 +00:00
|
|
|
onScroll: this.handleScroll,
|
2015-03-09 16:29:02 +00:00
|
|
|
onResponderGrant: this.scrollResponderHandleResponderGrant,
|
|
|
|
onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest,
|
|
|
|
onResponderTerminate: this.scrollResponderHandleTerminate,
|
|
|
|
onResponderRelease: this.scrollResponderHandleResponderRelease,
|
|
|
|
onResponderReject: this.scrollResponderHandleResponderReject,
|
2015-12-16 08:47:43 +00:00
|
|
|
sendMomentumEvents: (this.props.onMomentumScrollBegin || this.props.onMomentumScrollEnd) ? true : false,
|
2015-03-09 16:29:02 +00:00
|
|
|
};
|
2015-06-10 20:42:41 +00:00
|
|
|
|
2015-11-19 19:13:42 +00:00
|
|
|
var onRefreshStart = this.props.onRefreshStart;
|
2016-01-04 15:59:10 +00:00
|
|
|
if (onRefreshStart) {
|
|
|
|
// this is necessary because if we set it on props, even when empty,
|
|
|
|
// it'll trigger the default pull-to-refresh behavior on native.
|
|
|
|
props.onRefreshStart =
|
|
|
|
function() { onRefreshStart && onRefreshStart(this.endRefreshing); }.bind(this);
|
|
|
|
}
|
2016-01-28 13:35:14 +00:00
|
|
|
|
|
|
|
var { decelerationRate } = this.props;
|
|
|
|
if (decelerationRate) {
|
|
|
|
props.decelerationRate = processDecelerationRate(decelerationRate);
|
|
|
|
}
|
2015-11-19 19:13:42 +00:00
|
|
|
|
2015-03-09 16:28:54 +00:00
|
|
|
var ScrollViewClass;
|
|
|
|
if (Platform.OS === 'ios') {
|
|
|
|
ScrollViewClass = RCTScrollView;
|
|
|
|
} else if (Platform.OS === 'android') {
|
|
|
|
if (this.props.horizontal) {
|
|
|
|
ScrollViewClass = AndroidHorizontalScrollView;
|
|
|
|
} else {
|
|
|
|
ScrollViewClass = AndroidScrollView;
|
|
|
|
}
|
|
|
|
}
|
2015-03-25 19:55:10 +00:00
|
|
|
invariant(
|
|
|
|
ScrollViewClass !== undefined,
|
|
|
|
'ScrollViewClass must not be undefined'
|
|
|
|
);
|
2015-03-09 16:28:54 +00:00
|
|
|
|
2016-01-04 15:59:10 +00:00
|
|
|
var refreshControl = this.props.refreshControl;
|
|
|
|
if (refreshControl) {
|
|
|
|
if (Platform.OS === 'ios') {
|
|
|
|
// On iOS the RefreshControl is a child of the ScrollView.
|
|
|
|
return (
|
|
|
|
<ScrollViewClass {...props} ref={SCROLLVIEW}>
|
|
|
|
{refreshControl}
|
|
|
|
{contentContainer}
|
|
|
|
</ScrollViewClass>
|
|
|
|
);
|
|
|
|
} else if (Platform.OS === 'android') {
|
|
|
|
// On Android wrap the ScrollView with a AndroidSwipeRefreshLayout.
|
|
|
|
// Since the ScrollView is wrapped add the style props to the
|
|
|
|
// AndroidSwipeRefreshLayout and use flex: 1 for the ScrollView.
|
|
|
|
var refreshProps = refreshControl.props;
|
|
|
|
return (
|
|
|
|
<AndroidSwipeRefreshLayout
|
|
|
|
{...refreshProps}
|
|
|
|
colors={refreshProps.colors && refreshProps.colors.map(processColor)}
|
|
|
|
style={props.style}>
|
|
|
|
<ScrollViewClass {...props} style={styles.base} ref={SCROLLVIEW}>
|
|
|
|
{contentContainer}
|
|
|
|
</ScrollViewClass>
|
|
|
|
</AndroidSwipeRefreshLayout>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
return (
|
2015-03-09 16:28:54 +00:00
|
|
|
<ScrollViewClass {...props} ref={SCROLLVIEW}>
|
2015-02-20 04:10:52 +00:00
|
|
|
{contentContainer}
|
2015-03-09 16:28:54 +00:00
|
|
|
</ScrollViewClass>
|
2015-02-20 04:10:52 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var styles = StyleSheet.create({
|
|
|
|
base: {
|
|
|
|
flex: 1,
|
|
|
|
},
|
|
|
|
contentContainerHorizontal: {
|
|
|
|
alignSelf: 'flex-start',
|
|
|
|
flexDirection: 'row',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2015-03-09 16:29:02 +00:00
|
|
|
var validAttributes = {
|
2015-05-08 16:45:43 +00:00
|
|
|
...ReactNativeViewAttributes.UIView,
|
2015-03-09 16:29:02 +00:00
|
|
|
alwaysBounceHorizontal: true,
|
|
|
|
alwaysBounceVertical: true,
|
|
|
|
automaticallyAdjustContentInsets: true,
|
2015-03-31 21:59:14 +00:00
|
|
|
bounces: true,
|
2015-03-09 16:29:02 +00:00
|
|
|
centerContent: true,
|
2015-03-12 10:10:25 +00:00
|
|
|
contentInset: {diff: insetsDiffer},
|
|
|
|
contentOffset: {diff: pointsDiffer},
|
2015-03-09 16:29:02 +00:00
|
|
|
decelerationRate: true,
|
|
|
|
horizontal: true,
|
2016-01-27 18:16:10 +00:00
|
|
|
indicatorStyle: true,
|
2015-03-09 16:29:02 +00:00
|
|
|
keyboardDismissMode: true,
|
|
|
|
keyboardShouldPersistTaps: true,
|
|
|
|
maximumZoomScale: true,
|
|
|
|
minimumZoomScale: true,
|
|
|
|
pagingEnabled: true,
|
|
|
|
removeClippedSubviews: true,
|
|
|
|
scrollEnabled: true,
|
2015-03-12 10:10:25 +00:00
|
|
|
scrollIndicatorInsets: {diff: insetsDiffer},
|
2015-03-09 16:29:02 +00:00
|
|
|
scrollsToTop: true,
|
|
|
|
showsHorizontalScrollIndicator: true,
|
|
|
|
showsVerticalScrollIndicator: true,
|
2015-09-23 18:43:57 +00:00
|
|
|
snapToInterval: true,
|
|
|
|
snapToAlignment: true,
|
2015-03-12 10:10:25 +00:00
|
|
|
stickyHeaderIndices: {diff: deepDiffer},
|
2015-03-30 11:58:02 +00:00
|
|
|
scrollEventThrottle: true,
|
2015-03-09 16:29:02 +00:00
|
|
|
zoomScale: true,
|
|
|
|
};
|
|
|
|
|
2015-03-09 16:28:54 +00:00
|
|
|
if (Platform.OS === 'android') {
|
2015-11-18 16:24:26 +00:00
|
|
|
var AndroidScrollView = requireNativeComponent('RCTScrollView', ScrollView);
|
|
|
|
var AndroidHorizontalScrollView = requireNativeComponent(
|
|
|
|
'AndroidHorizontalScrollView',
|
|
|
|
ScrollView
|
|
|
|
);
|
2016-01-04 15:59:10 +00:00
|
|
|
var AndroidSwipeRefreshLayout = requireNativeComponent('AndroidSwipeRefreshLayout');
|
2015-03-09 16:28:54 +00:00
|
|
|
} else if (Platform.OS === 'ios') {
|
2015-04-17 01:17:19 +00:00
|
|
|
var RCTScrollView = requireNativeComponent('RCTScrollView', ScrollView);
|
2015-03-09 16:28:54 +00:00
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
module.exports = ScrollView;
|