mirror of
https://github.com/status-im/react-native.git
synced 2025-01-15 12:05:06 +00:00
77b8c09727
Summary: This re-implements sticky headers in JS to make it work on Android. The only change that was needed was to expose a way to attach a an animated value to an event manually since we can't use the Animated wrapper and `Animated.event` to do it for us because this is implemented directly in the `ScrollView` component. Simply exposed `attachNativeEvent` that takes a ref, event name and event object mapping. This is what is used by `Animated.event`. TODO: - Need to check why momentum scrolling isn't triggering scroll events properly on Android. - Remove native iOS implementation - cleanup / fix flow **Test plan** Test the example list in UIExplorer, test the ListViewPaging example. Closes https://github.com/facebook/react-native/pull/11315 Differential Revision: D4450278 Pulled By: sahrens fbshipit-source-id: fec8da2cffce9807d74f8e518ebdefeb6a708667
102 lines
2.8 KiB
JavaScript
102 lines
2.8 KiB
JavaScript
/**
|
|
* 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.
|
|
*
|
|
* @providesModule ScrollViewStickyHeader
|
|
* @flow
|
|
*/
|
|
'use strict';
|
|
|
|
const Animated = require('Animated');
|
|
const React = require('React');
|
|
const StyleSheet = require('StyleSheet');
|
|
|
|
type Props = {
|
|
children?: React.Element<*>,
|
|
scrollAnimatedValue: Animated.Value,
|
|
onLayout: (event: Object) => void,
|
|
};
|
|
|
|
class ScrollViewStickyHeader extends React.Component {
|
|
props: Props;
|
|
state = {
|
|
measured: false,
|
|
layoutY: 0,
|
|
nextHeaderLayoutY: (null: ?number),
|
|
};
|
|
|
|
setNextHeaderY(y: number) {
|
|
this.setState({ nextHeaderLayoutY: y });
|
|
}
|
|
|
|
_onLayout = (event) => {
|
|
this.setState({
|
|
measured: true,
|
|
layoutY: event.nativeEvent.layout.y,
|
|
});
|
|
|
|
this.props.onLayout(event);
|
|
};
|
|
|
|
render() {
|
|
const {measured, layoutY, nextHeaderLayoutY} = this.state;
|
|
|
|
let translateY;
|
|
if (measured) {
|
|
// The interpolation looks like:
|
|
// - Negative scroll: no translation
|
|
// - From 0 to the y of the header: no translation. This will cause the header
|
|
// to scroll normally until it reaches the top of the scroll view.
|
|
// - From the header y to the next header y: translate equally to scroll.
|
|
// This will cause the header to stay at the top of the scroll view.
|
|
// - Past the the next header y: no more translation. This will cause the header
|
|
// to continue scrolling up and make room for the next sticky header.
|
|
// In the case that there is no next header just translate equally to
|
|
// scroll indefinetly.
|
|
const inputRange = [-1, 0, layoutY];
|
|
const outputRange: Array<number> = [0, 0, 0];
|
|
if (nextHeaderLayoutY != null) {
|
|
inputRange.push(nextHeaderLayoutY, nextHeaderLayoutY + 1);
|
|
outputRange.push(nextHeaderLayoutY - layoutY, nextHeaderLayoutY - layoutY);
|
|
} else {
|
|
inputRange.push(layoutY + 1);
|
|
outputRange.push(1);
|
|
}
|
|
translateY = this.props.scrollAnimatedValue.interpolate({
|
|
inputRange,
|
|
outputRange,
|
|
});
|
|
} else {
|
|
translateY = 0;
|
|
}
|
|
|
|
const child = React.Children.only(this.props.children);
|
|
|
|
return (
|
|
<Animated.View
|
|
collapsable={false}
|
|
onLayout={this._onLayout}
|
|
style={[child.props.style, styles.header, {transform: [{translateY}]}]}>
|
|
{React.cloneElement(child, {
|
|
style: styles.fill,
|
|
})}
|
|
</Animated.View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
header: {
|
|
zIndex: 10,
|
|
},
|
|
fill: {
|
|
flex: 1,
|
|
},
|
|
});
|
|
|
|
module.exports = ScrollViewStickyHeader;
|