我正在开发一个 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
问题: 我的动画代码做错了什么?如何使不透明度插值正常工作,以便项目在滚动到视图中时淡入和淡出?
任何帮助或建议将不胜感激!
好吧,我找到了解决方案
我只需要分离
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",
},
});