Issue with React Native animations - react-native

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]});

Related

React Native items are not layered properly with FlatList

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),
},
});

Unable to resolve "constants/categories.json" from "screens\GridFilter\GridFilter.js"

I'm new at react-native and trying to build an app. When I try to run my code I'm getting this error................................................................
Failed building JavaScript bundle. Unable to resolve
"constants/categories.json" from "screens\GridFilter\GridFilter.js"
GridFilter.js:
import React from 'react';
import { StyleSheet, Text, View, FlatList, Dimensions, TouchableOpacity } from 'react-native';
import categories from 'constants/categories.json';
import filterIcons from 'components/Icons/filterIcons';
import { isIphoneX } from 'utils';
import fonts from 'theme/fonts';
const formatData = (data, numColumns) => {
const numberOfFullRows = Math.floor(data.length / numColumns);
let numberOfElementsLastRow = data.length - numberOfFullRows * numColumns;
while (numberOfElementsLastRow !== numColumns && numberOfElementsLastRow !== 0) {
data.push({ key: `blank-${numberOfElementsLastRow}`, empty: true });
numberOfElementsLastRow++;
}
return data;
};
const numColumns = 3;
const WIDTH = Dimensions.get('window').width;
function GridFilter({ navigation: { navigate } }) {
const renderItem = ({ item }) => {
if (item.empty === true) {
return <View style={[styles.item, styles.itemInvisible]} />;
}
const Icon = filterIcons[item.icon];
return (
<TouchableOpacity style={styles.item} onPress={() => navigate('SearchTopBarStack')}>
<View style={styles.iconWrapper}>
<Icon width={50} height={50} color="#9f9f9f" />
</View>
<Text style={styles.itemText}>{item.name}</Text>
</TouchableOpacity>
);
};
return (
<View style={styles.container}>
<FlatList
data={formatData(categories, numColumns)}
style={styles.wrapper}
renderItem={renderItem}
numColumns={numColumns}
contentContainerStyle={{ paddingBottom: 150 }}
/>
</View>
);
}
export default GridFilter;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
paddingTop: isIphoneX ? 40 : 20,
},
wrapper: {
flex: 1,
},
iconWrapper: {
margin: 10,
},
item: {
alignItems: 'center',
justifyContent: 'center',
flex: 1,
height: WIDTH / numColumns, // approximate a square
padding: 20,
borderColor: 'gray',
borderWidth: 1,
borderRadius: 5,
backgroundColor: '#f3f3f3',
margin: 5,
},
itemInvisible: {
backgroundColor: 'transparent',
},
itemText: {
color: 'black',
textAlign: 'center',
fontFamily: fonts.REGULAR,
},
});
I tried to delete node modules and rebuild. What should I do?
You should refer to your current directory or file in your import. Try to use './' or '../' when you import path. If you use vscode it will give you auto completions.
"../../" is worked for me.

A problem with react hooks with animation callback

import React, {useEffect, useState} from 'react';
import {Col} from "native-base";
import {Animated, TouchableOpacity, ViewProps} from "react-native";
interface AnimatedButtonProps extends ViewProps {
text: string;
size: Animated.Value;
onPress: (cb?: () => void) => void;
}
export const AnimatedButton = ({text, size, onPress}: AnimatedButtonProps) => {
const [defaultSize, setDefaultSize] = useState(new Animated.Value(30));
useEffect(() => {
// Update defaultSize from props
setDefaultSize(size);
});
let _onPress = () => {
console.log(defaultSize);
Animated.timing(defaultSize, {
toValue: 50,
duration: 300,
}).start();
console.log(defaultSize);
};
return (
<Col style={{justifyContent: "center", alignItems: "center"}}>
<TouchableOpacity style={{
width: 60,
height: 60,
justifyContent: "center",
alignItems: "center",
}}
onPress={() => onPress(_onPress)}>
<Animated.Text style={{
fontSize: defaultSize,
fontWeight: "bold"
}}>{text}</Animated.Text>
</TouchableOpacity>
</Col>
)
};
I am new to react hooks, tried to rewrite one of my components with react hooks. Any one can tell me why the callback animation doesn't work? The call back did trigger, but the defaultSize doesn't change at all. Below is my original component wrote in "React Class" way which works as expected. Some help will be really appreciated :D
class AnimatedButton extends Component<AnimatedButtonProps, AnimatedButtonState> {
state: AnimatedButtonState = initState;
componentDidMount(): void {
const {size} = this.props;
this.setState({
size
})
}
_onPress = () => {
const {size} = this.state;
Animated.sequence([
Animated.timing(size, {
toValue: 50,
duration: 300,
}),
Animated.timing(size, {
toValue: 30,
duration: 300,
})
]).start();
};
render() {
const {text} = this.props;
const {size} = this.state;
return (
<Col style={{justifyContent: "center", alignItems: "center"}}>
<TouchableOpacity style={{
width: 60,
height: 60,
justifyContent: "center",
alignItems: "center",
}}
onPress={() => this.props.onPress(this._onPress)}>
<Animated.Text style={{
fontSize: size,
fontWeight: "bold"
}}>{text}</Animated.Text>
</TouchableOpacity>
</Col>
);
}
}
export default AnimatedButton;
useEffect(() => {
setDefaultSize(size);
}, [defaultSize]);
Solved the problem

ReactNative How to keep the scroll view position when the keyboard opened

I am using React Native's ScrollView and FlatList.
When the keyboard is opened,
I would like to see the same screen as before opening the keyboard.
I thought I could use the scrollTo method depending on the keyboard state
It does not work properly.
Is there a typical implementation of a similar case?
keyboardWillShow(e) {
const { scrollView } = this;
const { scrollPostiion } = this.state;
const { height } = e.endCoordinates;
this.setState({
keyboardHeight: height,
scrollPostiion: scrollPostiion + height,
});
scrollView.scrollTo({ x: 0, y: scrollPostiion + height, animated: false });
}
keyboardWillHide() {
const { scrollView } = this;
const { scrollPostiion, keyboardHeight } = this.state;
this.setState({
keyboardHeight: 0,
scrollPostiion: scrollPostiion - keyboardHeight,
});
scrollView.scrollTo({ x: 0, y: scrollPostiion - keyboardHeight, animated: false });
}
changeNowScrollPosition = (event) => {
this.setState({
scrollPostiion: event.nativeEvent.contentOffset.y,
});
}
<ScrollView
ref={(c) => { this.scrollView = c; }}
keyboardShouldPersistTaps="handled"
pinchGestureEnabled={false}
keyboardDismissMode="interactive"
onScroll={(event) => {
changeNowScrollPosition(event);
}}
onScrollEndDrag={(event) => {
changeNowScrollPosition(event);
}}
scrollEventThrottle={16}
>
use KeyboardAvoidingView :
Following is a simple example:
import React, { Component } from 'react';
import { Text, Button, StatusBar, TextInput, KeyboardAvoidingView, View, StyleSheet } from 'react-native';
import { Constants } from 'expo';
export default class App extends Component {
state = {
email: '',
};
render() {
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" />
<KeyboardAvoidingView behavior="padding" style={styles.form}>
<TextInput
style={styles.input}
value={this.state.email}
onChangeText={email => this.setState({email})}
ref={ref => {this._emailInput = ref}}
placeholder="email#example.com"
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
returnKeyType="send"
onSubmitEditing={this._submit}
blurOnSubmit={true}
/>
<View>
<Button title="Sign Up" onPress={this._submit} />
<Text style={styles.legal}>
Some important legal fine print here
</Text>
</View>
</KeyboardAvoidingView>
</View>
);
}
_submit = () => {
alert(`Confirmation email has been sent to ${this.state.email}`);
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: 'white',
},
input: {
margin: 20,
marginBottom: 0,
height: 34,
paddingHorizontal: 10,
borderRadius: 4,
borderColor: '#000000',
borderWidth: 1,
fontSize: 16,
},
legal: {
margin: 10,
color: '#333',
fontSize: 12,
textAlign: 'center',
},
form: {
flex: 1,
justifyContent: 'space-between',
},
});
Please note: styling is important.
You're good to go!

Hide TabNavigators and Header on Scroll

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