最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Animating a single View based on multiple ScrollView(s) - Stack Overflow

programmeradmin4浏览0评论

I'm working on an application where I'm trying to Animate a View based on scroll Position of multiple ScrollViews.

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 ScrollViews.

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
  • What if you use it directly from the props without assigning it to state? – Alexander Vitanov Commented Oct 27, 2017 at 17:16
  • @AlexanderVitanov You're talking about 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
  • @Azeem Hassni, can you add your Tab.js file? – Observer Commented Oct 31, 2017 at 14:44
  • Sure you can check it in the snack snack.expo.io/SytBkdBAW -> /Tabs/index.js – Azeem Hassni Commented Oct 31, 2017 at 16:26
  • Say you open the app, scroll down a bit on the home tab, and then switch to the items tab. Should the items list be at the top? If so, and there's nowhere to scroll up to, how would you reveal the header? – Rob Hogan Commented Oct 31, 2017 at 19:59
 |  Show 2 more comments

3 Answers 3

Reset to default 5 +100

Try 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.

发布评论

评论列表(0)

  1. 暂无评论