React Native:根据滚动位置更改视图对应的内容

8

我正在使用Animation.view来更改headerheightbackground

我这样设置我的heightbackground

const HeaderHeight = this.state.scrollY.interpolate({
      inputRange:[0, Header_Max_Height - Header_Min_Height],
      outputRange:[Header_Max_Height, Header_Min_Height],
      extrapolate:'clamp'
    })

const AnimateHeaderBackgroundColor = this.state.scrollY.interpolate({
        inputRange: [ 0, ( Header_Max_Height - Header_Min_Height )  ],
        outputRange: [ '#009688', '#00BCD4' ],
        extrapolate: 'clamp'
    })

这是我的 animated.view。
<Animated.View style={{width:'100%', height: HeaderHeight, backgroundColor:AnimateHeaderBackgroundColor}}></Animated.View>

一切运作良好。

我的问题是是否有一种方法可以更改视图,比如heightbackgroundcolor

例如,假设我有两个视图:

//view1
<View style={{width:'100%',height:100, backgroundColor:'red'}}>
 <Text>View1</Text>
</View>

//view2
<View style={{width:'100%',height:100, backgroundColor:'blue'}}>
  <Text>View2</Text>
</View>

我希望默认情况下显示view1,当滚动到屏幕顶部时显示view2。将View放在outputRange中可以实现这个目的吗?


@AnkitMakwana 就像动画标题一样。我想要滑动视图而不是改变颜色。 - kirimi
当滑动视图时,动画效果是什么?改变高度还是透明度? - goldvenus
@nikolai-serg 不太确定你的意思。我想要在滚动时改变视图。 - kirimi
你的意思是想把 view1 放进一个 scrollView 中,当滚动到特定位置时将 view1 更换为 view2 吗? - Andus
既然您正在询问与动画相关的问题,是否表示您想要对上述过程进行动画处理?您想要什么样的动画效果(例如淡入/淡出)? - Andus
显示剩余4条评论
3个回答

8

我猜如果你想要在React Native中动画改变视图的话,可能没有直接的方法。不过,在你的情况下,我可以想到一个小技巧,使用 opacityposition: absoluteinterpolate() 的组合。以下是一个最小化示例,你可以直接复制粘贴测试:

import React, { Component } from 'react';
import { StyleSheet, Animated, View, ScrollView } from 'react-native';

class AnimationExample extends Component {
  constructor(props) {
    super(props)
    this.state = {
      showBlueView: false,
      animatedOpacityValue: new Animated.Value(0),
    }
  }

  handleScroll = (event) => {
    const { animatedOpacityValue, showBlueView } = this.state;
    const scrollPosition = event.nativeEvent.contentOffset.y;

    if (scrollPosition > 100 && !showBlueView) {
      Animated.timing(animatedOpacityValue, {
        toValue: 1,
      }).start(() => this.setState({ showBlueView: true }))
    }

    if (scrollPosition < 100 && showBlueView) {
      Animated.timing(animatedOpacityValue, {
        toValue: 0,
      }).start(() => this.setState({ showBlueView: false }))
    }
  }

  render() {
    const { animatedOpacityValue } = this.state;
    return (
      <ScrollView
        style={styles.scrollView}
        onScroll={this.handleScroll}
        scrollEventThrottle={16}
      >
        <View style={styles.green} />
        <View style={styles.animatedViewsPositioner}>
          <Animated.View
            style={{
              ...styles.red,
              opacity: animatedOpacityValue.interpolate({
                inputRange: [0, 1],
                outputRange: [1, 0],
              }),
            }}
          />
          <Animated.View
            style={{
              ...styles.blue,
              opacity: animatedOpacityValue.interpolate({
                inputRange: [0, 1],
                outputRange: [0, 1],
              }),
            }}
          />
        </View>
      </ScrollView>
    )
  }
}

const styles = StyleSheet.create({
  scrollView: {
    flex: 1,
  },
  green: {
    height: 600,
    width: '100%',
    backgroundColor: 'green',
  },
  red: {
    height: 300,
    width: '100%',
    backgroundColor: 'red',
  },
  blue: {
    position: 'absolute',
    height: 300,
    width: '100%',
    backgroundColor: 'blue',
  },
  animatedViewsPositioner: {
    position: 'relative',
  },
})

在上面的例子中,我首先通过将handleScroll函数应用于scrollView来访问滚动位置。确保将scrollEventThrottle设置为16,以确保每秒触发一次该函数,但要注意可能由此引起的性能问题(如果您关心,可以查看这里了解更多信息)。
为了在用户滚动到特定位置时触发视图更改(实际上并不是这样,但看起来像),我使用一个view来包装红色和蓝色视图,红色视图默认为opacity:1,而蓝色视图默认为opacity:0,位于红色视图之上。
我通过使用interpolate()来使它们的opacity动画化来隐藏红色视图并显示蓝色视图。在帮助下,两个不透明度值都由放置在状态中的一个animatedValue animatedOpacityValue控制。我添加了一个状态showBlueView来通过避免由onScroll触发的不断设置状态来优化性能。
这里有一个更新,可以在两个视图上添加touchableOpacities,只需在未使用蓝色视图时隐藏它即可。
首先,添加一个日志功能:
log = (stringToPrint) => () => {
  console.log(stringToPrint)
}

接下来,通过添加两个TouchableOpacity,将scrollView更改为以下形式
<ScrollView
  style={styles.scrollView}
  onScroll={this.handleScroll}
  scrollEventThrottle={16}
>
  <View style={styles.green} />
  <View style={styles.animatedViewsPositioner}>
    <Animated.View
      style={{
        ...styles.red,
        opacity: animatedOpacityValue.interpolate({
          inputRange: [0, 1],
          outputRange: [1, 0],
        }),
      }}
    >
      <TouchableOpacity
        style={{ backgroundColor: 'black', width: 80, height: 30 }}
        onPress={this.log('click on red')}
      />
    </Animated.View>
    {showBlueView && (
      <Animated.View
        style={{
          ...styles.blue,
          opacity: animatedOpacityValue.interpolate({
            inputRange: [0, 1],
            outputRange: [0, 1],
          }),
        }}
      >
        <TouchableOpacity
          style={{ backgroundColor: 'black', width: 80, height: 30 }}
          onPress={this.log('click on blue')}
        />
      </Animated.View>
    )}
  </View>
</ScrollView>

请注意,我添加了showBlueView && 以在不透明度为0时隐藏蓝色视图,这样它就不会阻止应用于红色视图的任何点击事件(即使蓝色视图被隐藏,它实际上位于具有opacity: 0 的红色视图之上)。

非常感谢您的回答。我正在尝试它。我将 TouchableOpacity 放在 Animated.View 中,但不起作用。您知道如何处理 Animated.View 中的 TouchableOpacity 吗? - kirimi
你说的“不起作用”是什么意思?你想要实现什么目标? - Andus
抱歉如果我表达不清楚。我喜欢你的想法,它也很有效。但是我尝试滑动“视图”,是为了将“按钮”和“touchableOpacity”放置在“animated.view”内部。但是,如果我在你的代码“Animated.View”中放置一个“button”或“touchableOpacity”,那么“onPress”将无法工作。 - kirimi
这是因为你实际上是让蓝色视图覆盖了红色视图,你在红色视图上放置了一个 touchableOpacity 吗? - Andus
其实我计划在两个视图中都使用 TouchableOpacity。是否可以在两个视图中都使用 TouchableOpacity - kirimi
让我们在聊天中继续这个讨论 - Andus

3

@Andus使用Animated.event的答案

这个想法是获取最新的scrollY,然后将其包装到视图的不透明度中。 蓝色目标的示例输入范围为0-50,并获得不透明度1至0。 这意味着在向下滚动前50个像素时会淡出。

红色的则是相反的,输入范围为0-200,输出范围为不透明度0至1(淡入)。

import React, { Component } from 'react';
import { StyleSheet, Animated, View, ScrollView, SafeAreaView } from 'react-native';

export default class AnimationExample extends Component {
  constructor(props) {
    super(props)
    this.state = {
      scrollY: new Animated.Value(0)
    }
  }

  render() {
  const {scrollY} = this.state;
    return (
      <SafeAreaView style={{flex: 1}}>
      <ScrollView
        style={styles.scrollView}
        onScroll={Animated.event(
          [{nativeEvent: {contentOffset: {y: this.state.scrollY}}}]
        )}
        scrollEventThrottle={16}
      >
        <View style={styles.animatedViewsPositioner}>
          <Animated.View
            style={[styles.box, styles.target, {
             opacity: scrollY.interpolate({
                inputRange: [0, 50],
                outputRange: [1, 0],
              }),
            }]}
          />
          <Animated.View
           style={[styles.box, styles.origin, {
             opacity: scrollY.interpolate({
                inputRange: [0, 200],
                outputRange: [0, 1],
              }),
            }]}
          />
        </View>

      </ScrollView>
      </SafeAreaView>
    )
  }
}

const styles = StyleSheet.create({
  scrollView: {
    flex: 1,
  },
  box: {
    height: 1000,
    width: '100%',
    position: 'absolute'
  },
  origin: {
    backgroundColor: 'red',
    zIndex: 1
  },
  target: {
    backgroundColor: 'blue',
    zIndex: 2
  },
  animatedViewsPositioner: {
    position: 'relative',
    backgroundColor: 'pink',
    height: 10000
  },
})

-1
如果您在显示视图时使用了ScrollView,我相信您可以使用onScroll回调来获取屏幕在ScrollView内的位置,并在用户滚动到顶部时动态更改高度和颜色。
<ScrollView onScroll={this.handleScroll} />

获取位置信息,

handleScroll: function(event: Object) {
  console.log(event.nativeEvent.contentOffset.y);
},

参考:在React Native中获取ScrollView的当前滚动位置


谢谢回复。我知道我可以动态地改变颜色或高度。但这不是我想要实现的目标。 - kirimi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接