PanResponder改变Card Y值和移出屏幕的问题
PanResponder to change Card Y value and issue of moving out of screen
我是 react-native 动画 API 和 PanResponder API 的新手。
我想创建 google 地图克隆 UI 和在结果后显示的卡片动画。
我想创建卡片,当用户向上滑动时,卡片会到达屏幕顶部,当用户向下滑动时,卡片应调整到原始位置。
Demo of Google Maps that i want to achieve
我已经为此实现了一些代码,但我面临的问题是卡片在顶部方向移出屏幕(在卡片到达顶部位置后向上滑动)。有时,向下滑动有效,但并非总是如此。
App.js
import React, { useEffect, useState } from "react";
import {
SafeAreaView,
View,
Text,
Dimensions,
PanResponder,
Animated,
} from "react-native";
import { Searchbar, FAB } from "react-native-paper";
import MapView from "react-native-maps";
import * as Location from "expo-location";
const SCREEN_HEIGHT = Dimensions.get("window").height;
const SCREEN_WIDTH = Dimensions.get("window").width;
const App = () => {
const [latitute, setLatitute] = useState(0);
const [longitute, setLongitute] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const pan = useState(
new Animated.ValueXY({ x: 0, y: SCREEN_HEIGHT - 200 })
)[0];
const panResponder = useState(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
pan.extractOffset();
return true;
},
onPanResponderMove: (e, gestureState) => {
pan.setValue({ x: 0, y: gestureState.dy });
},
onPanResponderRelease: (e, gestureState) => {
if (gestureState.moveY > SCREEN_HEIGHT - 200) {
Animated.spring(pan.y, {
toValue: 0,
tension: 1,
useNativeDriver: true,
}).start();
} else if (gestureState.moveY < 200) {
Animated.spring(pan.y, {
toValue: 0,
tension: 1,
useNativeDriver: true,
}).start();
} else if (gestureState.dy < 0) {
Animated.spring(pan.y, {
toValue: -SCREEN_HEIGHT + 200,
tension: 1,
useNativeDriver: true,
}).start();
} else if (gestureState.dy > 0) {
Animated.spring(pan.y, {
toValue: SCREEN_HEIGHT - 200,
tension: 1,
useNativeDriver: true,
}).start();
}
},
})
)[0];
const animatedHeight = {
transform: pan.getTranslateTransform(),
};
useEffect(() => {
(async () => {
let { status } = await Location.requestPermissionsAsync();
let location = await Location.getCurrentPositionAsync({});
console.log(location);
setLatitute(location.coords.latitude);
setLongitute(location.coords.longitude);
setIsLoading(false);
})();
}, []);
return (
<SafeAreaView
style={{
display: "flex",
flex: 1,
}}
>
{!isLoading && (
<View>
<MapView
style={{ width: SCREEN_WIDTH, height: SCREEN_HEIGHT }}
showsUserLocation
initialRegion={{
latitude: latitute,
longitude: longitute,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}}
/>
<Searchbar
placeholder="Search"
style={{
position: "absolute",
top: 10,
margin: 10,
}}
icon="menu"
onIconPress={() => {}}
/>
<FAB
style={{
position: "absolute",
top: SCREEN_HEIGHT * 0.8,
left: SCREEN_WIDTH * 0.8,
}}
icon="plus"
onPress={() => console.log("Pressed")}
/>
<Animated.View
style={[
animatedHeight,
{
position: "absolute",
right: 0,
left: 0,
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT,
backgroundColor: "#0af",
borderTopLeftRadius: 25,
borderTopRightRadius: 25,
zIndex: 10,
},
]}
{...panResponder.panHandlers}
>
<View>
<Text>Hi</Text>
</View>
</Animated.View>
</View>
)}
</SafeAreaView>
);
};
export default App;
如果你想做 bottomsheet 那么你可以使用 react-native-bottomsheet-reanimated
yarn add react-native-bottomsheet-reanimated
import React, { useEffect, useState } from "react";
import {
SafeAreaView,
View,
Text,
Dimensions,
PanResponder,
Animated,
StyleSheet
} from "react-native";
import { Searchbar, FAB } from "react-native-paper";
import MapView from "react-native-maps";
import * as Location from "expo-location";
import BottomSheet from "react-native-bottomsheet-reanimated";
const SCREEN_HEIGHT = Dimensions.get("window").height;
const SCREEN_WIDTH = Dimensions.get("window").width;
const App = () => {
const [latitute, setLatitute] = useState(0);
const [longitute, setLongitute] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const pan = useState(
new Animated.ValueXY({ x: 0, y: SCREEN_HEIGHT - 200 })
)[0];
useEffect(() => {
(async () => {
let { status } = await Location.requestPermissionsAsync();
let location = await Location.getCurrentPositionAsync({});
console.log(location);
setLatitute(location.coords.latitude);
setLongitute(location.coords.longitude);
setIsLoading(false);
})();
}, []);
return (
<SafeAreaView
style={{
display: "flex",
flex: 1,
}}
>
{!isLoading && (
<View>
<MapView
style={{ width: SCREEN_WIDTH, height: SCREEN_HEIGHT }}
showsUserLocation
initialRegion={{
latitude: latitute,
longitude: longitute,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}}
/>
<Searchbar
placeholder="Search"
style={{
position: "absolute",
top: 10,
margin: 10,
}}
icon="menu"
onIconPress={() => {}}
/>
<FAB
style={{
position: "absolute",
top: SCREEN_HEIGHT * 0.8,
left: SCREEN_WIDTH * 0.8,
}}
icon="plus"
onPress={() => console.log("Pressed")}
/>
<BottomSheet
bottomSheerColor="#FFFFFF"
initialPosition={"30%"} //200, 300
snapPoints={["30%","100%"]}
isBackDropDismisByPress={true}
isRoundBorderWithTipHeader={true}
containerStyle={{backgroundColor:"#0af"}}
header={
<View>
<Text style={styles.text}>Header</Text>
</View>
}
body={
<View style={styles.body}>
<Text>Hi</Text>
</View>
}
/>
</View>
)}
</SafeAreaView>
);
};
export default App;
const styles = StyleSheet.create({
body:{
justifyContent:"center",
alignItems:"center"
},
text:{
fontSize:20,
fontWeight:"bold"
}
});
我是 react-native 动画 API 和 PanResponder API 的新手。
我想创建 google 地图克隆 UI 和在结果后显示的卡片动画。
我想创建卡片,当用户向上滑动时,卡片会到达屏幕顶部,当用户向下滑动时,卡片应调整到原始位置。
Demo of Google Maps that i want to achieve
我已经为此实现了一些代码,但我面临的问题是卡片在顶部方向移出屏幕(在卡片到达顶部位置后向上滑动)。有时,向下滑动有效,但并非总是如此。
App.js
import React, { useEffect, useState } from "react";
import {
SafeAreaView,
View,
Text,
Dimensions,
PanResponder,
Animated,
} from "react-native";
import { Searchbar, FAB } from "react-native-paper";
import MapView from "react-native-maps";
import * as Location from "expo-location";
const SCREEN_HEIGHT = Dimensions.get("window").height;
const SCREEN_WIDTH = Dimensions.get("window").width;
const App = () => {
const [latitute, setLatitute] = useState(0);
const [longitute, setLongitute] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const pan = useState(
new Animated.ValueXY({ x: 0, y: SCREEN_HEIGHT - 200 })
)[0];
const panResponder = useState(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
pan.extractOffset();
return true;
},
onPanResponderMove: (e, gestureState) => {
pan.setValue({ x: 0, y: gestureState.dy });
},
onPanResponderRelease: (e, gestureState) => {
if (gestureState.moveY > SCREEN_HEIGHT - 200) {
Animated.spring(pan.y, {
toValue: 0,
tension: 1,
useNativeDriver: true,
}).start();
} else if (gestureState.moveY < 200) {
Animated.spring(pan.y, {
toValue: 0,
tension: 1,
useNativeDriver: true,
}).start();
} else if (gestureState.dy < 0) {
Animated.spring(pan.y, {
toValue: -SCREEN_HEIGHT + 200,
tension: 1,
useNativeDriver: true,
}).start();
} else if (gestureState.dy > 0) {
Animated.spring(pan.y, {
toValue: SCREEN_HEIGHT - 200,
tension: 1,
useNativeDriver: true,
}).start();
}
},
})
)[0];
const animatedHeight = {
transform: pan.getTranslateTransform(),
};
useEffect(() => {
(async () => {
let { status } = await Location.requestPermissionsAsync();
let location = await Location.getCurrentPositionAsync({});
console.log(location);
setLatitute(location.coords.latitude);
setLongitute(location.coords.longitude);
setIsLoading(false);
})();
}, []);
return (
<SafeAreaView
style={{
display: "flex",
flex: 1,
}}
>
{!isLoading && (
<View>
<MapView
style={{ width: SCREEN_WIDTH, height: SCREEN_HEIGHT }}
showsUserLocation
initialRegion={{
latitude: latitute,
longitude: longitute,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}}
/>
<Searchbar
placeholder="Search"
style={{
position: "absolute",
top: 10,
margin: 10,
}}
icon="menu"
onIconPress={() => {}}
/>
<FAB
style={{
position: "absolute",
top: SCREEN_HEIGHT * 0.8,
left: SCREEN_WIDTH * 0.8,
}}
icon="plus"
onPress={() => console.log("Pressed")}
/>
<Animated.View
style={[
animatedHeight,
{
position: "absolute",
right: 0,
left: 0,
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT,
backgroundColor: "#0af",
borderTopLeftRadius: 25,
borderTopRightRadius: 25,
zIndex: 10,
},
]}
{...panResponder.panHandlers}
>
<View>
<Text>Hi</Text>
</View>
</Animated.View>
</View>
)}
</SafeAreaView>
);
};
export default App;
如果你想做 bottomsheet 那么你可以使用 react-native-bottomsheet-reanimated
yarn add react-native-bottomsheet-reanimated
import React, { useEffect, useState } from "react";
import {
SafeAreaView,
View,
Text,
Dimensions,
PanResponder,
Animated,
StyleSheet
} from "react-native";
import { Searchbar, FAB } from "react-native-paper";
import MapView from "react-native-maps";
import * as Location from "expo-location";
import BottomSheet from "react-native-bottomsheet-reanimated";
const SCREEN_HEIGHT = Dimensions.get("window").height;
const SCREEN_WIDTH = Dimensions.get("window").width;
const App = () => {
const [latitute, setLatitute] = useState(0);
const [longitute, setLongitute] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const pan = useState(
new Animated.ValueXY({ x: 0, y: SCREEN_HEIGHT - 200 })
)[0];
useEffect(() => {
(async () => {
let { status } = await Location.requestPermissionsAsync();
let location = await Location.getCurrentPositionAsync({});
console.log(location);
setLatitute(location.coords.latitude);
setLongitute(location.coords.longitude);
setIsLoading(false);
})();
}, []);
return (
<SafeAreaView
style={{
display: "flex",
flex: 1,
}}
>
{!isLoading && (
<View>
<MapView
style={{ width: SCREEN_WIDTH, height: SCREEN_HEIGHT }}
showsUserLocation
initialRegion={{
latitude: latitute,
longitude: longitute,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}}
/>
<Searchbar
placeholder="Search"
style={{
position: "absolute",
top: 10,
margin: 10,
}}
icon="menu"
onIconPress={() => {}}
/>
<FAB
style={{
position: "absolute",
top: SCREEN_HEIGHT * 0.8,
left: SCREEN_WIDTH * 0.8,
}}
icon="plus"
onPress={() => console.log("Pressed")}
/>
<BottomSheet
bottomSheerColor="#FFFFFF"
initialPosition={"30%"} //200, 300
snapPoints={["30%","100%"]}
isBackDropDismisByPress={true}
isRoundBorderWithTipHeader={true}
containerStyle={{backgroundColor:"#0af"}}
header={
<View>
<Text style={styles.text}>Header</Text>
</View>
}
body={
<View style={styles.body}>
<Text>Hi</Text>
</View>
}
/>
</View>
)}
</SafeAreaView>
);
};
export default App;
const styles = StyleSheet.create({
body:{
justifyContent:"center",
alignItems:"center"
},
text:{
fontSize:20,
fontWeight:"bold"
}
});