Skip to content

Instantly share code, notes, and snippets.

@quarrant
Created October 17, 2019 13:19
Show Gist options
  • Save quarrant/60692e7b5463da72d5e5f5d14cca308c to your computer and use it in GitHub Desktop.
Save quarrant/60692e7b5463da72d5e5f5d14cca308c to your computer and use it in GitHub Desktop.
StickyHeaders.tsx
import React from 'react';
import { View, Animated, Text, StyleSheet, LayoutChangeEvent, LayoutRectangle } from 'react-native';
interface Props {
sections: any[];
}
interface State {
sectionsLayouts: LayoutRectangle[];
sectionHeaderNodes: {
index: number;
layout: LayoutRectangle;
}[];
}
export default class StickySectionList extends React.Component<Props, State> {
state: State = { sectionsLayouts: [], sectionHeaderNodes: [] };
scrollViewScrollEvent = new Animated.Value(0);
sectionHeaderNodes: {
index: number;
layout: LayoutRectangle;
}[] = [];
sectionsLayout: LayoutRectangle[] = [];
firstFixedHeaderIndex: number = 0;
onLayoutSection = ({ nativeEvent }: LayoutChangeEvent, item: { title: string; data: any[] }, index: number) => {
const { sections } = this.props;
this.sectionHeaderNodes.push({ index, layout: nativeEvent.layout });
if (this.sectionHeaderNodes.length === sections.length) {
this.setState({ sectionHeaderNodes: this.sectionHeaderNodes });
}
};
renderSection = (item: { title: string; data: any[] }, index: number) => {
return (
<View key={item.title} style={styles.section} onLayout={(e) => this.onLayoutSection(e, item, index)}>
{item.data.map(this.renderItem)}
</View>
);
};
renderItem = (item: { id: string; title: string }, index: number) => {
return (
<View key={item.id} style={styles.item}>
<Text>{item.title}</Text>
</View>
);
};
render() {
const { sections } = this.props;
const { sectionHeaderNodes } = this.state;
sectionHeaderNodes.sort((a, b) => a.index - b.index);
const sectionHeaders = sectionHeaderNodes.reduce(
(headers, node, index) => {
const transformStyle = {
transform: [
{
translateX: this.scrollViewScrollEvent.interpolate({
inputRange: [
node.layout.x + 8,
node.layout.x + node.layout.width,
node.layout.x + node.layout.width + node.layout.width,
],
outputRange: [node.layout.x + 8, 8, -node.layout.width],
}),
},
],
};
return headers.concat(
<Animated.View key={sections[index].title} style={[styles.header, transformStyle]}>
<Text>{sections[index].title}</Text>
</Animated.View>,
);
},
[] as React.ReactNode[],
);
return (
<View>
<View style={styles.headers}>{sectionHeaders}</View>
<Animated.ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={20}
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
x: this.scrollViewScrollEvent,
},
},
},
])}
>
{sections.map(this.renderSection)}
</Animated.ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
headers: {
flexDirection: 'row',
paddingVertical: 4,
},
header: {
backgroundColor: 'steelblue',
paddingVertical: 4,
paddingHorizontal: 8,
},
section: {
flexDirection: 'row',
paddingVertical: 4,
paddingHorizontal: 4,
},
item: {
height: 100,
paddingVertical: 4,
paddingHorizontal: 8,
justifyContent: 'center',
backgroundColor: 'tomato',
marginHorizontal: 4,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment