How animate Opacity in Flatlist with 2 columns - reactjs

I try to make a little opacity animation when items touch the top of the flatlist.
The problem is that just one item rendered with the animation and not 2 Items at the same row.
I want that as soon as the top of these two Items touch the top of the Flatlistview, the opacity decrease until two others Items's top below. And so on.
I put my code here if anyone has some solution for that.
Thank you !
import React, { Component, Fragment } from 'react';
import { View, StyleSheet,Dimensions, Button, Pressable, TouchableOpacity, SafeAreaView, Text, KeyboardAvoidingView, Switch,Image,FlatList, Animated } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';
import Data_hobbys from '../../Data_hobbys';
import { TextInput,ProgressBar , Colors } from 'react-native-paper';
const DATA = Data_hobbys;
const numColumns = 2;
const { height } = Dimensions.get("screen");
const Item = ({ title, path, opacity }) => (
<Animated.View style = {[styles.item, {opacity: opacity}]}>
<Image style = {styles.imageStyle} source = {path}/>
<Text style={styles.title}>{title}</Text>
</Animated.View>
);
const Sports_selection = () => {
const scrollY = React.useRef(new Animated.Value(0)).current
const renderItem = ({ item, index}) => {
const opacityInputRange = [
-1,
0,
150 * index,
150 * (index + 1)
]
const opacity = scrollY.interpolate({
inputRange: opacityInputRange,
outputRange: [1 ,1 ,1 ,0]
})
return (<Item title={item.title} path = {item.path} opacity = {opacity} />
);
}
return(
<View style = {styles.main_container}>
<ProgressBar progress={0.4} style = {styles.progressBar} color = '#D1A552'/>
<Text style = {styles.introduction_text}>Do you some sport or watch it ?</Text>
<Text style = {styles.instruction_text}>Yes ? Great select it !</Text>
<Animated.FlatList
onScroll = {Animated.event(
[{ nativeEvent: {contentOffset: {y: scrollY}}}],
{useNativeDriver: true}
)}
//contentContainerStyle={{flexDirection : "row", flexWrap : "wrap"}}
columnWrapperStyle={{justifyContent: 'space-evenly'}}
data = {DATA}
renderItem = {renderItem}
keyExtractor = {(item, index) => item.id}
numColumns = {numColumns}
style={{
height: "60%",
flexGrow: 0
}}
//xscrollEventThrottle={16}
style = {styles.flatlistStyle}
/>
<TouchableOpacity style = {styles.buttonNext}>
<Text style = {styles.TextButton}>Next</Text>
</TouchableOpacity>
</View>
);
}
export default Sports_selection;
const styles = StyleSheet.create({
main_container: {
flex: 1,
backgroundColor: 'white',
},
introduction_text: {
fontSize : 30,
fontWeight : 'bold',
color : '#D1A552',
textAlign : 'left',
paddingLeft: 15,
marginTop: '30%',
},
instruction_text: {
fontSize : 20,
fontWeight : 'normal',
color : 'black',
textAlign : 'left',
paddingLeft: 15,
marginTop: '2%',
},
item: {
justifyContent: 'center',
alignContent:'center',
height : 150,
width : 150,
marginVertical: 5,
},
title: {
fontWeight : 'normal',
paddingTop: 12,
fontSize: 15,
textAlign:'center',
},
imageStyle:{
alignSelf: 'center',
width: 100,
height: 100,
},
flatlistStyle:{
marginTop:'6%',
height: "50%",
flexGrow: 0,
},
buttonNext:{
position: 'absolute',
alignSelf: 'center',
backgroundColor: "#D1A552",
borderRadius: 30,
width: "40%",
height: 45,
justifyContent: "center",
bottom: "10%",
alignItems: "center",
},
TextButton:{
fontWeight : 'bold',
color: 'white',
fontSize: 20,
textAlign:'center',
},
progressBar: {
top: 70,
width: "70%",
alignSelf: 'center',
},
})

you need to calculate the scrollY with high of item
const HIGHT_OF_ITEM=100
const HIGHT_OF_CONTAINER=HIGHT_OF_ITEM+20
const Sports_selection = () => {
const scrollY = React.useRef(new Animated.Value(0)).current;
const renderItem = ({ item, index }) => {
const input_cal=(value)=>{
return value + HIGHT_OF_CONTAINER*(Math.floor(index/numColumns))
}
const opacityInputRange = [ input_cal(0), input_cal(50), input_cal(80),input_cal(100) ];
const opacity = scrollY.interpolate({
inputRange: opacityInputRange,
outputRange: [1,0.5,0.3,0]
, extrapolate:'clamp'
});
return <Item title={item.title} path={item.path} opacity={opacity} />;
};
snack demo

Related

How to make Toggle Button with Animated.View in React Native?

I can only use react native in the project, I need to make a Toggle Component with AnimatedView. I tried with react native switcher but it won't be responsive for mobile and web at the same time.
Here is my code
export const ToggleButton = () => {
const [isEnabled, setIsEnabled] = useState(false);
const [text, setText] = useState('');
const toggleSwitch = () => {
if (isEnabled) {
setText('OFF');
} else {
setText('ON');
}
setIsEnabled(previousState => !previousState);
};
return (
<View style={styles.container}>
<View>
{isEnabled ? <Text style={styles.textOn}>On</Text> : <Text style={styles.textOff}>Off</Text>}
<Switch
trackColor={{ false: Colors.BlueLight, true: Colors.PurpleLight }}
thumbColor={isEnabled ? Colors.BlueLight : Colors.BlueLight}
ios_backgroundColor="#3E3E3E"
onValueChange={toggleSwitch}
value={isEnabled}
/>
</View>
</View>
);
};
Someone give me a recommendation how to do it?
Hye finally i made a custom switch, do check out :
Do check out this expo https://snack.expo.dev/#gaurav1995/gnarly-sandwich
Its completely built with react native, no external libraries etc
Do lemme know in case of any doubts :)
import React, { useState, useRef } from 'react';
import {
Text,
View,
StyleSheet,
Animated,
TouchableOpacity,
Easing
} from 'react-native';
export default function App() {
const positionButton = useRef(new Animated.Value(0)).current;
const [isOn, setIsOn] = useState(false);
const startAnimToOff = () => {
Animated.timing(positionButton,{
toValue:0,
duration:500,
easing:Easing.ease
}).start()
};
const startAnimToOn = () => {
Animated.timing(positionButton,{
toValue:1,
duration:500,
easing:Easing.ease
}).start()
};
const positionInterPol = positionButton.interpolate({inputRange:[0,1],outputRange:[0,30]})
const backgroundColorAnim = positionButton.interpolate({inputRange:[0,1],outputRange:["#767577","#81b0ff"]})
const initialOpacityOn = positionButton.interpolate({inputRange:[0,1],outputRange:[0,1]})
const initialOpacityOff = positionButton.interpolate({inputRange:[0,1],outputRange:[1,0]})
const onPress = () => {
if (isOn) {
startAnimToOff();
setIsOn(false);
} else {
startAnimToOn();
setIsOn(true);
}
};
return (
<View style={styles.container}>
<TouchableOpacity style={{height:30,width:60}} activeOpacity={0.9} onPress={onPress} >
<Animated.View style={[styles.mainStyes,{
backgroundColor:backgroundColorAnim
}]} >
<Animated.Text
style={[
styles.eahcStyles,
{
opacity: initialOpacityOn,
},
]}>
ON
</Animated.Text>
<Animated.Text
style={[
styles.eahcStylesOf,
{
opacity: initialOpacityOff,
},
]}>
OFF
</Animated.Text>
<Animated.View style={[styles.basicStyle,{
transform:[{
translateX:positionInterPol
}]
}]} />
</Animated.View>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#ecf0f1',
padding: 8,
},
basicStyle: {
height: 20,
width: 20,
borderRadius: 20,
backgroundColor: '#FFF',
marginTop: 5,
marginLeft: 5,
},
eahcStyles: {
fontSize: 14,
color: '#f5dd4b',
position: 'absolute',
top: 6,
left: 5,
},
eahcStylesOf: {
fontSize: 14,
color: '#f4f3f4',
position: 'absolute',
top: 6,
right: 5,
},
mainStyes: {
borderRadius: 30,
backgroundColor: '#81b0ff',
height: 30,
width: 60,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});

react native flatlist animation is not working as expected on RTL devices

I'm working in app that needs flatlist animation with RTL devices. I built a small app to understand how it will work. When using the app with LTR devices, it works just fine, and when I use I18nManager.forceRTL(true) everything messed up.
I have three slides with two components, the first component shows the slide itself and the second shows the number of the slide.
When apply force RTL the first component starts with the first slide but the second component start form the last one!
Also, when I use two animations (transform and opacity) for one element of the first component. The first slide and the last slide disappeared!
Here is my code with force RTL:
import React, { useRef } from "react";
import {
SafeAreaView,
View,
StyleSheet,
Text,
StatusBar,
Dimensions,
Animated,
} from "react-native";
import { I18nManager } from "react-native";
I18nManager.allowRTL(true);
I18nManager.forceRTL(true);
const { width, height } = Dimensions.get("window");
const DATA = [
{
id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
title: "First Slide",
number: "1",
},
{
id: "3ac68afc-c605-48d3-a4f8-fbd91aa97f63",
title: "Second Slide",
number: "2",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d72",
title: "Third Slide",
number: "3",
},
];
const Item = ({ title, index, scrollX }) => {
const inputRange = [(index - 1) * width, index * width, (index + 1) * width];
const opacityInputRange = [
(index - 0.4) * width,
index * width,
(index + 0.4) * width,
];
const scale = scrollX.interpolate({
inputRange,
outputRange: [0, 1, 0],
});
const opacity = scrollX.interpolate({
inputRange: opacityInputRange,
outputRange: [0, 1, 0],
});
return (
<View style={styles.mainConatainer}>
<Animated.View
style={{
backgroundColor: "blue",
width: width / 1.6,
height: height / 3,
alignItems: "center",
justifyContent: "center",
borderRadius: width,
transform: [{ scale }],
opacity,
}}
>
<Animated.Text
style={{
fontSize: 32,
color: "white",
transform: [{ scale }],
}}
>
{title}
</Animated.Text>
</Animated.View>
</View>
);
};
const Footer = ({ scrollX }) => {
return (
<View>
{DATA.map(({ number }, index) => {
const inputRange = [
(index - 1) * width,
index * width,
(index + 1) * width,
];
const scale = scrollX.interpolate({
inputRange,
outputRange: [0, 1, 0],
});
return (
<Animated.View key={index} style={{ transform: [{ scale }] }}>
<Text
style={{
fontSize: 50,
alignItems: "center",
marginBottom: 50,
position: "absolute",
bottom: "40%",
right: "50%",
color: "blue",
}}
>
{number}
</Text>
</Animated.View>
);
})}
</View>
);
};
export default function App() {
const scrollX = useRef(new Animated.Value(0)).current;
return (
<SafeAreaView style={styles.container}>
<Animated.FlatList
data={DATA}
renderItem={({ item, index }) => (
<Item {...item} index={index} scrollX={scrollX} />
)}
keyExtractor={(item) => item.id}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }],
{ useNativeDriver: true }
)}
scrollEventThrottle={16}
inverted={true}
/>
<Footer scrollX={scrollX} />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
backgroundColor: "lightgrey",
},
mainConatainer: {
width,
alignItems: "center",
justifyContent: "center",
},
item: {
backgroundColor: "blue",
width: width / 1.6,
height: height / 3,
alignItems: "center",
justifyContent: "center",
borderRadius: width,
},
title: {
fontSize: 64,
color: "white",
},
});
Without force RTL:
import React, { useRef } from "react";
import {
SafeAreaView,
View,
StyleSheet,
Text,
StatusBar,
Dimensions,
Animated,
} from "react-native";
const { width, height } = Dimensions.get("window");
const DATA = [
{
id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
title: "First Slide",
number: "1",
},
{
id: "3ac68afc-c605-48d3-a4f8-fbd91aa97f63",
title: "Second Slide",
number: "2",
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d72",
title: "Third Slide",
number: "3",
},
];
const Item = ({ title, index, scrollX }) => {
const inputRange = [(index - 1) * width, index * width, (index + 1) * width];
const opacityInputRange = [
(index - 0.4) * width,
index * width,
(index + 0.4) * width,
];
const scale = scrollX.interpolate({
inputRange,
outputRange: [0, 1, 0],
});
const opacity = scrollX.interpolate({
inputRange: opacityInputRange,
outputRange: [0, 1, 0],
});
return (
<View style={styles.mainConatainer}>
<Animated.View
style={{
backgroundColor: "blue",
width: width / 1.6,
height: height / 3,
alignItems: "center",
justifyContent: "center",
borderRadius: width,
transform: [{ scale }],
opacity,
}}
>
<Animated.Text
style={{
fontSize: 32,
color: "white",
transform: [{ scale }],
}}
>
{title}
</Animated.Text>
</Animated.View>
</View>
);
};
const Footer = ({ scrollX }) => {
return (
<View>
{DATA.map(({ number }, index) => {
const inputRange = [
(index - 1) * width,
index * width,
(index + 1) * width,
];
const scale = scrollX.interpolate({
inputRange,
outputRange: [0, 1, 0],
});
return (
<Animated.View key={index} style={{ transform: [{ scale }] }}>
<Text
style={{
fontSize: 50,
alignItems: "center",
marginBottom: 50,
position: "absolute",
bottom: "40%",
right: "50%",
color: "blue",
}}
>
{number}
</Text>
</Animated.View>
);
})}
</View>
);
};
export default function App() {
const scrollX = useRef(new Animated.Value(0)).current;
return (
<SafeAreaView style={styles.container}>
<Animated.FlatList
data={DATA}
renderItem={({ item, index }) => (
<Item {...item} index={index} scrollX={scrollX} />
)}
keyExtractor={(item) => item.id}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }],
{ useNativeDriver: true }
)}
scrollEventThrottle={16}
inverted={true}
/>
<Footer scrollX={scrollX} />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
backgroundColor: "lightgrey",
},
mainConatainer: {
width,
alignItems: "center",
justifyContent: "center",
},
item: {
backgroundColor: "blue",
width: width / 1.6,
height: height / 3,
alignItems: "center",
justifyContent: "center",
borderRadius: width,
},
title: {
fontSize: 64,
color: "white",
},
});

Navigating to another screen while passing state to a button

So I originally needed to pass states while navigating from a screen to another because I thought that would be enough to update a button as well as the screen iteself. However, the button that controlled the states is not being updated with the screen.
In the demo provided below(I included the code here as well) you can see that when navigating from Screen3, the state updates so that the red screen renders but the button at the top does not update as well.
How can I update the button along with the screen being updated?
I need to go from screen3 to the red screen while the button at the top shows we are on the red screen as well.
Here is the demo as well as the code below. Please keep in mind you must run the snack on IOS or android and you need to be running Expo Version 42(located in the bottom right of the screen)
Thank you for any insight at all! I appreciate it more than you know.
Home.js
import Slider from './components/slider'
const Home = ({ route }) => {
const [isVisile, setIsVisible] = React.useState(true);
const [whichComponentToShow, setComponentToShow] = React.useState("Screen1");
React.useEffect(() => {
if(route.params && route.params.componentToShow) {
setComponentToShow(route.params.componentToShow);
}
}, [route.params]);
const goToMap = () => {
setComponentToShow("Screen2");
}
const goToList = () => {
setComponentToShow("Screen1");
}
return(
<View style={{backgroundColor: '#d1cfcf' ,flex: 1}}>
{whichComponentToShow === 'Screen1' && <ListHome />}
{whichComponentToShow === 'Screen2' && <MapHome />}
<View style={{position: 'absolute', top: 0, left: 0, right: 1}}>
<Slider
renderMap={goToMap}
renderList={goToList}
/>
</View>
</View>
);
}
Screen3.js
const Screen3 = (props) => {
const navigation = useNavigation();
const onPress = () => {
navigation.navigate('Home', {
screen: 'Home',
params: {
componentToShow: 'Screen2'
}
});
}
return (
<View
style={{
flex: 1,
backgroundColor: 'white',
justifyContent: 'center',
alignItems: 'center',
}}>
<TouchableOpacity onPress={onPress}>
<Text style={{ color: 'black', fontSize: 25 }}>
Navigate to home and change to map screen
</Text>
</TouchableOpacity>
</View>
);
};
Finally Slider.js
const Slider = (props) => {
const [active, setActive] = useState(false)
let transformX = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (active) {
Animated.timing(transformX, {
toValue: 1,
duration: 300,
useNativeDriver: true
}).start()
} else {
Animated.timing(transformX, {
toValue: 0,
duration: 300,
useNativeDriver: true
}).start()
}
}, [active]);
const rotationX = transformX.interpolate({
inputRange: [0, 1],
outputRange: [2, Dimensions.get('screen').width / 4]
})
return (
code for animation
)
Your Slider component needs to listen to screen focus with useFocusEffect and react accordingly. I added new props active to detect if the map screen is active.
Slider.js
import * as React from 'react';
import { useState, useEffect, useRef } from 'react'
import { View, Text, StyleSheet, Animated, TouchableOpacity, SafeAreaView, Dimensions, } from 'react-native';
import {
scale,
verticalScale,
moderateScale,
ScaledSheet,
} from 'react-native-size-matters';
import { useFocusEffect } from '#react-navigation/native';
const Slider = (props) => {
const [active, setActive] = useState(false)
let transformX = useRef(new Animated.Value(0)).current;
useFocusEffect( React.useCallback(()=>{
setActive(Boolean(props.active))
console.log()
},[props.active]))
useEffect(() => {
if (active) {
Animated.timing(transformX, {
toValue: 1,
duration: 300,
useNativeDriver: true
}).start()
} else {
Animated.timing(transformX, {
toValue: 0,
duration: 300,
useNativeDriver: true
}).start()
}
}, [active]);
const rotationX = transformX.interpolate({
inputRange: [0, 1],
outputRange: [2, Dimensions.get('screen').width / 4]
})
return (
<SafeAreaView style={{
alignItems: 'center',
backgroundColor:'transparent'
}}>
<View style={{
flexDirection: 'row',
position: 'relative',
height: 45,
width: 240,
borderRadius: 10,
backgroundColor: 'white',
marginHorizontal: 5
}}>
<Animated.View
style={{
position: 'absolute',
height: 45 - 2*2,
top: 2,
bottom: 2,
borderRadius: 10,
width: Dimensions
.get('screen').width / 3 - 3.5 ,
transform: [
{
translateX: rotationX
}
],
backgroundColor: '#d1cfcf',
}}
>
</Animated.View>
<TouchableOpacity style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}} onPress={() => {setActive(false); props.renderList() }}>
<Text>
List
</Text>
</TouchableOpacity>
<TouchableOpacity style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}} onPress={() => {setActive(true); props.renderMap() }}>
<Text>
Map
</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
export default Slider
I update Slider Component in Home.js as below.
<Slider
renderMap={goToMap}
renderList={goToList}
active={route.params && route.params.componentToShow==='Screen2'}
/>
Check full working snack:
https://snack.expo.dev/#emmbyiringiro/283f93

React Native TouchableHighlight onFocus onBlur

I am creating a menu for an Android TV, the Menu expands when it get focus, but when moving from one menu item to the other I get a flicker because I can't find a way to make the menu focus independent from the menu item focus.
I've tried to use a Touchable around the Flat list but then i cant move focus to the items.
import { FlatList, StyleSheet, Text, TouchableHighlight, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { MaterialIcons } from '#expo/vector-icons';
import React from 'react';
interface MenuListOption {
name: string;
icon: string;
}
interface MenuItemProps {
setMenuFocus: (v: boolean) => void;
item: MenuListOption;
menuFocus: boolean;
}
export const homeLists: MenuListOption[] = [
{
name: 'HOME',
icon: 'home-filled',
},
{
name: 'MOVIES',
icon: 'local-movies',
},
{
name: 'TV SHOWS',
icon: 'movie',
},
];
const MenuItem = ({ setMenuFocus, item, menuFocus }: MenuItemProps): JSX.Element => {
const [focus, setFocus] = React.useState(false);
const onFocus = React.useCallback(() => {
setFocus(true);
setMenuFocus(true);
}, [setMenuFocus]);
const onBlur = React.useCallback(() => {
setFocus(false);
setMenuFocus(false);
}, [setMenuFocus]);
return (
<TouchableHighlight onFocus={onFocus} onBlur={onBlur} style={[styles.item, focus ? styles.itemFocused : null]}>
<View style={styles.itemWrapper}>
<MaterialIcons name={item.icon} size={50} color="red" />
{menuFocus && <Text style={styles.text}>{item.name}</Text>}
</View>
</TouchableHighlight>
);
};
const Menu = (): JSX.Element => {
const [focus, setFocus] = React.useState(false);
const colorsArray = focus ? ['gray', 'gray', 'transparent'] : ['gray', 'gray'];
const renderItem = ({ item }: { item: MenuListOption }) => (
<MenuItem setMenuFocus={setFocus} item={item} menuFocus={focus} />
);
return (
<View style={[styles.wrapper, focus ? styles.wrapperFocused : null]}>
<LinearGradient colors={colorsArray} start={{ x: 0, y: 0.9 }} end={{ x: 1, y: 0.9 }}>
<View style={focus ? styles.logoFocus : styles.logo}>
{focus && <MaterialIcons style={{ paddingRight: 20 }} name={'tv'} size={40} color={'white'} />}
<Text style={[styles.title, focus && styles.textFocus]}>MyApp</Text>
</View>
<FlatList
contentContainerStyle={{ justifyContent: 'center', flex: 1 }}
style={styles.list}
data={homeLists}
renderItem={renderItem}
keyExtractor={(item) => String(item.name)}
/>
</LinearGradient>
</View>
);
};
const styles = StyleSheet.create({
wrapper: {
height: '100%',
position: 'absolute',
top: 0,
zIndex: 1,
left: -200,
transform: [{ translateX: 200 }],
},
title: {
fontSize: 15,
lineHeight: 38,
fontWeight: '400',
textAlign: 'left',
},
wrapperFocused: {
width: 900,
},
logo: {
justifyContent: 'center',
flexDirection: 'row',
marginTop: 20,
},
list: {
flexGrow: 0,
height: '100%',
padding: 10,
},
logoFocus: {
justifyContent: 'flex-start',
flexDirection: 'row',
marginTop: 60,
marginLeft: 20,
},
textFocus: {
fontSize: 35,
},
item: {
maxWidth: 250,
marginBottom: 40,
alignSelf: 'stretch',
left: 0,
padding: 10,
},
itemFocused: {
borderBottomColor: 'yellow',
borderBottomWidth: 5,
},
itemWrapper: {
maxWidth: 250,
flexDirection: 'row',
},
text: {
color: 'white',
fontSize: 28,
lineHeight: 41,
fontWeight: '600',
marginLeft: 50,
marginTop: 5,
},
});
export default Menu;
Here is an animation of the problem:
menu

Why is react-native Animated View not rendering?

Goal: create a main button that when pressed, spins 180 degrees along the Z axis, and starts an animation for the opacity and translation (Y axis) of secondary option buttons that are revealed once main button is pressed. then reverse the whole process to collapse the secondary options.
MainButton implementation:
import React from 'react';
import { StyleSheet, View, Dimensions } from 'react-native';
import Component from './Component';
const optionsArr = [
{ icon: require('./src/assets/img/chat.png'), onPress: () => alert('option 1') },
{ icon: require('./src/assets/img/video_icon.png'), onPress: () => alert('option 2') },
{ icon: require('./src/assets/img/voice_icon.png'), onPress: () => alert('option 3') },
{ icon: require('./src/assets/img/camera.png'), onPress: () => alert('option 4') }
];
const App = () => {
return (
<View style={styles.screen}>
<Component icon={require('./src/assets/img/arrow-up.png')} optionItems={optionsArr} />
</View>
);
};
const styles = StyleSheet.create({
screen: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#E6E6E6'
}
});
export default App;
MainButton.js:
import React, { useState, useEffect } from 'react';
import { Image, Animated, StyleSheet, TouchableOpacity, Dimensions } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import OptionItemBtn from './OptionItemBtn';
const { height, width } = Dimensions.get('window');
const MainButton = (props) => {
const [ animatedHeight ] = useState(new Animated.Value(0));
const [ animatedRotate ] = useState(new Animated.Value(0));
const [ expanded, setExpanded ] = useState(false);
const handlePress = () => {
if (expanded) {
// button is opened
let collapseHeight = 0.00000001;
Animated.parallel([
Animated.spring(animatedHeight, {
toValue: collapseHeight
}),
Animated.spring(animatedRotate, {
toValue: 0
})
]).start();
setExpanded(!expanded);
} else {
// button is collapsed
Animated.parallel([
Animated.spring(animatedHeight, {
toValue: width * 0.13 * props.optionItems.length
}),
Animated.spring(animatedRotate, {
toValue: 1
})
]).start();
}
};
const animatedRotation = animatedRotate.interpolate({
inputRange: [ 0, 0.5, 1 ],
outputRange: [ '0deg', '90deg', '180deg' ]
});
return (
<Animated.View style={{ transform: [ { rotateZ: animatedRotation } ] }}>
<Animated.View style={{ transform: [ { translateY: animatedHeight } ] }}>
{props.optionItems.map((item, index) => {
<OptionItemBtn icon={item.icon} onPress={item.onPress} index={index} />;
})}
</Animated.View>
<TouchableOpacity style={styles.container} onPress={() => handlePress()}>
<Image resizeMode={'contain'} source={props.icon} style={styles.icon} />
</TouchableOpacity>
</Animated.View>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 30,
backgroundColor: '#E06363',
elevation: 15,
shadowOffset: {
height: 3,
width: 3
},
shadowColor: '#333',
shadowOpacity: 0.5,
shadowRadius: 5,
height: width * 0.14,
width: width * 0.14
},
icon: {
height: width * 0.06,
width: width * 0.06
}
});
export default MainButton;
OptionItem.js:
import React from 'react';
import { Image, StyleSheet, TouchableOpacity, Dimensions } from 'react-native';
const { height, width } = Dimensions.get('window');
const OptionItemBtn = (props) => {
return (
<TouchableOpacity style={styles.container} onPress={props.onPress}>
<Image resizeMode={'contain'} source={props.icon} style={styles.icon} />
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 30,
backgroundColor: '#219F75',
elevation: 5,
shadowOffset: {
height: 3,
width: 3
},
shadowColor: '#333',
shadowOpacity: 0.5,
shadowRadius: 5,
height: width * 0.13,
width: width * 0.13,
position: 'absolute'
},
icon: {
height: width * 0.08,
width: width * 0.08
}
});
export default OptionItemBtn;
Problem: The Main button displays and animates as expected. it spins along the Z-axis correctly when expanding and collapsing. the issue is the OptionItemBtn's dont render at all. Why?
{props.optionItems.map((item, index) =>
<OptionItemBtn icon={item.icon} onPress={item.onPress} index={index} />
)}
Make change in your MainButton.js file as above

Resources