I'm working on an application where I'm trying to Animate a View
based on scroll Position of multiple ScrollView
s.
This is how the screen looks.
The above screen has 2 parts
- A
View
component on Top - A
TabNavigator
component at the Bottom
each tab in TabNavigator
has a ScrollView
in it (in this case there are 2 but can be more), What I want to achieve is to collapse the View
as the user scrolls down and expand it when the user scrolls up.
On a single Tab I was doing well, it was working exactly how I wanted it to do, but the problem came when I added the 2nd Tab.
The Problem
When I scroll a bit on tab1 and move to tab2 and try to scroll, it gets jerky. see the GIF to understand what I'm trying to say
Update
Check this snack on expo.io to see the problem live
snack.expo.io/SytBkdBAW
What I tried
App.js
export default class App extends Component {
constructor (props) {
super(props)
this.state = {
/* omitted - not related */
scrollY: new Animated.Value(0)
}
}
render () {
let translateY = this.state.scrollY.interpolate({
inputRange: [0, 600],
outputRange: [0, -290],
extrapolate: 'clamp'
});
let TabsTranslateY = this.state.scrollY.interpolate({
inputRange: [0, 600],
outputRange: [0, -290],
extrapolate: 'clamp'
});
return (
<View style={styles.container}>
<Animated.View style={{transform: [{translateY: translateY}], overflow: 'hidden'}}>
<Text style={styles.welcome}>
Welcome to React Native!!
</Text>
<Text style={styles.time}>
{this.state.hour} : {this.state.minute}
</Text>
<TouchableOpacity onPress={() => { /* omitted */ }} style={styles.button}><Text style={styles.buttonText}>Set Time</Text></TouchableOpacity>
</Animated.View>
<Animated.View style={{
flex: 0,
transform: [{translateY: TabsTranslateY}],
height: Dimensions.get('window').height
}}>
<Tabs removeClippedSubviews={false} screenProps={{animatedScrollY: this.state.scrollY}}/>
</Animated.View>
</View>
)
}
}
const styles = StyleSheet.create({/* omitted styles*/})
Home.js (Tab1)
/* omitted imports */
export default class Home extends Component {
/* omitted navigation options */
constructor (props) {
super(props)
this.state = {
scrollY: this.props.screenProps.animatedScrollY
}
}
render () {
return (
<View>
<Animated.ScrollView onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: this.state.scrollY}}}],
{useNativeDriver: true}
)} scrollEventThrottle={16}>
{Array(90).fill().map((v, i) => {
return <Text key={i}
style={{flex: 1, backgroundColor: '#333', padding: 20, marginVertical: 10, color: 'white'}}>Item
#{i + 1}</Text>
})}
</Animated.ScrollView>
</View>
)
}
}
Photos.js (Tab2)
/* omitted imports */
export default class Photos extends Component {
/* omitted navigation options */
constructor (props) {
super(props)
this.state = {
PhotosScrollY: this.props.screenProps.animatedScrollY
}
}
render () {
return (
<Animated.ScrollView onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: this.state.PhotosScrollY}}}],
{useNativeDriver: true}
)} scrollEventThrottle={16}>
<View style={{flex: 1,}}>
{Array(90).fill().map((v, i) => {
return <View key={i} style={/* omitted */}>
<Text style={/* omitted */}>
Photo #{i + 1}
</Text>
</View>
})}
</View>
</Animated.ScrollView>
)
}
}
I'm not sure how to overcome this problem, Any suggestions and solutions are appreciated.
Thanks.
I'm working on an application where I'm trying to Animate a View
based on scroll Position of multiple ScrollView
s.
This is how the screen looks.
The above screen has 2 parts
- A
View
component on Top - A
TabNavigator
component at the Bottom
each tab in TabNavigator
has a ScrollView
in it (in this case there are 2 but can be more), What I want to achieve is to collapse the View
as the user scrolls down and expand it when the user scrolls up.
On a single Tab I was doing well, it was working exactly how I wanted it to do, but the problem came when I added the 2nd Tab.
The Problem
When I scroll a bit on tab1 and move to tab2 and try to scroll, it gets jerky. see the GIF to understand what I'm trying to say
Update
Check this snack on expo.io to see the problem live
snack.expo.io/SytBkdBAW
What I tried
App.js
export default class App extends Component {
constructor (props) {
super(props)
this.state = {
/* omitted - not related */
scrollY: new Animated.Value(0)
}
}
render () {
let translateY = this.state.scrollY.interpolate({
inputRange: [0, 600],
outputRange: [0, -290],
extrapolate: 'clamp'
});
let TabsTranslateY = this.state.scrollY.interpolate({
inputRange: [0, 600],
outputRange: [0, -290],
extrapolate: 'clamp'
});
return (
<View style={styles.container}>
<Animated.View style={{transform: [{translateY: translateY}], overflow: 'hidden'}}>
<Text style={styles.welcome}>
Welcome to React Native!!
</Text>
<Text style={styles.time}>
{this.state.hour} : {this.state.minute}
</Text>
<TouchableOpacity onPress={() => { /* omitted */ }} style={styles.button}><Text style={styles.buttonText}>Set Time</Text></TouchableOpacity>
</Animated.View>
<Animated.View style={{
flex: 0,
transform: [{translateY: TabsTranslateY}],
height: Dimensions.get('window').height
}}>
<Tabs removeClippedSubviews={false} screenProps={{animatedScrollY: this.state.scrollY}}/>
</Animated.View>
</View>
)
}
}
const styles = StyleSheet.create({/* omitted styles*/})
Home.js (Tab1)
/* omitted imports */
export default class Home extends Component {
/* omitted navigation options */
constructor (props) {
super(props)
this.state = {
scrollY: this.props.screenProps.animatedScrollY
}
}
render () {
return (
<View>
<Animated.ScrollView onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: this.state.scrollY}}}],
{useNativeDriver: true}
)} scrollEventThrottle={16}>
{Array(90).fill().map((v, i) => {
return <Text key={i}
style={{flex: 1, backgroundColor: '#333', padding: 20, marginVertical: 10, color: 'white'}}>Item
#{i + 1}</Text>
})}
</Animated.ScrollView>
</View>
)
}
}
Photos.js (Tab2)
/* omitted imports */
export default class Photos extends Component {
/* omitted navigation options */
constructor (props) {
super(props)
this.state = {
PhotosScrollY: this.props.screenProps.animatedScrollY
}
}
render () {
return (
<Animated.ScrollView onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: this.state.PhotosScrollY}}}],
{useNativeDriver: true}
)} scrollEventThrottle={16}>
<View style={{flex: 1,}}>
{Array(90).fill().map((v, i) => {
return <View key={i} style={/* omitted */}>
<Text style={/* omitted */}>
Photo #{i + 1}
</Text>
</View>
})}
</View>
</Animated.ScrollView>
)
}
}
I'm not sure how to overcome this problem, Any suggestions and solutions are appreciated.
Thanks.
Share Improve this question edited Oct 31, 2017 at 3:32 Azeem Hassni asked Oct 27, 2017 at 16:56 Azeem HassniAzeem Hassni 8861 gold badge13 silver badges28 bronze badges 7 | Show 2 more comments3 Answers
Reset to default 5 +100Try to use Animated.add()
So you need in App
const tab1ScrollY = new Animated.Value(0)
const tab2ScrollY = new Animated.Value(0)
const scrollY = Animated.add(tab1ScrollY,tab2ScrollY)
scrollY
it's offset of tab1 scroll
+ tab2 scroll
I faced this issue. I tried solving it this way. (Optimising it still..)
<ParentComponent>
<CollapsibleHeader/>
<TabNavigator/>
</ParentComponent>
The scrollState lies in Parent Component, and tabs inside TabNavigator has scrollView. I update the state in the parent component using callback after binding the Animation event.
So whenever you move between two tabs, the state lies in the parent component and it is updated from a different place.
May be this could help you.
NOTE: Still optimising it.
---------Edit--------
Parent Component:
state: scrollY: new Animated.Value(0)
componentDidMount(){
binding event.
}
componentWillUnmount(){
Unbind event.
}
onScrollEventCaptured(){
(update parent state and send it to <CollapsibleHeader/>)*
}
Tab 1: This has local state. (Optimising this part) ,scrollY: new Animated.Value(0)
Has a ListView
onScroll function on ListView:
onScroll={Animated.event([{
nativeEvent: {
contentOffset: {
y: this.state.scrollY
}
}
}], {
listener: (event) => {
AppEvents.fire("topBar", this.state.scrollY);
},
})}
AppEvents.fire() is the event which is captured in Parent. (The captured event then sets state, then passed as a prop to the which actually animates.)*
For Tab 2: The same as tab 1.
*Both are same.
Still doing the optimisation work to this. The animation for me is little jerky in iOS development but it looks great in production iOS app. Android no words everywhere its jerky.
I've never used react-native, but I can definitely say why you have this issue. I used debugging tool and I figured out that when you clicked on the tab and app calls this portion of the code every time:
let translateY = this.state.scrollY.interpolate({
inputRange: [0, 600],
outputRange: [0, -290],
extrapolate: 'clamp'
});
let TabsTranslateY = this.state.scrollY.interpolate({
inputRange: [0, 600],
outputRange: [0, -290],
extrapolate: 'clamp'
});
where this.state.scrollY = new Animated.Value(0)
all the time.
Basically, that means when you will click on the tab and start scrolling, it will scroll from 0. You need to find a solution remember previous state of the Animated.Value or change input/output ranges for animation.
Here is sample how to get click on the tab from App.js:
<Tabs removeClippedSubviews={false} screenProps={{animatedScrollY: this.state.scrollY}}
onNavigationStateChange={(prevState, currentState,action) => {
console.log(currentState);
}}
/>
Hopefully it will help you.
animatedScrollY
right? - No I can't use in tabs directly from state because that's only available in App.js, and even if I were I don't think that would change anything. – Azeem Hassni Commented Oct 27, 2017 at 17:22/Tabs/index.js
– Azeem Hassni Commented Oct 31, 2017 at 16:26