-
-
Save yasaminyaldaei/7ceef4ad185065b1a796ab1f19ac0f17 to your computer and use it in GitHub Desktop.
React Native Collapsible Header with Sticky Bar using Animated API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import {Animated, StyleSheet} from 'react-native'; | |
class CollapsibleHeader extends React.Component { | |
state = { | |
layoutHeight: 0, | |
clampedScroll: 0, | |
stickyHeight: this.props.stickyHeaderHeight, | |
}; | |
componentDidUpdate() { | |
const {scrollY, stickyHeaderHeight} = this.props; | |
const {layoutHeight, clampedScroll} = this.state; | |
if (stickyHeaderHeight && layoutHeight && !clampedScroll) { | |
this.setState({ | |
clampedScroll: Animated.diffClamp( | |
scrollY, | |
0, | |
layoutHeight - stickyHeaderHeight, | |
), | |
}); | |
} | |
} | |
setStickyHeight = (stickyHeight) => { | |
this.setState({ | |
stickyHeight, | |
}); | |
}; | |
onLayout = ({ | |
nativeEvent: { | |
layout: {height}, | |
}, | |
}) => { | |
this.setState({ | |
layoutHeight: height, | |
}); | |
this.props.onLayout && this.props.onLayout(height); | |
}; | |
render() { | |
const {clampedScroll, layoutHeight, stickyHeight} = this.state; | |
const translateY = | |
clampedScroll && layoutHeight && stickyHeight | |
? Animated.multiply(clampedScroll, -1) | |
: 0; | |
return ( | |
<Animated.View | |
style={[styles.container, {transform: [{translateY}]}]} | |
onLayout={this.onLayout}> | |
{this.props.children} | |
</Animated.View> | |
); | |
} | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
right: 0, | |
backgroundColor: 'black', | |
zIndex: 10, | |
}, | |
}); | |
export default CollapsibleHeader; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {createRef, Component} from 'react'; | |
import {Animated, Text, StyleSheet} from 'react-native'; | |
import ListItem from '../Components/ListItem'; | |
import CollapsibleHeader from '../Components/CollapsibleHeader'; | |
import TabBar from '../Components/TabBar'; | |
class Home extends Component { | |
scrollY = new Animated.Value(0); | |
header = createRef(null); | |
state = { | |
headerHeight: 0, | |
stickyHeaderHeight: 0, | |
data: [], | |
}; | |
componentDidMount() { | |
// Fetching data | |
} | |
onHeaderLayout = (headerHeight) => { | |
this.setState({ | |
headerHeight, | |
}); | |
}; | |
onStickyHeaderLayout = (stickyHeaderHeight) => { | |
this.setState({ | |
stickyHeaderHeight, | |
}); | |
this.header?.current?.setStickyHeight(stickyHeaderHeight); | |
}; | |
render() { | |
const {stickyHeaderHeight, data} = this.state; | |
return ( | |
<> | |
<Animated.FlatList | |
contentContainerStyle={{paddingTop: this.state.headerHeight}} | |
onScroll={Animated.event( | |
[{nativeEvent: {contentOffset: {y: this.scrollY}}}], | |
{useNativeDriver: true}, | |
)} | |
data={data} | |
renderItem={({item}) => ( | |
<ListItem name={item.name} avatar={item.avatar} id={item.id} /> | |
)} | |
keyExtractor={(item) => item.id} | |
/> | |
<CollapsibleHeader | |
ref={this.header} | |
onLayout={this.onHeaderLayout} | |
scrollY={this.scrollY} | |
stickyHeaderHeight={stickyHeaderHeight}> | |
<Text style={styles.sectionTitle}>My Awesome App</Text> | |
<TabBar onLayout={this.onStickyHeaderLayout} /> | |
</CollapsibleHeader> | |
</> | |
); | |
} | |
} | |
const styles = StyleSheet.create({ | |
sectionTitle: { | |
fontSize: 24, | |
fontWeight: '600', | |
color: 'white', | |
margin: 20, | |
}, | |
}); | |
export default Home; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import {View, Text, StyleSheet, FlatList, TouchableOpacity} from 'react-native'; | |
const TABS = [ | |
{ | |
title: 'Section 1', | |
key: 0, | |
}, | |
{ | |
title: 'Section 2', | |
key: 1, | |
}, | |
{ | |
title: 'Section 3', | |
key: 2, | |
}, | |
]; | |
class TabBar extends React.Component { | |
state = { | |
selected: 0, | |
}; | |
onViewLayout = ({ | |
nativeEvent: { | |
layout: {height, y}, | |
}, | |
}) => { | |
const {onLayout} = this.props; | |
onLayout && onLayout(height, y); | |
}; | |
onTabChange = (selected) => { | |
this.setState( | |
{ | |
selected, | |
}, | |
() => { | |
const {onChange} = this.props; | |
onChange && onChange(this.state.selected); | |
}, | |
); | |
}; | |
render() { | |
const {selected} = this.state; | |
return ( | |
<View style={styles.container} onLayout={this.onViewLayout}> | |
<FlatList | |
horizontal | |
data={TABS} | |
keyExtractor={(item) => '' + item.key} | |
renderItem={({item, index}) => ( | |
<TouchableOpacity onPress={() => this.onTabChange(index)}> | |
<Text | |
style={[ | |
styles.tab, | |
selected === index ? styles.selectedTab : null, | |
]}> | |
{item.title} | |
</Text> | |
</TouchableOpacity> | |
)} | |
/> | |
</View> | |
); | |
} | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
paddingHorizontal: 20, | |
alignItems: 'baseline', | |
backgroundColor: 'black', | |
}, | |
tab: { | |
fontSize: 14, | |
color: 'white', | |
padding: 10, | |
marginRight: 10, | |
}, | |
selectedTab: { | |
borderBottomWidth: 3, | |
borderBottomColor: 'white', | |
}, | |
}); | |
export default TabBar; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment