React native Flatlist does not scroll inside the custom Animated Bottom sheet - react-native

I have created one custom Animated bottom sheet. User can move the bottom sheet scroll up and down. Inside my bottom sheet, I have used flatList where I fetched the data and render the items as a card. Up-till now everything works as expected but I had an issue Flatlist scrolling. Inside the bottom sheet the Flat-list does not scroll. I have made hard coded height value 2000px, which is really practice and also FlatList's contentContainerStyle added hard coded paddingBottom 2000(also another bad practice). I want to scroll the FlatList based on Flex-box. I don't know how to fix this issue.
I share my code on expo-snacks
This is my all code
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;
`;

Here is the updated version of your code. working fine on simulator
import React, { useState, useEffect, useRef } from "react";
import {
StyleSheet,
View,
Dimensions,
SafeAreaView,
RefreshControl,
Animated,
LayoutAnimation,
} from "react-native";
import MapView from "react-native-maps";
import styled from "styled-components";
import { PanGestureHandler, FlatList } from "react-native-gesture-handler";
const { width, height } = Dimensions.get("screen");
const extendedHeight = height * 0.7;
const normalHeight = height * 0.4;
const bottomPadding = height * 0.15;
if (
Platform.OS === "android" &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
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 translateY = useRef(new Animated.Value(0)).current;
const [flatListHeight, setFlatListHeight] = useState(extendedHeight);
const [event, setEvent] = useState([]);
const [loading, setLoading] = useState(false);
// This is Fetch Dtata
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(api);
const json = (await response.json()).data;
setEvent(json);
} catch (error) {
console.log("erro", error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
const bottomSheetIntropolate = translateY.interpolate({
inputRange: [0, 1],
outputRange: [-normalHeight, 0],
});
const animatedStyle = {
transform: [
{
translateY: bottomSheetIntropolate,
},
],
};
const animate = (bringDown) => {
setFlatListHeight(extendedHeight);
Animated.timing(translateY, {
toValue: bringDown ? 1 : 0,
duration: 500,
useNativeDriver: true,
}).start();
};
const onGestureEnd = (e) => {
if (e.nativeEvent.translationY > 0) {
LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
setFlatListHeight(normalHeight);
}
};
const gestureHandler = (e) => {
if (e.nativeEvent.translationY > 0) {
animate(true);
} else if (e.nativeEvent.translationY < 0) {
animate(false);
}
};
const 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>
);
};
return (
<>
<MapView style={styles.mapStyle} initialRegion={initialRegion} />
<PanGestureHandler onGestureEvent={gestureHandler} onEnded={onGestureEnd}>
<Animated.View
style={[styles.container, { top: height * 0.7 }, animatedStyle]}
>
<SafeAreaView style={styles.wrapper}>
<ContentConatiner>
<Title>I am scroll sheet</Title>
<HeroFlatList
style={{ height: flatListHeight }}
data={event}
ListFooterComponent={() => (
<View style={{ height: bottomPadding }} />
)}
refreshControl={
<RefreshControl
enabled={true}
refreshing={loading}
onRefresh={fetchData}
/>
}
keyExtractor={(_, index) => index.toString()}
renderItem={renderItem}
/>
</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
},
})``;
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;
`;
Things I have updated in your code
useRef instead of useState for holding animated value
Replaced inline closure with const for renderItem (this can simplify the JSX and improve the performance)
Unified your animation const to one.
Utilised onEnded prop to decrease the height of FlatList gracefully with the help of LayoutAnimation
Since you were accessing the width from Dimensions, the height can also be fetched from the same resource i.e. removed the hook
updated the translateY logic calculation and Flatlist height to percentage base
listFooterItem to provide some padding to last item
A minor update to your fetch logic

If you're not against using react-native-reanimated, then I've made a minimally modified version of your code that should do exactly what you want.
I use Reanimated's v1 compatibility API, so you don't have to install the babel transpiler or anything. It should work as-is.
https://snack.expo.dev/#switt/flatlist-scroll-reanimated
Reanimated is a better fit here, because React-Native's native Animated module cannot animate the top, bottom, width, height, etc. properties, it'd likely require setting useNativeDriver to false for what you're trying to achieve. That would lead to some performance drops/choppy frames during animation.
Here's your edited code just for convenience
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;
`;

Finally I found the solution what I wanted. Thank you Stack-overflow community. Without your help I could not able to do that.
import React, { useState, useEffect } from "react";
import {
StyleSheet,
Text,
View,
Dimensions,
useWindowDimensions,
SafeAreaView,
RefreshControl,
Animated,
Platform
} from "react-native";
import MapView from "react-native-maps";
import styled from "styled-components";
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
TouchableOpacity,
FlatList
} from "react-native-gesture-handler";
const { width } = Dimensions.get("screen");
const IPHONE_DEVICE_START_HEIGHT = Platform.OS === 'ios' ? 0.4 : 0.6;
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: false,
}).start();
};
const closeDownBottomSheet = () => {
Animated.timing(translateY, {
toValue: 1,
duration: 500,
useNativeDriver: false,
}).start();
};
const bottomSheetIntropolate = translateY.interpolate({
inputRange: [0, 1],
outputRange: [
height * 0.5 - height / 2.4 + IPHONE_DEVICE_START_HEIGHT,
height * IPHONE_DEVICE_START_HEIGHT,
],
extrapolate: 'clamp',
});
const animatedStyle = {
top: bottomSheetIntropolate,
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}>
<Animated.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>
);
}}
/>
</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: {
flexGrow: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;
`;

Looking at your code,
You need to change
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
TouchableOpacity,
FlatList as GFlatList
} from "react-native-gesture-handler";
Import FlatList from react-native-gesture-handler and update your code as below,
const HeroFlatList = styled(GFlatList).attrs({
contentContainerStyle: {
paddingBottom: 50
},
height:510,
})``;
I have tested this on your code and it works. Please check.

keep HeroFlatList in scrollView.
<ScrollView>
<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>
);
}}
/>
</ScrollView>

Related

How to apply elevation to TouchableOpacity in react native?

I am using android in react native.
I have a TouchableOpacity of FirstButton and SecondButton. I gave the SecondButton a position absolute but gave the FirstButton an elevation so that the FirstButton overwrites the SecondButton.
FirstButton overwritten SecondButton, the problem is that when I run onPress, secondfunc fires. I expected secondfunc to fire because FirstButton overrides SecondButton
Why is this happening? How should I fix this?
this is my code
import React from 'react';
import styled from "styled-components/native";
import { Text, View, StyleSheet } from "react-native";
const FirstButton = styled.TouchableOpacity`
width: 100px;
height: 40px;
background: lavender;
`;
const SecondButton = styled.TouchableOpacity`
position: absolute;
top:0;
bottom:0;
right:0;
left: 5%;
width: 100px;
height: 40px;
background-color: lightpink;
`;
const App = () => {
const firstConfunc = () => {
console.log("firstConfunc");
}
const secondfunc = () => {
console.log("secondconfuc");
}
return (
<>
<FirstButton
onPress={firstConfunc}
style={styles.FirstButton}
// style={{ zIndex: 1 }}
>
<Text>FirstContain</Text>
</FirstButton>
<SecondButton
style={styles.SecondButton}
onPress={secondfunc}>
<Text>secondContainer</Text>
</SecondButton>
</>
);
};
const styles = StyleSheet.create({
FirstButton: {
elevation: 4
},
SecondButton: {
elevation: 1
}
})
export default App;
Please try something like this:
main: {
flex: 1,
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 5,
},
shadowOpacity: 0.10,
shadowRadius: 6.27,
elevation: 10,
},
Wrap your TouchableOpacity inside a View :-
import * as React from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<View style={{ backgroundColor:'white', elevation: 5}}>
<TouchableOpacity style={{padding: 20}} onPress={() => console.log("Pressed")}>
<Text>Press Me! </Text>
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
}
});

how to make modal to center in react native

How I make my box to center like justify content center but with position absolute ?
top: '50%' is too close to the bottom its not centered.
Modal.tsx
import React, { useEffect } from 'react';
import { StyleSheet, View, Pressable, StyleProp, ViewStyle } from 'react-native';
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
interface IModalCenter {
children: React.ReactNode;
onPress: () => void;
style?: StyleProp<ViewStyle>;
}
const ModalCenter = ({ children, onPress, style }: IModalCenter) => {
const x = useSharedValue(0);
useEffect(() => {
x.value = 1;
}, []);
const aView = useAnimatedStyle(() => {
const scale = withSpring(x.value);
return {
transform: [{ scale }]
}
});
return (
<GestureHandlerRootView style={s.container}>
<Animated.View style={{flexGrow: 1, backgroundColor: 'rgba(0,0,0,0.4)',}} />
<Animated.View style={[style, aView]}>
{ children }
</Animated.View>
</GestureHandlerRootView>
)
};
const s = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
flex: 1,
zIndex: 600,
margin: 'auto'
}
})
export default ModalCenter;
app.tsx
<ModalCenter style={{backgroundColor: '#fff', position: 'absolute', margin: 'auto', alignSelf: 'center', height: 200, width: 300, borderRadius: 8, elevation: 4}} onPress={handleToggleModalMessage}>
<Text>Hello</Text>
</ModalCenter>
how can i make it center ? ........................................................................................................................................................................................
You need to add 'justify-content' property to the container:
container: {
...StyleSheet.absoluteFillObject,
flex: 1,
zIndex: 600,
margin: 'auto',
justify-content: 'center' <---- add this
}

React-native reanimated distance calculation to the View

Today I want to make an animation to be determined by the user. In my example is when the user moves the square close and hit the red rectangle for example to write something in console. But i don't know how to calculate distance to the rectangle and I can't determine when the square touched the rectangle? So my code sa far -
import React from "react";
import { StyleSheet, View } from "react-native";
import {
GestureHandlerRootView,
PanGestureHandler,
PanGestureHandlerGestureEvent,
} from "react-native-gesture-handler";
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming,
} from "react-native-reanimated";
const SIZE = 100.0;
const CIRCLE_RADIUS = SIZE * 2;
type ContextType = {
translateX: number;
translateY: number;
size: number;
};
export default function App() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const size = useSharedValue(100);
const panGestureEvent = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
ContextType
>({
onStart: (event, context) => {
context.translateX = translateX.value;
context.translateY = translateY.value;
size.value = 122;
},
onActive: (event, context) => {
translateX.value = event.translationX + context.translateX;
translateY.value = event.translationY + context.translateY;
},
onEnd: () => {
const distance = Math.sqrt(translateX.value ** 2 + translateY.value ** 2);
size.value = 100;
if (distance < CIRCLE_RADIUS + SIZE / 2) {
translateX.value = withSpring(30);
translateY.value = withSpring(50);
}
},
});
const rStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: translateX.value,
},
{
translateY: translateY.value,
},
],
width: withTiming(size.value, { duration: 160 }),
height: withTiming(size.value, { duration: 160 }),
};
});
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={styles.container}>
<View style={styles.circle}>
<PanGestureHandler onGestureEvent={panGestureEvent}>
<Animated.View
style={[
{
width: size.value,
height: size.value,
backgroundColor: "rgba(0, 0, 256, 0.5)",
borderRadius: 20,
},
rStyle,
]}
/>
</PanGestureHandler>
</View>
<View
style={{ width: 30, height: 50, backgroundColor: "red", top: 80 }}
></View>
</View>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
circle: {
width: CIRCLE_RADIUS * 2,
height: CIRCLE_RADIUS * 2,
alignItems: "center",
justifyContent: "center",
borderRadius: CIRCLE_RADIUS,
borderWidth: 5,
borderColor: "rgba(0, 0, 256, 0.5)",
},
});

Why all the Icons of my custom bottom tab bar navigator are moving at the same time?

I am trying to create a CustomBottomTabNavigator in react native. By now I have applied the linear gradient and added the icons on top of the tab. My goal is to move the icon upwards when the focus is on it, but for some reason, all the icons are moving upwards when the focus is on only one icon.
Here is the code:
import React, { useRef } from "react";
import {
View,
Text,
StyleSheet,
Animated,
TouchableOpacity,
} from "react-native";
import * as Icons from "#expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
const CustomTabBar = ({ state, descriptors, navigation }) => {
let icons_name = ["home", "search", "tv", "user"];
const animatedValueHome = useRef(new Animated.Value(0)).current;
const translateY = animatedValueHome.interpolate({
inputRange: [50, 100, 150],
outputRange: [25, 50, 75],
});
const animationHome = (focus, name) => {
console.log("name", name);
navigation.navigate(name);
if (focus === true) {
Animated.timing(animatedValueHome, {
toValue: -25,
duration: 1000,
useNativeDriver: false,
}).start();
} else {
Animated.timing(animatedValueHome, {
toValue: 0,
duration: 1000,
useNativeDriver: false,
}).start();
}
};
return (
<LinearGradient
colors={["#181823", "#3A3A46", "#3A3A46"]}
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
locations={[0.2, 0.6, 0.3]}
style={styles.container}
>
<View style={styles.tabs}>
{state.routes.map((route, index) => {
const isFocused = state.index === index;
return (
<Animated.View
key={index}
style={{
flex: 1,
flexDirection: "row",
transform: [{ translateY }],
}}
>
<Icons.Feather
name={icons_name[`${index}`]}
size={24}
color="#fff"
onPress={() => animationHome(isFocused, route.name)}
/>
</Animated.View>
);
})}
</View>
</LinearGradient>
);
};
export default CustomTabBar;
const styles = StyleSheet.create({
container: {
position: "absolute",
height: 40,
bottom: 20,
right: 30,
left: 20,
elevation: 2,
borderRadius: 20,
},
tabs: {
flex: 1,
flexDirection: "row",
alignItems: "center",
marginLeft: 48,
},
});
Here is the gif of the animation that is happening
Gif. I am using animated API from react-native to achieve this animation.
Let each of the child components have their own animation values.
// In the parent component
{state.routes.map((route, index) => {
const isFocused = state.index === index;
return <Child isFocused={isFocused} />;
})}
// Then for each child
const Child = ({ isFocused }) => {
const animatedValueHome = useRef(new Animated.Value(0)).current;
const translateY = animatedValueHome.interpolate({
inputRange: [50, 100, 150],
outputRange: [25, 50, 75],
});
const animationHome = (focus, name) => {
console.log("name", name);
navigation.navigate(name);
if (focus === true) {
Animated.timing(animatedValueHome, {
toValue: -25,
duration: 1000,
useNativeDriver: false,
}).start();
} else {
Animated.timing(animatedValueHome, {
toValue: 0,
duration: 1000,
useNativeDriver: false,
}).start();
}
};
return (
<Animated.View
style={{
transform: [{ translateY }],
}}
>
<Icons.Feather onPress={() => animationHome(isFocused, route.name)}/>
</Animated.View>
);
}

Issue with React Native animations

I'm facing a problem with centring the text after the animation finishes as you can see in the video here https://www.youtube.com/watch?v=hhBGUp9_GAY&feature=youtu.be. I want to get both titles perfectly centered horizontally on all devices no matter the screen width. I'm using the Animated API. Any suggestions?
Here is my approach
import React, { useEffect } from "react";
import { View, StyleSheet, Animated, Text, Dimensions, AsyncStorage } from "react-native";
export default function Welcome({ navigation }) {
const width = Dimensions.get('screen').width
let position1 = new Animated.ValueXY(0, 0);
let position2 = new Animated.ValueXY(0, 0);
useEffect(() => {
Animated.timing(position1, {
toValue: { x: width / 4.5, y: 0 },
duration: 900
}).start();
Animated.timing(position2, {
toValue: { x: -width / 3, y: 0 },
duration: 900
}).start();
}, []);
_retrieveData = async () => {
try {
const token = await AsyncStorage.getItem('tokehhn');
if (token !== null) {
// We have data!!
setTimeout(() => navigation.navigate('Home'), 2000)
} else {
setTimeout(() => navigation.navigate('Auth'), 2000)
}
} catch (error) {
// Error retrieving data
}
};
useEffect(() => {
_retrieveData()
}, [])
return (
<View style={styles.container}>
<Animated.View style={position1.getLayout()}>
{/* <View style={styles.ball} /> */}
<Text style={{ position: 'relative', fontWeight: 'bold', fontSize: 24, color: '#5790f9' }}>Welcome to Glue</Text>
</Animated.View>
<Animated.View style={position2.getLayout()}>
{/* <View style={styles.ball} /> */}
<Text style={{ position: 'relative', right: -220, fontWeight: 'bold', fontSize: 21, color: '#5790f9' }}>Where everything happens</Text>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center'
}
});
Thats how you do it:
let {width} = Dimensions.get('window')
export default function App() {
let animation = new Animated.Value(-width);
let translateX = animation.interpolate({inputRange:[-width,0],outputRange:[2*width,0]});
React.useEffect(()=>{
Animated.timing(animation,{toValue:0}).start();
},[])//eslint-ignore-line
return (
<View style={styles.container}>
<Animated.Text style={[styles.text,{transform:[{translateX:animation}]}]}>LOL</Animated.Text>
<Animated.Text style={[styles.text,{transform:[{translateX}]}]}>Longer LOLLLL</Animated.Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
text:{
textAlign:'center'
}
});
I have created snack as well
Make it a simple and clean interpolation.
The code looks always clean, and readable if we use Animated.Value in range of 0 - 1.
Full code:
import React, {useEffect} from 'react';
import {View, StyleSheet, Animated} from 'react-native';
const App = () => {
const animate = new Animated.Value(0);
const inputRange = [0, 1];
const translate1 = animate.interpolate({inputRange, outputRange: [-100, 0]});
const translate2 = animate.interpolate({inputRange, outputRange: [100, 0]});
useEffect(() => {
Animated.timing(animate, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
}, []);
return (
<View style={styles.container}>
<Animated.Text
style={[styles.text, {transform: [{translateX: translate1}]}]}>
First Text
</Animated.Text>
<Animated.Text
style={[styles.text, {transform: [{translateX: translate2}]}]}>
Second Text
</Animated.Text>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
text: {
fontSize: 25,
},
});
Using that animated value, implement any other animations if needed.
For example, If you need to scale the text while moving:
const scale = animate.interpolate({inputRange, outputRange: [1, 1.5]});