动画样式插值在 React Native Reanimated 中不起作用

问题描述 投票:0回答:1

我正在开发一个 React Native 项目,我想创建一个类似轮子的选择器,其中的项目在滚动到视图中时会淡入和淡出。我正在使用 React-native-reanimated 来制作动画,但不透明度插值似乎没有按预期工作。

我尝试过的: 这是我的组件代码:

import { StyleSheet, View, Modal } from "react-native";
import React from "react";
import { LinearGradient } from "expo-linear-gradient";
import Text from "../atoms/Text";
import Animated, {
  Extrapolation,
  interpolate,
  useAnimatedScrollHandler,
  useAnimatedStyle,
  useSharedValue,
} from "react-native-reanimated";
import Button from "../atoms/Button";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { COLORS, DEVICE } from "../../common";

const ITEM_HEIGHT = 60;
const ITEM_WIDTH = 100;
const WHEEL_HEIGHT = ITEM_HEIGHT * 5;

const WheelModal = () => {
  const { bottom } = useSafeAreaInsets();
  const eventY = useSharedValue(0);

  const scrollHandler = useAnimatedScrollHandler((event) => {
    eventY.value = event.contentOffset.y;
  });

  const animatedStyle = useAnimatedStyle(() => {
    const activeNumber = eventY.value / ITEM_HEIGHT;
    const inputRange = [
      (activeNumber - 2) * ITEM_HEIGHT,
      (activeNumber - 1) * ITEM_HEIGHT,
      activeNumber * ITEM_HEIGHT,
      (activeNumber + 1) * ITEM_HEIGHT,
      (activeNumber + 2) * ITEM_HEIGHT,
    ];

    const opacity = interpolate(
      eventY.value,
      inputRange,
      [0.1, 0.1, 1, 0.1, 0.1],
      Extrapolation.CLAMP
    );

    return {
      opacity,
    };
  });

  const onSelect = () => {
    console.log("selected year", yearGenerator()[eventY.value / ITEM_HEIGHT]);
  };

  return (
    <Modal transparent visible={true} animationType="fade">
      <View style={styles.titleBox}>
        <Text weight="bold" size="lg" tx="Choose the year" />
      </View>
      <LinearGradient
        locations={[0.1, 0.3, 0.7, 0.9, 1]}
        colors={[
          "rgba(256,256,256,.6)",
          "rgba(256,256,256,.95)",
          "rgba(256,256,256,1)",
          "rgba(256,256,256,.1)",
          "rgba(256,256,256,.2)",
        ]}
        style={styles.gradient}
      >
        <Animated.FlatList
          bounces={false}
          showsVerticalScrollIndicator={false}
          removeClippedSubviews={true}
          onScroll={scrollHandler}
          data={yearGenerator()}
          decelerationRate="normal"
          scrollEventThrottle={16}
          snapToInterval={ITEM_HEIGHT}
          style={styles.flatList}
          contentContainerStyle={styles.flatListContent}
          keyExtractor={(_, index) => index.toString()}
          renderItem={({ item }) => (
            <Animated.View style={[styles.animatedView, animatedStyle]}>
              <Text size="lg" weight="medium" text={item.toString()} />
            </Animated.View>
          )}
        />
        <View style={styles.borderView} />
      </LinearGradient>
      <Button
        onPress={onSelect}
        tx="Select"
        style={[styles.selectButton, { height: 45 + bottom }]}
      />
    </Modal>
  );
};

const styles = StyleSheet.create({
  gradient: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  titleBox: {
    position: "absolute",
    top: DEVICE.height / 2 - WHEEL_HEIGHT / 2 - 49,
    alignSelf: "center",
    zIndex: 999,
  },
  flatList: {
    flexGrow: 0,
    height: WHEEL_HEIGHT,
  },
  flatListContent: {
    flexGrow: 1,
    paddingVertical: ITEM_HEIGHT * 2,
  },
  animatedView: {
    height: ITEM_HEIGHT,
    width: ITEM_WIDTH,
    justifyContent: "center",
    alignItems: "center",
  },
  borderView: {
    width: ITEM_WIDTH,
    height: ITEM_HEIGHT,
    position: "absolute",
    borderTopWidth: 1,
    borderBottomWidth: 1,
    borderColor: COLORS.primary,
  },
  selectButton: {
    position: "absolute",
    bottom: 0,
    alignSelf: "center",
    width: "100%",
    borderRadius: 0,
  },
});

const yearGenerator = () => {
  const year = new Date().getFullYear();
  const years = [];
  for (let i = 2015; i <= year; i++) {
    years.push(i);
  }
  return years;
};

export default WheelModal;


我的期望: 我预计当项目远离中心项目时,它们的不透明度会降低,从而产生褪色效果。

实际发生的情况: 不透明度动画不起作用,所有项目都具有相同的不透明度,无论它们在列表中的位置如何。

附加信息: 反应本机版本:0.74.3 React Native 重生版本:3.10.1

截图: my result

问题: 我的动画代码做错了什么?如何使不透明度插值正常工作,以便项目在滚动到视图中时淡入和淡出?

任何帮助或建议将不胜感激!

react-native react-native-reanimated
1个回答
0
投票

好吧,我找到了解决方案

我只需要分离

renderItem
组件来分离组件并使用索引而不是
activeNumber

就是这样

车轮组件:

import { StyleSheet, View, Modal } from "react-native";
import React from "react";
import { LinearGradient } from "expo-linear-gradient";
import Text, { TextProps } from "../../atoms/Text";
import Animated, {
  useAnimatedScrollHandler,
  useSharedValue,
} from "react-native-reanimated";
import Button from "../../atoms/Button";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { COLORS, DEVICE } from "../../../common";
import RenderItem from "./RenderItem";

type PropsType = {
  data: { lable: string; value: string }[];
  onSelect: (value: PropsType["data"][0]) => void;
  isVisible: boolean;
  title: TextProps["tx"];
};
const SHOW_ITEMS = 5;
const ITEM_HEIGHT = 60;
const WHEEL_HEIGHT = ITEM_HEIGHT * SHOW_ITEMS;
const ITEM_WIDTH = 100;

const WheelModal = ({ onSelect, data, isVisible, title }: PropsType) => {
  const { bottom } = useSafeAreaInsets();
  const eventY = useSharedValue(0);

  const scrollHandler = useAnimatedScrollHandler((event) => {
    eventY.value = event.contentOffset.y;
  });

  const _onSelect = () => {
    const SelectedItem = data[eventY.value / ITEM_HEIGHT];
    onSelect(SelectedItem);
  };

  return (
    <Modal transparent visible={isVisible} animationType="fade">
      <View style={styles.titleBox}>
        <Text weight="bold" size="lg" tx={title} />
      </View>
      <LinearGradient
        locations={[0.1, 0.3, 0.7, 0.9, 1]}
        colors={[
          "rgba(256,256,256,.6)",
          "rgba(256,256,256,.95)",
          "rgba(256,256,256,1)",
          "rgba(256,256,256,.7)",
          "rgba(256,256,256,.2)",
        ]}
        style={styles.gradient}
      >
        <Animated.FlatList
          bounces={false}
          showsVerticalScrollIndicator={false}
          removeClippedSubviews={true}
          onScroll={scrollHandler}
          data={data ?? []}
          decelerationRate="normal"
          scrollEventThrottle={16}
          snapToInterval={ITEM_HEIGHT}
          style={styles.flatList}
          contentContainerStyle={styles.flatListContent}
          keyExtractor={(_, index) => index.toString()}
          renderItem={(props) => <RenderItem eventY={eventY} {...props} />}
        />
        <View style={styles.borderView} />
      </LinearGradient>
      <Button
        onPress={_onSelect}
        tx="Select"
        style={[styles.selectButton, { height: 50 + bottom }]}
      />
    </Modal>
  );
};

const styles = StyleSheet.create({
  gradient: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  titleBox: {
    position: "absolute",
    top: DEVICE.height / 2 - WHEEL_HEIGHT / 2 - 49,
    alignSelf: "center",
    zIndex: 999,
  },
  flatList: {
    flexGrow: 0,
    height: WHEEL_HEIGHT,
  },
  flatListContent: {
    flexGrow: 1,
    paddingVertical: ITEM_HEIGHT * Math.floor(SHOW_ITEMS / 2),
  },

  borderView: {
    width: ITEM_WIDTH,
    height: ITEM_HEIGHT,
    position: "absolute",
    borderTopWidth: 1,
    borderBottomWidth: 1,
    borderColor: COLORS.primary,
    zIndex: -1,
    alignSelf: "center",
    top: DEVICE.height / 2 - ITEM_HEIGHT / 2,
  },
  selectButton: {
    position: "absolute",
    bottom: 0,
    alignSelf: "center",
    width: "100%",
    borderRadius: 0,
  },
});

export default WheelModal;

渲染项目组件

  import { StyleSheet, View } from "react-native";
import React from "react";
import Animated, {
  Extrapolation,
  interpolate,
  useAnimatedStyle,
} from "react-native-reanimated";
import Text from "../../atoms/Text";

const ITEM_HEIGHT = 60;
const ITEM_WIDTH = 100;
const RenderItem = ({ item, index, eventY }) => {
  const inputRange = [
    (index - 2) * ITEM_HEIGHT,
    (index - 1) * ITEM_HEIGHT,
    index * ITEM_HEIGHT,
    (index + 1) * ITEM_HEIGHT,
    (index + 2) * ITEM_HEIGHT,
  ];

  const animatedStyle = useAnimatedStyle(() => {
    const opacity = interpolate(
      eventY.value,
      inputRange,
      [0.1, 0.3, 1, 0.3, 0.1],
      Extrapolation.CLAMP
    );
    return {
      opacity,
    };
  });

  return (
    <Animated.View style={[styles.animatedView, animatedStyle]}>
      <Text size="lg" weight="medium" text={item.toString()} />
    </Animated.View>
  );
};

export default RenderItem;

const styles = StyleSheet.create({
  animatedView: {
    height: ITEM_HEIGHT,
    width: ITEM_WIDTH,
    justifyContent: "center",
    alignItems: "center",
  },
});
© www.soinside.com 2019 - 2024. All rights reserved.