I'm new to react-native animation API and PanResponder API.
I want to create google maps clone UI and animations of card which is show after results.
I want to create card and when user swipe up, the card you reach to top of screen and when user swipe down, the card should resize to original position.
Demo of Google Maps that i want to achieve
I have implemented some of code for that, but i am facing problem like card is moving out of screen in top direction (swiping up after card is reach to top position). Sometimes, swipe down works but not always.
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;
if you wanna make bottomsheet then you can use 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"
}
});