Created
December 9, 2016 23:54
-
-
Save jevakallio/e95b8ee3c649eb64b2dc768be9375e11 to your computer and use it in GitHub Desktop.
React Native: Synchronized ScrollViews
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 Exponent from 'exponent'; | |
import React from 'react'; | |
import { range } from 'lodash'; | |
import { | |
StyleSheet, | |
Dimensions, | |
ScrollView, | |
Animated, | |
Text, | |
View, | |
} from 'react-native'; | |
// each page is full-screen wide and 1000px tall | |
const w = Dimensions.get('window').width; | |
const h = 1000; | |
// render arbitrary row, not important | |
const Row = ({ title, index, height, color }) => ( | |
<View | |
key={`${title}-${index}`} | |
style={{ | |
height, | |
backgroundColor: index % 2 === 0 ? 'white' : color, | |
flex: 1, | |
alignItems: 'center', | |
justifyContent: 'center' | |
}} | |
> | |
<Text>{title} #{index}</Text> | |
</View> | |
); | |
class Page extends React.Component { | |
componentDidMount() { | |
// when the scroll position of the currently visible | |
// view changes, update this scrollview position | |
this.listener = this.props.position.addListener((position) => { | |
if (!this.props.active) { | |
this._scrollInstance.scrollTo({ | |
x: 0, | |
y: position.value, | |
animated: false | |
}); | |
} | |
}); | |
} | |
componentWillUnmount() { | |
this.props.position.removeListener(this.listener); | |
} | |
render() { | |
const {name, rowCount, onScroll, active, color} = this.props; | |
return ( | |
<ScrollView | |
key={name} | |
ref={(instance) => this._scrollInstance = instance} | |
style={{flex: 1, width: w}} | |
scrollEventThrottle={100} | |
onScroll={active ? onScroll : null} | |
> | |
{range(rowCount).map((index) => ( | |
<Row | |
key={`row-${index}`} | |
color={color} | |
title={name} | |
index={index} | |
height={h / rowCount} | |
/> | |
))} | |
</ScrollView> | |
); | |
} | |
} | |
class App extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = {page: 0}; | |
// create an animated value and update it with the y-scroll | |
// position on the active scrollview when it scrolls. | |
// we will then pass the position to all the scroll views, | |
// who will synchronize their scroll states to the current position | |
// on each change | |
this.pageScrollPosition = new Animated.Value(0); | |
this.pageScrollListener = Animated.event( | |
[{nativeEvent: {contentOffset: {y: this.pageScrollPosition }}}] | |
); | |
} | |
render() { | |
return ( | |
<ScrollView | |
style={{flex: 1}} | |
horizontal={true} | |
pagingEnabled={true} | |
scrollEventThrottle={200} | |
onScroll={(e) => { | |
this.setState({ | |
page: Math.floor(e.nativeEvent.contentOffset.x / w) | |
}); | |
}} | |
> | |
<Page | |
name='One' | |
rowCount={25} | |
color='rgba(255, 0, 0, 0.2)' | |
onScroll={this.pageScrollListener} | |
position={this.pageScrollPosition} | |
active={this.state.page === 0} | |
/> | |
<Page | |
name='Two' | |
rowCount={25} | |
color='rgba(0, 255, 0, 0.2)' | |
onScroll={this.pageScrollListener} | |
position={this.pageScrollPosition} | |
active={this.state.page === 1} | |
/> | |
<Page | |
name='Three' | |
rowCount={25} | |
color='rgba(0, 0, 255, 0.2)' | |
onScroll={this.pageScrollListener} | |
position={this.pageScrollPosition} | |
active={this.state.page === 2} | |
/> | |
</ScrollView> | |
); | |
} | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
flex: 1 | |
}, | |
}); | |
Exponent.registerRootComponent(App); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Similar effect can also be achieved with a horizontal ScrollView nested inside a vertical ScrollView without having to track and sync offset of adjacent pages. Something like: