diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 155bc3444e5837..cec4bf2a6600f4 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -589,6 +589,11 @@ export type Props = $ReadOnly<{| * The default value is true. */ showsVerticalScrollIndicator?: ?boolean, + /** + * When true, Sticky header is hidden when scrolling down, and dock at the top + * when scrolling up + */ + stickyHeaderHiddenOnScroll?: ?boolean, /** * An array of child indices determining which children get docked to the * top of the screen when scrolling. For example, passing @@ -1642,6 +1647,7 @@ class ScrollView extends React.Component { onLayout={event => this._onStickyHeaderLayout(index, event, key)} scrollAnimatedValue={this._scrollAnimatedValue} inverted={this.props.invertStickyHeaders} + hiddenOnScroll={this.props.stickyHeaderHiddenOnScroll} scrollViewHeight={this.state.layoutHeight}> {child} diff --git a/Libraries/Components/ScrollView/ScrollViewStickyHeader.js b/Libraries/Components/ScrollView/ScrollViewStickyHeader.js index f7acb3d1cfbd81..db5ae18682b852 100644 --- a/Libraries/Components/ScrollView/ScrollViewStickyHeader.js +++ b/Libraries/Components/ScrollView/ScrollViewStickyHeader.js @@ -9,6 +9,10 @@ */ import AnimatedImplementation from '../../Animated/AnimatedImplementation'; +import AnimatedAddition from '../../Animated/nodes/AnimatedAddition'; +import AnimatedDiffClamp from '../../Animated/nodes/AnimatedDiffClamp'; +import AnimatedNode from '../../Animated/nodes/AnimatedNode'; + import * as React from 'react'; import StyleSheet from '../../StyleSheet/StyleSheet'; import View from '../View/View'; @@ -29,6 +33,7 @@ export type Props = { // The height of the parent ScrollView. Currently only set when inverted. scrollViewHeight: ?number, nativeID?: ?string, + hiddenOnScroll?: ?boolean, ... }; @@ -50,7 +55,7 @@ class ScrollViewStickyHeader extends React.Component { translateY: null, }; - _translateY: ?AnimatedImplementation.Interpolation = null; + _translateY: ?AnimatedNode = null; _shouldRecreateTranslateY: boolean = true; _haveReceivedInitialZeroTranslateY: boolean = true; _ref: any; // TODO T53738161: flow type this, and the whole file @@ -87,12 +92,15 @@ class ScrollViewStickyHeader extends React.Component { updateTranslateListener( translateY: AnimatedImplementation.Interpolation, isFabric: boolean, + offset: AnimatedDiffClamp | null, ) { if (this._translateY != null && this._animatedValueListenerId != null) { this._translateY.removeListener(this._animatedValueListenerId); } + offset + ? (this._translateY = new AnimatedAddition(translateY, offset)) + : (this._translateY = translateY); - this._translateY = translateY; this._shouldRecreateTranslateY = false; if (!isFabric) { @@ -178,7 +186,6 @@ class ScrollViewStickyHeader extends React.Component { // eslint-disable-next-line dot-notation (this._ref && this._ref['_internalInstanceHandle']?.stateNode?.canonical) ); - // Initially and in the case of updated props or layout, we // recreate this interpolated value. Otherwise, we do not recreate // when there are state changes. @@ -259,6 +266,22 @@ class ScrollViewStickyHeader extends React.Component { outputRange, }), isFabric, + this.props.hiddenOnScroll + ? new AnimatedDiffClamp( + this.props.scrollAnimatedValue + .interpolate({ + extrapolateLeft: 'clamp', + inputRange: [layoutY, layoutY + 1], + outputRange: ([0, 1]: Array), + }) + .interpolate({ + inputRange: [0, 1], + outputRange: ([0, -1]: Array), + }), + -this.state.layoutHeight, + 0, + ) + : null, ); }