Last active
September 15, 2020 21:13
-
-
Save williamtran29/a4559a878f0b55b10a8f8a71b330ecf8 to your computer and use it in GitHub Desktop.
[React Native ListView] Scroll to a section by a sectionId, Scroll to a row by a rowId, Receive callbacks when a section changes, Pinned Section and more...
This file contains hidden or 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, { | |
Component, | |
PropTypes | |
} from 'react' | |
import { | |
ListView | |
} from 'react-native' | |
// arbitrarily large distance to pre-render all sections for measurements | |
const RENDER_AHEAD_DISTANCE = 1000000 | |
const scrollOffset = y => ({ | |
y, | |
x: 0, | |
animated: true | |
}) | |
class SectionListView extends Component { | |
static DataSource = ListView.DataSource | |
static propTypes = { | |
...ListView.propTypes, | |
onSectionChanged: PropTypes.func | |
} | |
static defaultProps = { | |
...ListView.defaultProps, | |
// arbitrarily large distance to pre-render all sections for measurements | |
scrollRenderAheadDistance: RENDER_AHEAD_DISTANCE, | |
scrollEventThrottle: 1 | |
} | |
constructor(props) { | |
super(props) | |
// this data should never trigger a render pass, so it stays out of state. | |
this.sections = [] | |
this.rowOffsets = {} | |
this.currentSection = null | |
} | |
onSectionHeaderLayout = (sectionId, event) => { | |
const offset = event.nativeEvent.layout.y | |
let sections = this.sections.filter(section => section.section !== sectionId) | |
sections.push({ | |
offset, | |
section: sectionId | |
}) | |
sections = sections.sort((a, b) => { | |
if (a.offset < b.offset) { | |
return -1 | |
} else if (a.offset > b.offset) { | |
return 1 | |
} | |
return 0 | |
}) | |
this.sections = sections | |
} | |
onRowLayout = (sectionId, rowId, event) => { | |
if (!this.rowOffsets[sectionId]) { | |
this.rowOffsets[sectionId] = {} | |
} | |
this.rowOffsets[sectionId][rowId] = event.nativeEvent.layout.y | |
} | |
onScroll = (event) => { | |
if (this.props.onScroll) { | |
this.props.onScroll(event) | |
} | |
if (!this.props.onSectionChanged) { | |
return | |
} | |
const offset = event.nativeEvent.contentOffset.y | |
let section | |
this.sections.forEach((currentSection) => { | |
if (currentSection.offset <= offset) { | |
section = currentSection.section | |
} | |
}) | |
if (section && this.currentSection !== section) { | |
this.currentSection = section | |
if (this.props.onSectionChanged) { | |
this.props.onSectionChanged(section) | |
} | |
} | |
} | |
scrollToSection = (sectionId) => { | |
if (this.listView) { | |
const section = this.sections.find(item => item.section === sectionId) | |
if (section) { | |
this.listView.getScrollResponder().scrollTo(scrollOffset(section.offset)) | |
} | |
} | |
} | |
scrollToRow = (sectionId, rowId) => { | |
if (this.listView && this.rowOffsets[sectionId] && this.rowOffsets[sectionId][rowId]) { | |
this.listView.getScrollResponder().scrollTo(scrollOffset(this.rowOffsets[sectionId][rowId])) | |
} | |
} | |
renderSectionHeader = (sectionData, sectionId) => { | |
return React.cloneElement(this.props.renderSectionHeader(sectionData, sectionId), { | |
onLayout: event => this.onSectionHeaderLayout(sectionId, event) | |
}) | |
} | |
renderRow = (data, sectionId, rowId) => { | |
return React.cloneElement(this.props.renderRow(data, sectionId, rowId), { | |
onLayout: event => this.onRowLayout(sectionId, rowId, event) | |
}) | |
} | |
render() { | |
return ( | |
<ListView | |
{...this.props} | |
ref={component => this.listView = component} | |
onScroll={this.onScroll} | |
renderRow={this.renderRow} | |
renderSectionHeader={this.renderSectionHeader} | |
/> | |
) | |
} | |
} | |
export default SectionListView |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment