I'm trying to use FlatList to stack custom-made dropdown menus on top of each other in React Native. I want the first dropdown to overlap the second while the second overlaps the third. The image below shows that the opposite is working, where the third dropdown overlaps the second while the second overlaps the first.
However, if I use the map method, it seems to work just fine.
import React from "react";
import { View, StyleSheet, FlatList } from "react-native";
import Dropdown from "../components/Dropdown";
import normalize from "react-native-normalize";
export default () => {
const arr = [0, 65, 130]; // controls the margin between the dropdowns // top
const layers = [3, 2, 1]; // Z-index
return (
<View style={styles.container}>
<FlatList // With FlatList
data={arr}
renderItem={({ item, index }) => (
<View style={[styles.dropdown, { top: item, zIndex: layers[index] }]}>
<Dropdown />
</View>
)}
/>
{/* {arr.map((spacing, index) => {
// With map
return (
<View
style={[styles.dropdown, { top: spacing, zIndex: layers[index] }]}
>
<Dropdown />
</View>
);
})} */}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
dropdown: {
position: "absolute",
},
cardContainer: {
top: "41%",
left: "37%",
height: normalize(134),
},
});
Dropdown code
import React, { useState, useRef } from "react";
import {
Animated,
Easing,
View,
Text,
StyleSheet,
TouchableWithoutFeedback,
} from "react-native";
import normalize from "react-native-normalize";
import DropDownIcon from "../assets/DownArrowIcon";
export default () => {
const fadeAnim = useRef(new Animated.Value(0)).current;
const [toggle, setToggle] = useState(true); // controls the dropdown animation
const [accessLevels, setAccessLevels] = useState([
"Manager",
"Viewer",
"Admin",
]);
const height = normalize(33); // Initial height of the dropdown menu
const fadeIn = () => {
// Will change fadeAnim value to .5 in
Animated.timing(fadeAnim, {
toValue: 0.5,
easing: Easing.inOut(Easing.exp),
duration: 325,
}).start();
};
const fadeOut = () => {
// Will change fadeAnim value to 0
Animated.timing(fadeAnim, {
toValue: 0,
easing: Easing.inOut(Easing.exp),
duration: 250,
}).start();
};
const handleAnimation = () => {
setToggle((prev) => !prev);
toggle ? fadeIn() : fadeOut();
};
const handleSwap = (item) => {
// swap the selected item with the first item of the dropdown menu
let index = accessLevels.indexOf(item);
setAccessLevels((prevData) => {
let data = [...prevData];
let temp = data[0];
data[0] = item;
data[index] = temp;
return data; // We could order this in alphabetical order!
});
};
return (
<Animated.View
style={[
styles.dropdown,
{
height: fadeAnim.interpolate({
inputRange: [0, 1],
outputRange: [height, normalize(125)],
}),
},
]}
>
<TouchableWithoutFeedback onPress={handleAnimation}>
{/*First dropdown item*/}
<View
style={{
flexDirection: "row",
justifyContent: "space-evenly",
alignItems: "center",
height: normalize(34),
// color is based on the type of access level for the first dropdown item
backgroundColor:
accessLevels[0] === "Manager"
? "#019385"
: accessLevels[0] === "Viewer"
? "#376EC5"
: "#DECB52",
}}
>
<Text style={styles.dropdownText}>{accessLevels[0]}</Text>
<Animated.View // animation for the dropdown icon
style={{
transform: [
{
rotateX: fadeAnim.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"],
}),
},
],
}}
>
<DropDownIcon />
</Animated.View>
</View>
</TouchableWithoutFeedback>
<View // bottom two dropdown items
style={{
justifyContent: "space-evenly",
alignItems: "center",
minHeight: normalize(46),
flex: 1,
}}
>
<TouchableWithoutFeedback // second dropdown item
onPress={() => {
handleAnimation();
handleSwap(accessLevels[1]);
}}
>
<View style={styles.dropdownBottomItems}>
<Text style={{ color: "white" }}>{accessLevels[1]}</Text>
</View>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback // third dropdown item
onPress={() => {
handleAnimation();
handleSwap(accessLevels[2]);
}}
>
<View style={styles.dropdownBottomItems}>
<Text style={{ color: "white" }}>{accessLevels[2]}</Text>
</View>
</TouchableWithoutFeedback>
</View>
</Animated.View>
);
};
const styles = StyleSheet.create({
dropdown: {
backgroundColor: "#4E585E",
width: normalize(97),
borderRadius: 4,
overflow: "hidden",
},
dropdownText: {
color: "white",
},
dropdownBottomItems: {
width: "100%",
justifyContent: "center",
alignItems: "center",
height: normalize(24),
},
});
Related
I have a horizontal ScrollView, all items have different widths
Is there a way to scroll one by one, so it would stop in the center of each item?
If the first answer to question is yes, then is there a way to know the index of item (that is centered), so I can change the index of selected dot?
https://snack.expo.dev/#drujik/horizontal-scroll-diff-widths
Example for: https://i.stack.imgur.com/thKLv.gif
I'm using FlatList and its props.
More information: https://reactnative.dev/docs/flatlist
import * as React from "react";
import { StyleSheet, Dimensions, FlatList, Text, View } from "react-native";
const data = [
{
text: "Page1",
width: 300,
},
{
text: "Page2",
width: 250,
},
{
text: "Page3",
width: 200,
},
];
const DOT_SIZE = 8;
const { width } = Dimensions.get("window");
export default function App() {
const [indexDot, setIndexDot] = React.useState(0);
const onChangeDot = (event) => {
setIndexDot(Math.ceil(event.nativeEvent.contentOffset.x / width));
};
const renderPagination = React.useMemo(() => {
return (
<View style={styles.wrapPagination}>
{data.map((_, index) => {
return (
<View
key={index}
style={[
styles.dot,
{
backgroundColor:
indexDot === index ? "#E7537A" : "rgba(0, 0, 0, 0.3)",
},
]}
/>
);
})}
</View>
);
}, [data?.length, indexDot]);
const renderItem = ({ item }) => (
<View style={styles.wrapItem}>
<View
style={{
...styles.item,
width: item.width,
}}
>
<Text>{item.text}</Text>
</View>
</View>
);
return (
<>
<FlatList
horizontal
pagingEnabled
disableIntervalMomentum
showsHorizontalScrollIndicator={false}
data={data}
renderItem={renderItem}
scrollEventThrottle={200}
onMomentumScrollEnd={onChangeDot}
/>
{renderPagination}
</>
);
}
const styles = StyleSheet.create({
wrapPagination: {
flexDirection: "row",
flex: 1,
justifyContent: "center",
alignItems: "center",
},
dot: {
height: DOT_SIZE,
width: DOT_SIZE,
borderRadius: DOT_SIZE / 2,
marginHorizontal: 3,
},
wrapItem: {
width,
padding: 20,
alignItems: "center",
justifyContent: "center",
},
item: {
alignItems: "center",
justifyContent: "center",
borderWidth: 1,
},
});
You can achieve this by react-native-snap-carousel and For dot, You can use pagination.
import Carousel, { Pagination } from 'react-native-snap-carousel';
const [active, setActive] = useState(0)
const [data, setData] = useState([])
<Carousel
keyExtractor={(_, index) => `id-${index}`}
data={data}
renderItem={renderItem}
onSnapToItem={setActive} />
<Pagination
dotsLength=length}
activeDotIndex={active}
dotStyle={{ backgroundColor: colors.blue }}
inactiveDotStyle={{ backgroundColor: colors.gray }}
inactiveDotScale={1}
inactiveDotOpacity={1} />
I'm trying to accomplish the vertical carousel as shown in the below gif. I'm struck by the second screen, where when the user scrolls the data from bottom to top or vice versa both the content and image change, how to achieve this? looking forward to your help?enter image description here
I have included a snack example which is mostly similar to what you want. You can use reanimated, Flatlist achieve the animation:
Snack Link
Code:
import * as React from 'react';
import { Text, View, StyleSheet, FlatList, Dimensions } from 'react-native';
import Constants from 'expo-constants';
import Animated, {
useSharedValue,
useAnimatedScrollHandler,
useAnimatedStyle,
interpolate,
Extrapolate,
} from 'react-native-reanimated';
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
const { width, height } = Dimensions.get('window');
const bottomHeight = height - 150 - 30 - Constants.statusBarHeight;
const data = [
{
title: 'data1',
image:
'https://assets.website-files.com/5f204aba8e0f187e7fb85a87/5f210a533185e7434d9efcab_hero%20img.jpg',
},
{
title: 'data2',
image:
'https://www.whoa.in/201604-Whoa/10-alone-broken-image-mobile-wallpaper-hd-image.jpg',
},
{
title: 'data3',
image:
'https://images.pexels.com/photos/674010/pexels-photo-674010.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500',
},
{
title: 'data4',
image:
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTntlma5HASL0HAM-KiC01A-JX4MxKousAA6A&usqp=CAU',
},
];
const ImageContent = ({ image, scrollValue, index }) => {
const animatedStyle = useAnimatedStyle(() => {
const inputRange = [index * bottomHeight, (index + 1) * bottomHeight];
const translateY = interpolate(
scrollValue.value,
inputRange,
[0, -150],
Extrapolate.CLAMP
);
return {
transform: [{ translateY }],
};
});
return (
<Animated.Image
source={{ uri: image }}
style={[styles.image, { zIndex: data.length - index }, animatedStyle]}
resizeMode="cover"
/>
);
};
const TopPart = React.memo(({ scrollValue }) => {
return (
<View style={styles.topPartContainer}>
{data.map(({ image }, index) => (
<ImageContent {...{ scrollValue, image, index }} />
))}
</View>
);
});
const Item = ({ item, index }) => {
return (
<View
style={[
styles.item,
]}>
<Text style={{ color: 'red' }}>{item.title}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
topPartContainer: {
width: 150,
height: 150,
borderRadius: 75,
alignSelf: 'center',
overflow: 'hidden',
},
image: {
...StyleSheet.absoluteFillObject,
backgroundColor: '#fff',
borderRadius: 75,
},
item: {
width,
backgroundColor: '#fff',
justifyContent: 'center',
alignItems: 'center',
height: bottomHeight,
},
});
function App() {
const scrollValue = useSharedValue(0);
const handler = useAnimatedScrollHandler((event) => {
scrollValue.value = event.contentOffset.y;
});
return (
<View style={styles.container}>
<TopPart {...{ scrollValue }} />
<View style={{ flex: 1, paddingTop: 30, height: bottomHeight }}>
<AnimatedFlatList
contentContainerStyle={{ height: data.length * bottomHeight }}
showsVerticalScrollIndicator={false}
onScroll={handler}
scrollEventThrottle={16}
data={data}
pagingEnabled
keyExtractor={(item) => item.title}
renderItem={({ item, index }) => <Item {...{ item, index }} />}
/>
</View>
</View>
);
}
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]});
So I am practicing the React Native Reanimated or the Animation in React Native with Expo. I'm trying to open a screen from bottom to top using Animated.
When I press the Text it will show another screen on top of my Parent Component with Animation from bottom to top.
But this is the output I get:
Output I want to get:
Here are my codes:
class App extends React.Component {
state = {
openHome: false
}
animation = new Value(0);
openHome = () => {
this.setState({
openHome: true
})
Animated.timing(this.animation, {
duration: 300,
toValue: 1
}).start();
}
render () {
const {animation} = this;
const translateY = animation.interpolate({
inputRange: [0, 1],
outputRange: [-height, 0],
easing: Easing.inOut(Easing.ease)
});
return (
<View style={styles.container}>
<Text onPress={this.openHome}>Open up Home to start working on your app!</Text>
{
<Animated.View style={{ transform: [{ translateY }] }}>
<Home open={this.state.openHome}/>
</Animated.View>
}
</View>
);
}
}
Code for my Child Component which is Home.js:
const Home = props => {
if (!props.open) {
return (
<View style={styles.firstContainer}>
</View>
);
} else {
return (
<View style={styles.secondContainer}>
<Text>Hello</Text>
</View>
);
}
}
const styles = StyleSheet.create({
firstContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'transparent',
width: '100%'
},
secondContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'red',
width: '100%',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0
}
})
You need new Animated.Value(0) instead of new Value(0) in the first place, if you want to animate a value.
I want to hide the Header and the TabNavigator tabs onScroll. How do I do that? I want to hide them onScroll and show them on ScrollUp. My code:
import React, { Component } from 'react';
import { View, Text, ScrollView, StyleSheet, TouchableOpacity} from 'react-native';
class ScrollTest extends Component {
render(){
const { params } = this.props.navigation.state;
return(
<View style={styles.container}>
<ScrollView>
<View style={{styles.newView}}><Text>Test</Text></View>
<View style={{styles.newView}}><Text>Test</Text></View>
<View style={{styles.newView}}><Text>Test</Text></View>
<View style={{styles.newView}}><Text>Test</Text></View>
<View style={{styles.newView}}><Text>Test</Text></View>
<View style={{styles.newView}}><Text>Test</Text></View>
<View style={{styles.newView}}><Text>Test</Text></View>
<View style={{styles.newView}}><Text>Test</Text></View>
</ScrollView>
</View>
)
}
}
const styles = StyleSheet.create({
container:{
flex:1, padding:5
},
newView:{
height: 200, backgroundColor:'green', margin:10
}
})
export default ScrollTest;
I checked this link for Animated API but not able to figureout how to implement it in onScoll?
So the header HomeScreen and the tabs Tab1 and Tab2 should hide on scroll and show when scrolled up. How do I do that?
Please help getting started on this.
Many thanks.
I was also stuck with the same animation thing, I tried this code for maximizing and minimizing the header using the Animated API of react-native.
You can also do the same for showing and hiding it.
import React, { Component } from 'react';
import { Text, View, StyleSheet, ScrollView, Image,Animated } from 'react-native';
const HEADER_MAX_HEIGHT = 200;// set the initial height
const HEADER_MIN_HEIGHT = 60;// set the height on scroll
const HEADER_SCROLL_DISTANCE = HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT;
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
scrollY: new Animated.Value(0),
};
}
render() {
const headerHeight = this.state.scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE],
outputRange: [HEADER_MAX_HEIGHT,HEADER_MIN_HEIGHT],
extrapolate: 'clamp',
});
return(
<View style={{flex: 1}}>
<ScrollView
scrollEventThrottle={1}
onScroll={Animated.event(
[{nativeEvent:
{contentOffset: {y: this.state.scrollY}}}]
)}>
<View style={styles.container}>
<Text style={styles.paragraph}>hello</Text>
<Image source={{uri: "https://static.pexels.com/photos/67843/splashing-splash-aqua-water-67843.jpeg"}} style={styles.imageStyle}/>
<Image source={{uri: "https://www.elastic.co/assets/bltada7771f270d08f6/enhanced-buzz-1492-1379411828-15.jpg" }}
style={styles.imageStyle}/>
</View>
</ScrollView>
<Animated.View style={[styles.footer, {height: headerHeight}]}>
<View style={styles.bar}>
<Text>text here</Text>
</View>
</Animated.View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 24,
backgroundColor: '#ecf0f1',
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
color: '#34495e',
},
imageStyle: {
height: 360,
width: '100%',
},
footer: {
position:'absolute',
top: 0,
left: 0,
right: 0,
backgroundColor: 'red',
},
bar: {
alignItems: 'center',
justifyContent: 'center',
},
});
Hope this helps someone.
I resolved for my case, hope this will be helpful
import React from 'react';
import {
Animated,
Text,
View,
StyleSheet,
ScrollView,
Dimensions,
RefreshControl,
} from 'react-native';
import Constants from 'expo-constants';
import randomColor from 'randomcolor';
const HEADER_HEIGHT = 44 + Constants.statusBarHeight;
const BOX_SIZE = Dimensions.get('window').width / 2 - 12;
const wait = (timeout: number) => {
return new Promise((resolve) => {
setTimeout(resolve, timeout);
});
};
function App() {
const [refreshing, setRefreshing] = React.useState(false);
const scrollAnim = new Animated.Value(0);
const minScroll = 100;
const clampedScrollY = scrollAnim.interpolate({
inputRange: [minScroll, minScroll + 1],
outputRange: [0, 1],
extrapolateLeft: 'clamp',
});
const minusScrollY = Animated.multiply(clampedScrollY, -1);
const translateY = Animated.diffClamp(minusScrollY, -HEADER_HEIGHT, 0);
const onRefresh = React.useCallback(() => {
setRefreshing(true);
wait(2000).then(() => {
setRefreshing(false);
});
}, []);
return (
<View style={styles.container}>
<Animated.ScrollView
contentContainerStyle={styles.gallery}
scrollEventThrottle={1}
bounces={true}
showsVerticalScrollIndicator={false}
style={{
zIndex: 0,
height: '100%',
elevation: -1,
}}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollAnim } } }],
{ useNativeDriver: true }
)}
overScrollMode="never"
contentInset={{ top: HEADER_HEIGHT }}
contentOffset={{ y: -HEADER_HEIGHT }}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}>
{Array.from({ length: 20 }, (_, i) => i).map((uri) => (
<View style={[styles.box, { backgroundColor: 'grey' }]} />
))}
</Animated.ScrollView>
<Animated.View style={[styles.header, { transform: [{ translateY }] }]}>
<Text style={styles.title}>Header</Text>
</Animated.View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
},
gallery: {
flexDirection: 'row',
flexWrap: 'wrap',
padding: 4,
},
box: {
height: BOX_SIZE,
width: BOX_SIZE,
margin: 4,
},
header: {
flex: 1,
height: HEADER_HEIGHT,
paddingTop: Constants.statusBarHeight,
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
top: 0,
left: 0,
right: 0,
backgroundColor: randomColor(),
},
title: {
fontSize: 16,
},
});
export default App;
checkout on Expo https://snack.expo.io/#raksa/auto-hiding-header