React Native - Animation works in iOS and Android Emulator, but not in Android Device - react-native

This is an extremely unusual problem...
I have a View that acts like a flashcard. I have used animations in order to flip over the flashcard.
After I click the flashcard, the system completely breaks down. It works fine ONCE, but then it doesn't even detect the clicks.
import React, {Component} from 'react';
import { View, Text, StyleSheet, Animated, TouchableWithoutFeedback, Easing } from 'react-native';
import Swiper from 'react-native-swiper';
import AppText from "../components/AppText";
type Props = {};
export default class FlashcardScreen extends Component<Props> {
static navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('title', 'Flashcards')
}
};
constructor(props){
super(props);
this.animatedValue = new Animated.Value(0);
this.value = 0;
this.textAnimatedValue = new Animated.Value(0);
this.animatedValue.addListener(({value}) => {
this.value = value;
if (value === 90){
Animated.timing(this.textAnimatedValue, {
toValue: 0,
duration: 250,
}).start();
}
});
}
state = {
cards: this.props.navigation.state.params.cards,
displayTexts: Object.keys(this.props.navigation.state.params.cards),
index: 0,
};
flipAnimation = (index) => {
alert("Clicked!");
let { displayTexts, cards } = this.state;
const tempDisplayTexts = [...displayTexts];
const toValue = this.value >= 90 ? 0 : 180;
const entry = Object.entries(cards)[index];
Animated.parallel([
Animated.timing(this.animatedValue, {
toValue: 90,
duration: 250,
easing: Easing.linear
}),
Animated.timing(this.textAnimatedValue, {
toValue: 90,
duration: 250
})
]).start(() => {
if(displayTexts[index] === entry[0]){
tempDisplayTexts[index] = entry[1];
} else {
tempDisplayTexts[index] = entry[0];
}
this.setState({ displayTexts: tempDisplayTexts });
Animated.parallel([
Animated.timing(this.animatedValue, {
toValue: toValue,
duration: 250,
easing: Easing.linear
}),
]).start();
});
};
render() {
let { cards, displayTexts } = this.state;
this.SetInterpolate = this.animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg'],
});
const Rotate_X_AnimatedStyle = {
transform: [{ rotateX: this.SetInterpolate }],
};
this.textSetInterpolate = this.textAnimatedValue.interpolate({
inputRange: [0, 90],
outputRange: ['0deg', '90deg'],
});
const Test_Rotate_X_AnimatedStyle = {
transform: [{ rotateX: this.textSetInterpolate }]
};
return (
<View style={styles.container}>
<Swiper showsPagination={false} loop={false} onIndexChanged={(index) => this.setState({ index })}>
{
Object.entries(cards).map((question, index) => {
return (
<View style={styles.main}>
<TouchableWithoutFeedback onPress={() => this.flipAnimation(index)}>
<Animated.View style={[Rotate_X_AnimatedStyle, styles.card]} />
</TouchableWithoutFeedback>
<Animated.Text
onPress={() => this.flipAnimation(index)}
style={[styles.text, Test_Rotate_X_AnimatedStyle]}>
{displayTexts[index]}
</Animated.Text>
</View>
)
})
}
</Swiper>
<AppText style={styles.position} text={(this.state.index + 1) + " / " + this.state.displayTexts.length} />
</View>
);
}
}
I tried to change the code so that each card has its own animatedValue and textValue, but it did not fix the problem.
Here is a link to my recordings. The first one is the emulator, while the second one is the device:
https://imgur.com/gallery/xKYs3bc
Thanks in advance for any help! I've spent hours on this :/

Related

Restart React Native Animation When Component's Props Change

I have a React Native component rendering a notification text. I want the text to fade in and fade out after a small delay. When a new notification text is set, I want to restart the same animation.
I have the following code:
export default function Notification({ notification }) {
if (!notification) {
return null;
}
const [fadeAnimation] = useState(new Animated.Value(0));
useEffect(() => {
Animated.sequence([
Animated.timing(fadeAnimation, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
Animated.delay(3000),
Animated.timing(fadeAnimation, {
toValue: 0,
duration: 500,
useNativeDriver: true,
})]).start()
}, []);
return (
<Animated.View style={{ opacity: fadeAnimation, }}>
<View style={styles.notificaton}>
<Text style={styles.text}>{notification}</Text>
</View>
</Animated.View >
)
}
I read that I should be able to reset the animation with setValue(0) again, however I do not know where and when to call it.
work for me
import React, { Component } from 'react';
import { Animated, View } from 'react-native';
class Testing extends Component {
constructor(props) {
super(props);
this.animatedValue = new Animated.Value(0);
}
componentDidUpdate(prevProps) {
if (prevProps.myProp !== this.props.myProp) {
this.startAnimation();
}
}
startAnimation() {
this.animatedValue.setValue(0);
Animated.timing(this.animatedValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
}
render() {
const { myProp } = this.props;
const opacity = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
});
return (
<View>
<Animated.Text style={{ opacity }}>
My prop val : {myProp}
</Animated.Text>
</View>
);
}
}
export default Testing;

React Native Oval Scroll

How can I do a similar oval scroll?
What can I use for this?
Based on the assumption that you want something like this, I wrote a simple example
If someday the link turns out to be broken, below I attach the code additionally
import React, { useCallback, useState, useRef } from "react";
import {
FlatList,
Text,
View,
StyleSheet,
Dimensions,
Animated
} from "react-native";
const { height } = Dimensions.get("window");
const screenMiddle = height / 2;
const itemScaleOffset = height / 3;
const DATA = new Array(20).fill(0).map((...args) => ({
id: args[1],
title: args[1]
}));
// args[1] is an index, just I hate warnings
const Item = ({ title, offsetY }) => {
const [scrollEdges, setScrollEdges] = useState({
top: 0,
middle: 0,
bottom: 0
});
const onLayout = useCallback(
({
nativeEvent: {
layout: { top, height }
}
}) =>
setScrollEdges((edges) => ({
...edges,
top: top - itemScaleOffset - screenMiddle,
middle: top + height / 2 - screenMiddle,
bottom: top + height + itemScaleOffset - screenMiddle
})),
[]
);
const scale = offsetY.interpolate({
inputRange: [scrollEdges.top, scrollEdges.middle, scrollEdges.bottom],
outputRange: [0.66, 1, 0.66],
extrapolate: "clamp"
});
return (
<Animated.View
onLayout={onLayout}
style={[
{
transform: [
{
scale
}
]
},
styles.item
]}
>
<Text style={styles.title}>{title}</Text>
</Animated.View>
);
};
const keyExtractor = ({ id }) => id.toString();
const App = () => {
const offsetY = useRef(new Animated.Value(0)).current;
const renderItem = useCallback(
({ item: { title } }) => <Item title={title} offsetY={offsetY} />,
[offsetY]
);
return (
<View style={styles.app}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={keyExtractor}
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: offsetY
}
}
}
],
{
useNativeDriver: false
}
)}
/>
</View>
);
};
const styles = StyleSheet.create({
app: {
flex: 1
},
item: {
backgroundColor: "#f9c2ff",
padding: 20,
marginVertical: 8,
marginHorizontal: 16
},
title: {
fontSize: 32
}
});
export default App;
I think you should use Reanimated 2 who has a very easy sintaxis and also very powerful. Maybe in combination with RNGestureHandler.

React Native Animated.timing() multiple animations

I have an animation that uses Animated.timing() which slides a component in to view when the condition mapIsCentered = true. When the condition isn't met the component just disappears ungracefully. I'd like for it to slide in and out as the condition changes.
One thing to note the mapIsCentered state is updated on a different screen and passed as a prop to the component I am working in. I have logged the state and it updates when the map is moved.
** the slide in works as expected
Thanks to #Ashwith for the first answer
const values = useRef(new Animated.ValueXY({ x: 0, y: 120 })).current;
useEffect(() => {
Animated.timing(values, {
toValue: mapIsCentered ? { x: 0, y: 0 } : { x: 0, y: 120 },
duration: 500,
useNativeDriver: false,
}).start();
}, [mapIsCentered]);
{!walkInProgress && !hasOnGoingWalks && (
<Animated.View
style={{
transform: [{ translateY: values.y }],
}}
>
<WeatherToast
translations={translations}
loading={loading}
weather={weather}
/>
</Animated.View>
Thanks in advance!
I have changed the structure hope it works for you...
snack: https://snack.expo.io/#ashwith00/excited-orange
App.js
const walkInProgress = false , hasOnGoingWalks = false;
export default function App() {
const { width } = useWindowDimensions();
const [mapCentered, setMapCentered] = React.useState(false)
const toggle = () => {
setMapCentered((ct) => !ct);
};
return (
<View style={styles.container}>
<WeatherToast mapCentered={mapCentered && !walkInProgress && !hasOnGoingWalks} />
<Button title="shift" onPress={toggle} />
</View>
);
}
WeatherTost.js
export default ({ mapCentered }) => {
const [visible, setVisible] = useState(mapCentered);
const { width } = useWindowDimensions();
const values = React.useRef(new Animated.ValueXY({ x: 0, y: 120 })).current;
React.useEffect(() => {
if (mapCentered) {
setVisible(true);
Animated.timing(values, {
toValue: { x: 0, y: 0 },
duration: 300,
}).start();
} else {
Animated.timing(values, {
toValue: { x: width, y: 0 },
duration: 300,
}).start(({ finished }) => {
if (finished) {
setVisible(false);
}
});
}
}, [mapCentered]);
const styles = [];
return visible ? (
<Animated.View
style={{
width: 200,
height: 200,
position: 'absolute',
backgroundColor: 'red',
transform: [
{
translateX: values.x,
},
{
translateY: values.y,
},
],
}}
/>
) : (
<View />
);
};

Slide up down animation in the react native

I am using animation for up and down in the react native, But the animation just slide from up to down and then stop at the bottom i want to move it up and down continuously. I have also used animation loop so please check and provide me solution for this
import React, { useEffect, useState } from 'react'
import { Text, View, Animated, Easing, StyleSheet } from 'react-native'
import LoaderLogo from '../../icons/commonicons/LoaderLogo'
import { Loadericon } from '../../constants/Image';
import LinearGradient from 'react-native-linear-gradient';
import { dynamicSize } from '../sizechoose';
const amimationScreen = () => {
const startValue = new Animated.Value(0);
const endValue = dynamicSize(225);
const startValue2 = new Animated.Value(225);
const endValue2 = dynamicSize(0);
const duration = 5000;
useEffect(() => {
Animated.sequence([
Animated.timing(startValue, {
toValue: endValue,
duration: duration,
useNativeDriver: true,
}),
Animated.timing(startValue2, {
toValue: endValue2,
duration: duration,
useNativeDriver: true,
})
]).start()
}, [startValue, endValue, duration]);
return (
<Animated.View style={[{ transform: [{ translateY: startValue }] }]}>
<View style={{backgroundColor:'red',height:10,width:100}}>
</View>
</Animated.View>
)
}
export default amimationScreen
I also tried with react-native-animatable package but it is not good to use for me as it starts animation from the top of the screen.
This worked for me:
const App = () => {
const animated = new Animated.Value(0);
const duration = 5000;
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(animated, {
toValue: 255,
duration: duration,
useNativeDriver: true,
}),
Animated.timing(animated, {
toValue: 0,
duration: duration,
useNativeDriver: true,
}),
]),
).start();
}, []);
return (
<Animated.View style={[{transform: [{translateY: animated}]}]}>
<View style={{backgroundColor: 'red', height: 10, width: 100}}></View>
</Animated.View>
);
};
So instead of having two instances of Animated.Value for translation, create one and let it transition from 0 to 255 and from 255 back to 0 in sequence. And make it loop once the sequence has finished.
I think the main problem in your original approach is that startValue decides how the view translates since this is what you pass as the value of translateY. The downward animation therefore happens correctly in your example. The upward animation however does not happen, because startValue2 is passed to Animated.timing and startValue is not used in the translation of any views in your example.
import React, { useEffect, useRef, useState } from 'react';
import { Animated, Dimensions, Easing, StyleSheet, View } from 'react-native';
export const App = () => {
const animatedValue = useRef(new Animated.Value(0)).current;
const [isTop, setIsTop] = useState(true);
const startAnimation = toValue => {
Animated.timing(animatedValue, {
toValue,
duration: 1000,
easing: Easing.linear,
useNativeDriver: true
}).start(() => {
setIsTop(!isTop);
})
}
useEffect(() => {
startAnimation(isTop ? 1 : 0);
}, [isTop]);
const translateY = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, Dimensions.get('window').height - 70],
extrapolate: 'clamp'
})
return (
<View style={styles.container}>
<Animated.View style={[styles.square, { transform: [{ translateY }] }]}>
</Animated.View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center'
},
square: {
width: 70,
height: 70,
backgroundColor: 'red'
}
});

How to change Text in Animation in react-native?

I use Animated.Text for change Animation Text but it's not working properly
I also require in animation fade out old text & fade in the new text.
import React, { Component, PropTypes } from 'react';
import {
StyleSheet,
View,
Text,
Image,
Dimensions,
Animated
} from 'react-native';
import styles from './styles';
const win = Dimensions.get('window');
export default class Logo extends Component {
constructor(props) {
super(props);
this.tempText = new Animated.Value("Hello");
}
componentWillMount () {
Animated.timing(this.tempText, {
duration: 5000,
toValue: "New Text",
}).start();
};
render() {
return (
<View style={{flex:1}}>
<Animated.Text style={{color: "#9b9b9b"}}>
{this.tempText}
</Animated.Text>
</View>
);
}
}
Actual output Get - Change text after 5 Second but it's not working.please help me.
What you're trying to achieve can be done without using Animated at all, and actually, Animated isn't intended for this particular use.
Replacing the text can be done with a simple variable, and the text change can be triggered by a setTimeout.
Animated is intended for changing a numeric value, not a text value. Think of it this way - if the change is supposed to happen over a 5 second interval, what would the mid-value be?
Do this instead:
export default class Logo extends Component {
constructor(props) {
super(props);
this.state = {text: "Hello"};
}
componentDidMount () {
setTimeout(() => this.setState({text: "New Text"}), 5000);
};
render() {
return (
<View style={{flex:1}}>
<Animated.Text style={{color: "#9b9b9b"}}>
{this.state.text}
</Animated.Text>
</View>
);
}
}
My example with smoothly opacity animation.
Sorry, without fadeIn, fadeOut.
const inputRange = [0, 1, 2, 3];
const AnimatedText = Animated.createAnimatedComponent(Text);
const animationProps = {
duration: 500,
easing: Easing.out(Easing.linear),
isInteraction: false,
useNativeDriver: true,
};
class Onboarding extends PureComponent {
activeDot = 0;
animationDot = new Animated.Value(0);
toggleOnButton = () => {
Animated.timing(this.animationDot, {
toValue: this.activeDot + 1,
...animationProps,
}).start((endState) => {
if (endState.finished) {
this.activeDot = this.activeDot + 1;
}
});
}
renderButton = () => {
const opacityNext = this.animationDot.interpolate({
inputRange,
outputRange: [1, 1, 1, 0]
});
const opacityGetStarted = this.animationDot.interpolate({
inputRange,
outputRange: [0, 0, 0, 1]
});
return (
<TouchableOpacity style={styles.button} onPress={this.toggleOnButton}>
<AnimatedText style={[styles.buttonText, { opacity: opacityNext }]}>
Next
</AnimatedText>
<AnimatedText style={[styles.buttonText, {
top: normalize(isIOS ? 12 : 8), position: 'absolute', opacity: opacityGetStarted
}]}
>
Get started
</AnimatedText>
</TouchableOpacity>
);
}
}