Reputation: 3423
I am really new to React-native-reanimated. I am trying to create one custom bottom-sheet like this app. I am using PanGestureHandler
from react-native-gesture-handler
for move the Animated View to go up and down. For gestureHandler
I am using useAnimatedGestureHandler
props from react-native-reanimated
. I want to move the Animated View from start point to middle screen and bottom of screen. This is My Bottom sheet start point image, when scroll the card down it should come middle of the screen like this image, again scroll down the card it will come bottom like this image.
I am having difficulties with the conditional useAnimatedGestureHandler
onEnd movement. Currently I am tracking onEnd's event.translationY
and make a condition out of it.
This is how it works currently:
When the App start, the Animated View is top of the screen, if I move the card scroll to bottom it goes middle of the screen and it does not go down from middle of the screen, I can move it to up from middle of the screen or if I scroll hard to bottom it goes all the way to bottom and if I try scroll the View up it does not go middle, it just goes up to start View.
I am trying to make the condition based screen size but I don't know how to make it.
I shared my code in expo-snacks
This is my all code
import React, { useState, useEffect } from "react";
import { StyleSheet, useWindowDimensions, RefreshControl } from "react-native";
import MapView from "react-native-maps";
import styled from "styled-components";
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
FlatList,
} from "react-native-gesture-handler";
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
Easing,
withSpring,
} from "react-native-reanimated";
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 top = useSharedValue(height);
const [event, setEvent] = useState([]);
const [loading, setLoading] = useState(false);
const prevTop = useSharedValue(height * 0.5);
// This is Fetch Data
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();
}, []);
const animatedStyle = useAnimatedStyle(() => {
return {
top: top.value * 0.2,
bottom: 0,
};
});
const gestureHandler = useAnimatedGestureHandler(
{
onStart(_, context) {
context.translateY = top.value;
},
onActive(event, context) {
top.value = context.translateY + event.translationY;
},
onEnd(event, _) {
// THIS IS MY CONDITION OF ANIMATED VIEW
if (event.translationY > 0 && event.translationY < 400) {
console.log("middle-top", top.value);
console.log("middle-height", height);
top.value = withSpring(height * 2.5, {
duration: 500,
easing: Easing.inOut(Easing.ease),
});
} else if (event.translationY > 450 && event.translationY < 800) {
console.log("bottom-top", top.value);
console.log("bottom-height", height);
top.value = withSpring(height * 4, {
duration: 500,
easing: Easing.inOut(Easing.ease),
});
} else if (event.translationY < 0) {
console.log("start-top", top.value);
console.log("start-height", height);
top.value = withSpring(height, {
duration: 500,
easing: Easing.inOut(Easing.ease),
});
}
},
},
[top]
);
return (
<>
<MapView style={styles.mapStyle} initialRegion={initialRegion} />
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View style={[styles.container, 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>
);
}}
/>
</Animated.View>
</PanGestureHandler>
</>
);
}
const styles = StyleSheet.create({
container: {
position: "absolute",
left: 0,
right: 0,
top: 0,
backgroundColor: "white",
shadowOffset: {
height: -6,
width: 0,
},
shadowOpacity: 0.1,
shadowRadius: 5,
borderTopEndRadius: 15,
borderTopLeftRadius: 15,
},
mapStyle: {
flex: 1,
},
});
const HeroFlatList = styled(FlatList).attrs({
contentContainerStyle: {
flexGrow: 1,
},
})`
padding: 12px;
`;
const Title = styled.Text`
font-size: 16px;
font-weight: 700;
margin-bottom: 10px;
align-self: center;
padding: 10px;
`;
const DescriptionText = styled.Text`
font-size: 14px;
opacity: 0.7;
`;
const DateText = styled.Text`
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: #fff;
shadow-color: #000;
shadow-opacity: 0.3;
shadow-radius: 20px;
shadow-offset: 0 10px;
`;
Tech | Version |
---|---|
react-native-gesture-handler | ^1.10.3 |
react-native-reanimated | ^2.2.0 |
Upvotes: 3
Views: 3821
Reputation: 3317
Not the perfect solution... added a new sharedValue to track if its moving up or down.
const prevTop = useSharedValue(height * 0.5);
and respective code on gesture end.
onEnd() {
if (top.value > prevTop.value) {
top.value = withTiming(height * 0.98);
} else {
top.value = withTiming(Math.min(200, top.value));
}
prevTop.value = top.value;
},
Still there is a scope of improvement.
Upvotes: 1