我创建了一个自定义的动画底部工作表。用户可以上下移动底部工作表。在我的底部工作表中,我使用了 flatList 来获取数据并将项目呈现为卡片。到目前为止,一切都按预期工作,但我在 Flatlist 滚动时遇到了问题。在底部工作表内,平面列表不会滚动。我已经制作了硬编码的高度值 2000px
,这是真正的实践,也是 FlatList 的 contentContainerStyle
添加硬编码 paddingBottom 2000
(也是另一种不好的做法)。我想根据 Flex-box
滚动 FlatList .我不知道如何解决这个问题。
我在 expo-snacks 上分享我的代码
这是我的所有代码
import React, { useState, useEffect } from "react";
import {
StyleSheet,
Text,
View,
Dimensions,
useWindowDimensions,
SafeAreaView,
RefreshControl,
Animated,
Button,
FlatList,
} from "react-native";
import MapView from "react-native-maps";
import styled from "styled-components";
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
TouchableOpacity,
} from "react-native-gesture-handler";
const { width } = Dimensions.get("screen");
const initialRegion = {
latitudeDelta: 15,
longitudeDelta: 15,
latitude: 60.1098678,
longitude: 24.7385084,
};
const api =
"http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=50";
export default function App() {
const { height } = useWindowDimensions();
const [translateY] = useState(new Animated.Value(0));
const [event, setEvent] = useState([]);
const [loading, setLoading] = useState(false);
// This is Fetch Dtata
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(api);
const data = await response.json();
setEvent(data.data);
setLoading(false);
} catch (error) {
console.log("erro", error);
}
};
useEffect(() => {
fetchData();
}, []);
// Animation logic
const bringUpActionSheet = () => {
Animated.timing(translateY, {
toValue: 0,
duration: 500,
useNativeDriver: true,
}).start();
};
const closeDownBottomSheet = () => {
Animated.timing(translateY, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
};
const bottomSheetIntropolate = translateY.interpolate({
inputRange: [0, 1],
outputRange: [-height / 2.4 + 50, 0],
});
const animatedStyle = {
transform: [
{
translateY: bottomSheetIntropolate,
},
],
};
const gestureHandler = (e: PanGestureHandlerGestureEvent) => {
if (e.nativeEvent.translationY > 0) {
closeDownBottomSheet();
} else if (e.nativeEvent.translationY < 0) {
bringUpActionSheet();
}
};
return (
<>
<MapView style={styles.mapStyle} initialRegion={initialRegion} />
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View
style={[styles.container, { top: height * 0.7 }, animatedStyle]}
>
<SafeAreaView style={styles.wrapper}>
<ContentConatiner>
<Title>I am scroll sheet</Title>
<HeroFlatList
data={event}
refreshControl={
<RefreshControl
enabled={true}
refreshing={loading}
onRefresh={fetchData}
/>
}
keyExtractor={(_, index) => index.toString()}
renderItem={({ item, index }) => {
const image = item?.description.images.map((img) => img.url);
const startDate = item?.event_dates?.starting_day;
return (
<EventContainer key={index}>
<EventImage
source={{
uri:
image[0] ||
"https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
}}
/>
<DescriptionContainer>
<Title ellipsizeMode="tail" numberOfLines={1}>
{item?.name?.en}
</Title>
<DescriptionText>
{item?.description?.intro ||
"No description available"}
</DescriptionText>
<DateText>{startDate}</DateText>
</DescriptionContainer>
</EventContainer>
);
}}
/>
</ContentConatiner>
</SafeAreaView>
</Animated.View>
</PanGestureHandler>
</>
);
}
const styles = StyleSheet.create({
container: {
position: "absolute",
top: 0,
left: 0,
right: 0,
backgroundColor: "white",
shadowColor: "black",
shadowOffset: {
height: -6,
width: 0,
},
shadowOpacity: 0.1,
shadowRadius: 5,
borderTopEndRadius: 15,
borderTopLeftRadius: 15,
},
mapStyle: {
width: width,
height: 800,
},
});
const HeroFlatList = styled(FlatList).attrs({
contentContainerStyle: {
padding: 14,
flexGrow: 1, // IT DOES NOT GROW
paddingBottom: 2000, // BAD PRACTICE
},
height: 2000 /// BAD PRACTICE
})``;
const ContentConatiner = styled.View`
flex: 1;
padding: 20px;
background-color: #fff;
`;
const Title = styled.Text`
font-size: 16px;
font-weight: 700;
margin-bottom: 5px;
`;
const DescriptionText = styled(Title)`
font-size: 14px;
opacity: 0.7;
`;
const DateText = styled(Title)`
font-size: 14px;
opacity: 0.8;
color: #0099cc;
`;
const EventImage = styled.Image`
width: 70px;
height: 70px;
border-radius: 70px;
margin-right: 20px;
`;
const DescriptionContainer = styled.View`
width: 200px;
`;
const EventContainer = styled(Animated.View)`
flex-direction: row;
padding: 20px;
margin-bottom: 10px;
border-radius: 20px;
background-color: rgba(255, 255, 255, 0.8);
shadow-color: #000;
shadow-opacity: 0.3;
shadow-radius: 20px;
shadow-offset: 0 10px;
`;
最佳答案
如果您不反对使用 react-native-reanimated
,然后我对你的代码做了一个最小修改的版本,它应该完全符合你的要求。
我使用 Reanimated 的 v1 兼容性 API,因此您不必安装 babel 转译器或任何东西。它应该按原样工作。
https://snack.expo.dev/@switt/flatlist-scroll-reanimated
Reanimated 更适合这里,因为 React-Native 的原生 Animated 模块不能为 top
设置动画。 , bottom
, width
, height
等属性,可能需要设置 useNativeDriver
至 false
为了你想要达到的目标。这将导致动画过程中出现一些性能下降/断断续续的帧。
这是您编辑的代码,只是为了方便
import React, { useState, useEffect } from "react";
import {
StyleSheet,
Text,
View,
Dimensions,
useWindowDimensions,
SafeAreaView,
RefreshControl,
Animated,
Button,
FlatList,
ScrollView
} from "react-native";
import MapView from "react-native-maps";
import styled from "styled-components";
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
TouchableOpacity,
} from "react-native-gesture-handler";
import Reanimated, { EasingNode } from 'react-native-reanimated';
const { width } = Dimensions.get("screen");
const initialRegion = {
latitudeDelta: 15,
longitudeDelta: 15,
latitude: 60.1098678,
longitude: 24.7385084,
};
const api =
"http://open-api.myhelsinki.fi/v1/events/?distance_filter=60.1699%2C24.9384%2C10&language_filter=en&limit=50";
export default function App() {
const { height } = useWindowDimensions();
const translateY = React.useRef(new Reanimated.Value(0)).current;
const [event, setEvent] = useState([]);
const [loading, setLoading] = useState(false);
// This is Fetch Dtata
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(api);
const data = await response.json();
setEvent(data.data);
setLoading(false);
} catch (error) {
console.log("erro", error);
}
};
useEffect(() => {
fetchData();
}, []);
// Animation logic
const bringUpActionSheet = () => {
Reanimated.timing(translateY, {
toValue: 0,
duration: 500,
useNativeDriver: true,
easing: EasingNode.inOut(EasingNode.ease)
}).start();
};
const closeDownBottomSheet = () => {
Reanimated.timing(translateY, {
toValue: 1,
duration: 500,
useNativeDriver: true,
easing: EasingNode.inOut(EasingNode.ease)
}).start();
};
const bottomSheetTop = translateY.interpolate({
inputRange: [0, 1],
outputRange: [height * 0.7 - height / 2.4 + 50, height * 0.7]
});
const animatedStyle = {
top: bottomSheetTop,
bottom: 0
};
const gestureHandler = (e: PanGestureHandlerGestureEvent) => {
if (e.nativeEvent.translationY > 0) {
closeDownBottomSheet();
} else if (e.nativeEvent.translationY < 0) {
bringUpActionSheet();
}
};
return (
<>
<MapView style={styles.mapStyle} initialRegion={initialRegion} />
<PanGestureHandler onGestureEvent={gestureHandler}>
<Reanimated.View
style={[styles.container, { top: height * 0.7 }, animatedStyle]}
>
<Title>I am scroll sheet</Title>
<HeroFlatList
data={event}
refreshControl={
<RefreshControl
enabled={true}
refreshing={loading}
onRefresh={fetchData}
/>
}
keyExtractor={(_, index) => index.toString()}
renderItem={({ item, index }) => {
const image = item?.description.images.map((img) => img.url);
const startDate = item?.event_dates?.starting_day;
return (
<EventContainer key={index}>
<EventImage
source={{
uri:
image[0] ||
"https://res.cloudinary.com/drewzxzgc/image/upload/v1631085536/zma1beozwbdc8zqwfhdu.jpg",
}}
/>
<DescriptionContainer>
<Title ellipsizeMode="tail" numberOfLines={1}>
{item?.name?.en}
</Title>
<DescriptionText>
{item?.description?.intro ||
"No description available"}
</DescriptionText>
<DateText>{startDate}</DateText>
</DescriptionContainer>
</EventContainer>
);
}}
/>
</Reanimated.View>
</PanGestureHandler>
</>
);
}
const styles = StyleSheet.create({
container: {
position: "absolute",
top: 0,
left: 0,
right: 0,
backgroundColor: "white",
shadowColor: "black",
shadowOffset: {
height: -6,
width: 0,
},
shadowOpacity: 0.1,
shadowRadius: 5,
borderTopEndRadius: 15,
borderTopLeftRadius: 15,
},
mapStyle: {
width: width,
height: 800,
},
});
const HeroFlatList = styled(FlatList).attrs({
contentContainerStyle: {
paddingBottom: 50
},
// height:510,
// flex:1
})``;
const Title = styled.Text`
font-size: 16px;
font-weight: 700;
margin-bottom: 5px;
`;
const DescriptionText = styled(Title)`
font-size: 14px;
opacity: 0.7;
`;
const DateText = styled(Title)`
font-size: 14px;
opacity: 0.8;
color: #0099cc;
`;
const EventImage = styled.Image`
width: 70px;
height: 70px;
border-radius: 70px;
margin-right: 20px;
`;
const DescriptionContainer = styled.View`
width: 200px;
`;
const EventContainer = styled(Animated.View)`
flex-direction: row;
padding: 20px;
margin-bottom: 10px;
border-radius: 20px;
background-color: rgba(255, 255, 255, 0.8);
shadow-color: #000;
shadow-opacity: 0.3;
shadow-radius: 20px;
shadow-offset: 0 10px;
`;
关于react-native - React Native Flatlist 不会在自定义动画底部工作表内滚动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69253810/