React Native-iOS中的Bounce效果与diffflamp动画混在一起

时间:2018-07-04 11:27:58

标签: ios animation react-native

编辑:我讨厌谷歌搜索答案,并且发现一些十年前从未解决过的问题,所以我为可能想知道的人回答自己的问题。就我而言,我只是为滚动视图禁用了bounces道具。由于FlatList扩展了React的ScrollView,因此在我创建的动画FlatList组件中将bounces设置为false可以阻止它弹起并解决了我的问题。祝你有美好的一天。

希望您过得愉快。我正在尝试动态设置标题的动画,但是由于某种原因,每当我滚动到滚动视图的开始或结尾之外时,反弹效果就会与Animation混淆。(如下面的gif所示)

GIF

Same GIF but higher resolution

如您所见,当我滚动到顶部并启用bounce动画时,页眉认为我正在向下滚动,因为bounce将列表中的第一个元素返回顶部。我该如何解决?我在网上某处看到,向动画值添加插值器会有所帮助,尽管我不太了解。 下面是我的代码。谢谢

const AnimatedFlatList = Animated.createAnimatedComponent(FlatList)

const tempArray = [
  ...(an array of my data)
]

export default class TempScreen extends React.Component {
  static navigationOptions = {
    header: null
  }

  constructor(props) {
    super(props)
    this.state = {
      animatedHeaderValue: new Animated.Value(0),
    }
  }

  render() {
    const animatedHeaderHeight = Animated.diffClamp(this.state.animatedHeaderValue, 0, 60)
      .interpolate({
        inputRange: [0, 70],
        outputRange: [70, 0],
      })
    return ( <
      View >
      <
      Animated.View style = {
        {
          backgroundColor: 'white',
          borderBottomColor: '#DEDEDE',
          borderBottomWidth: 1,
          padding: 15,
          width: Dimensions.get('window').width,
          height: animatedHeaderHeight,
        }
      } >
      <
      /Animated.View> <
      AnimatedFlatList scrollEventThrottle = {
        16
      }
      onScroll = {
        Animated.event(
          [{
            nativeEvent: {
              contentOffset: {
                y: this.state.animatedHeaderValue
              }
            }
          }]
        )
      }
      data = {
        tempArray
      }
      renderItem = {
        ({
          item
        }) =>
        <
        View style = {
          {
            flex: 1
          }
        } >
        <
        Text style = {
          {
            fontWeight: 'bold',
            fontSize: 30
          }
        } > {
          item.name
        } < /Text> <
        Text > {
          item.year
        } < /Text> <
        /View>
      }
      />

      <
      /View>

    )
  }
}

3 个答案:

答案 0 :(得分:2)

如果只想解决“弹跳”问题,则问题是iOS赋予diffClamp负的scrollY值。您需要对它们进行过滤,并确保scrollY保持> = 0,以避免diffClamp受到过度滚动的影响。

const clampedScrollY = scrollY.interpolate({
  inputRange: [0, 1],
  outputRange: [0, 1],
  extrapolateLeft: 'clamp',
});

另一个不错的技巧是使用“悬崖”技术,以使标题仅在最小scrollY位置之后消失。

这是我的应用中的代码:

const minScroll = 100;

const clampedScrollY = scrollY.interpolate({
  inputRange: [minScroll, minScroll + 1],
  outputRange: [0, 1],
  extrapolateLeft: 'clamp',
});

const minusScrollY = Animated.multiply(clampedScrollY, -1);

const translateY = Animated.diffClamp(
  minusScrollY,
  -AnimatedHeaderHeight,
  0,
);

const opacity = translateY.interpolate({
  inputRange: [-AnimatedHeaderHeight, 0],
  outputRange: [0.4, 1],
  extrapolate: 'clamp',
});

clampedScrollY将是:

  • scrollY = 0时为0
  • scrollY = 50时为0
  • scrollY = 100时为0
  • 30,当scrollY = 130
  • 170,当scrollY = 270

您明白了。因此diffClamp仅在scrollY> 100时才> 0,并在该阈值后以1递增1。

答案 1 :(得分:1)

我像两个小时前一样遇到了同样的问题...

您可以设置Scrollview属性bounces=false,但是如果您想让RefreshControl刷新ScrollView内容(例如我的情况),则bounce属性必须保持活动状态

我在这篇很酷的文章https://medium.com/appandflow/react-native-collapsible-navbar-e51a049b560a之后修正了这个问题。

我不是Animated库的专家,所以我发布了代码:

constructor(props) {

    const scrollAnim = new Animated.Value(0);
    const offsetAnim = new Animated.Value(0);

    this.state = {
        scrollAnim,
        offsetAnim,
        AnimatedViewHeight: 1,
        clampedScroll: Animated.diffClamp(
            Animated.add(
                scrollAnim.interpolate({
                    inputRange: [0, 1],
                    outputRange: [0, 1],
                    extrapolateLeft: 'clamp',
                }),
                offsetAnim
            ),0, 1
        )
    }
}

render() {

    const minScroll = this.state.AnimatedViewHeight;

    const navbarTranslate = this.state.clampedScroll.interpolate({
        inputRange: [0, minScroll],
        outputRange: [0, -minScroll],
        extrapolate: 'clamp',
    });

    return (
        <View style={{
            flex: 1
        }}>

      <Animated.View
        onLayout={(event) => {
          var { height } = event.nativeEvent.layout;
          this.setState({
              AnimatedViewHeight: height,
              clampedScroll: Animated.diffClamp(
                    Animated.add(
                          this.state.scrollAnim.interpolate({
                                inputRange: [0, 1],
                                outputRange: [0, 1],
                                extrapolateLeft: 'clamp',
                          }),
                          this.state.offsetAnim
                    ), 0, height)
          })
        }}
        style={[{ transform: [{ translateY: navbarTranslate }] }]}>

       <View><Text>THIS IS YOUR HEADER</Text></View>

       </Animated.View>

       <AnimatedFlatList
           // iOS offset for RefreshControl
           contentInset={{
               top: this.state.AnimatedViewHeight,
           }}
           contentOffset={{
               y: -this.state.AnimatedViewHeight,
           }}
           scrollEventThrottle={1}
           onScroll={
               Animated.event(
                        [{ nativeEvent: { contentOffset: { y: this.state.scrollAnim } } }],
                        { useNativeDriver: true },
               )}
               data={this.state.data}
               keyExtractor={(item, idx) => idx}
               ListFooterComponent={this.renderFooter}
               renderItem={this.renderItem}
               onEndReached={this.handleLoadMore}
               refreshControl={
                    <RefreshControl
                        refreshing={this.state.refreshing}
                        onRefresh={this.onRefresh}
                        // Android offset for RefreshControl
                        progressViewOffset={this.state.AnimatedViewHeight}
                    />
                }
                onEndReachedThreshold={0.5} />
        </View>

    )
}

this.state.AnimatedViewHeight是标题的高度,可以通过调用onLayout函数来获取。在此函数内部,我还设置了一个新的lampedScroll,因为我有一个新的高度(在我的情况下,标头没有固定的大小)。 然后,在render()中,根据您的Animated Scrollview的滚动位置定义一个变量(navbarTranslate)来控制headerSize。

答案 2 :(得分:0)

我使用此答案https://stackoverflow.com/a/51638296/3639398

解决了
import React from 'react';
import {
  Animated,
  Text,
  View,
  StyleSheet,
  ScrollView,
  Dimensions,
  RefreshControl,
} from 'react-native';
import Constants from 'expo-constants';
import randomColor from 'randomcolor';

const HEADER_HEIGHT = 44 + Constants.statusBarHeight;
const BOX_SIZE = Dimensions.get('window').width / 2 - 12;

const wait = (timeout: number) => {
  return new Promise((resolve) => {
    setTimeout(resolve, timeout);
  });
};
function App() {
  const [refreshing, setRefreshing] = React.useState(false);

  const scrollAnim = new Animated.Value(0);
  const minScroll = 100;

  const clampedScrollY = scrollAnim.interpolate({
    inputRange: [minScroll, minScroll + 1],
    outputRange: [0, 1],
    extrapolateLeft: 'clamp',
  });

  const minusScrollY = Animated.multiply(clampedScrollY, -1);

  const translateY = Animated.diffClamp(minusScrollY, -HEADER_HEIGHT, 0);

  const onRefresh = React.useCallback(() => {
    setRefreshing(true);
    wait(2000).then(() => {
      setRefreshing(false);
    });
  }, []);

  return (
    <View style={styles.container}>
      <Animated.ScrollView
        contentContainerStyle={styles.gallery}
        scrollEventThrottle={1}
        bounces={true}
        showsVerticalScrollIndicator={false}
        style={{
          zIndex: 0,
          height: '100%',
          elevation: -1,
        }}
        onScroll={Animated.event(
          [{ nativeEvent: { contentOffset: { y: scrollAnim } } }],
          { useNativeDriver: true }
        )}
        overScrollMode="never"
        contentInset={{ top: HEADER_HEIGHT }}
        contentOffset={{ y: -HEADER_HEIGHT }}
        refreshControl={
          <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
        }>
        {Array.from({ length: 20 }, (_, i) => i).map((uri) => (
          <View style={[styles.box, { backgroundColor: 'grey' }]} />
        ))}
      </Animated.ScrollView>
      <Animated.View style={[styles.header, { transform: [{ translateY }] }]}>
        <Text style={styles.title}>Header</Text>
      </Animated.View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
  },
  gallery: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    padding: 4,
  },
  box: {
    height: BOX_SIZE,
    width: BOX_SIZE,
    margin: 4,
  },
  header: {
    flex: 1,
    height: HEADER_HEIGHT,
    paddingTop: Constants.statusBarHeight,
    alignItems: 'center',
    justifyContent: 'center',
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    backgroundColor: randomColor(),
  },
  title: {
    fontSize: 16,
  },
});

export default App;

在Expo https://snack.expo.io/@raksa/auto-hiding-header上结帐