Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
/* @flow */
import React, { PureComponent, PropTypes } from 'react';
import {
Animated,
View,
StyleSheet,
ScrollView,
} from 'react-native';
const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);
const styles = StyleSheet.create({
constainer: {
flex: 1,
},
header: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
},
});
type Props = {
header: React.Element<*>;
}
type State = {
headerHeight: number,
scrollAnim: Animated.Value,
offsetAnim: Animated.Value,
}
export default class CollapsingHeaderScrollView extends PureComponent<void, Props, State> {
static propTypes = {
header: PropTypes.node.isRequired,
};
state = {
headerHeight: 0,
scrollAnim: new Animated.Value(0),
offsetAnim: new Animated.Value(0),
};
componentDidMount() {
this.state.scrollAnim.addListener(this._handleScroll);
}
componentWillUnmount() {
this.state.scrollAnim.removeListener(this._handleScroll);
}
_previousScrollvalue: number = 0;
_currentScrollValue: number = 0;
_scrollEndTimer: any;
_handleLayout = e => {
if (this.state.headerHeight === e.nativeEvent.layout.height) {
return;
}
this.setState({
headerHeight: e.nativeEvent.layout.height,
});
}
_handleScroll = ({ value }) => {
this._previousScrollvalue = this._currentScrollValue;
this._currentScrollValue = value;
};
_handleScrollEndDrag = () => {
this._scrollEndTimer = setTimeout(this._handleMomentumScrollEnd, 250);
};
_handleMomentumScrollBegin = () => {
clearTimeout(this._scrollEndTimer);
};
_handleMomentumScrollEnd = () => {
const previous = this._previousScrollvalue;
const current = this._currentScrollValue;
if (previous > current || current < (this.state.headerHeight / 2)) {
// User scrolled down or scroll amount was too less, lets snap back our header
Animated.spring(this.state.offsetAnim, {
toValue: -current,
tension: 300,
friction: 35,
useNativeDriver: true,
}).start();
} else {
Animated.timing(this.state.offsetAnim, {
toValue: 0,
duration: 500,
useNativeDriver: true,
}).start();
}
};
render() {
const { header, ...rest } = this.props;
const { scrollAnim, offsetAnim } = this.state;
const translateY = this.state.headerHeight ? Animated.add(scrollAnim, offsetAnim).interpolate({
inputRange: [0, this.state.headerHeight],
outputRange: [0, -this.state.headerHeight],
extrapolate: 'clamp',
}) : 0;
return (
<View style={styles.container}>
<AnimatedScrollView
{...rest}
scrollEventThrottle={16}
onScroll={Animated.event(
[ { nativeEvent: { contentOffset: { y: this.state.scrollAnim } } } ],
{ useNativeDriver: true }
)}
onMomentumScrollBegin={this._handleMomentumScrollBegin}
onMomentumScrollEnd={this._handleMomentumScrollEnd}
onScrollEndDrag={this._handleScrollEndDrag}
/>
<Animated.View onLayout={this._handleLayout} style={[styles.header, { transform: [{ translateY }] }]}>
{header}
</Animated.View>
</View>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.