Animating a single View based on multiple ScrollView(s)

走远了吗. 提交于 2020-07-04 10:05:20

问题


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.


回答1:


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




回答2:


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.




回答3:


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.



来源:https://stackoverflow.com/questions/46980077/animating-a-single-view-based-on-multiple-scrollviews

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!