Reset Animation for Animted React Native - react-native

I've stumbled upon a problem when trying to use resetAnimation() in React Native.
I'm trying to build an animated.view that resets when a new view is displayed on a touchablewithoutfeedback component.
I cannot figure out how to reset the animation so that when I press my touchablewithoutfeedback the animation resets and starts again for the new that is displayed. It runs on the first render but then it stops and just displays the text normally.
Here are some snippets of my code.
import React, { useState, useEffect } from 'react';
import { Animated, StyleSheet } from 'react-native';
const FadeView = (props) => {
const [fadeAnim] = useState(new Animated.Value(0)); // Initial value for opacity: 0
React.useEffect(() => {
Animated.timing(
fadeAnim,
{
toValue: 1,
duration: 1000,
}
).start(fadeAnim.resetAnimation())
}, []);
return (
<Animated.View // Special animatable View
style={{
...props.style,
opacity: fadeAnim, // Bind opacity to animated value
}}
>
{props.children}
</Animated.View>
);
}
const styles = StyleSheet.create({
view:{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
// You can then use your `FadeInView` in place of a `View` in your components:
export default FadeView;
And where i try to use it.
<FadeView>
<Text style = {styles.gameText}> {question} </Text>
</FadeView>

I managed to solve it by removing the
, []
at the end of ).start(fadeAnim.resetAnimation())
}, []);
and adding
fadeAnim.resetAnimation();
after that codeblock.

Related

Problem with lining up contents: react native

I'm currently having a problem with the clickable size of a reusable button which includes an icon and text. When I run this code it seems like the entire row becomes clickable when I only want the icon and text to become clickable. Does anyone know how to solve this problem? Thanks
App.js
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import IconTextButton from './components/iconTextButton';
export default function App() {
return (
<View style={styles.container}>
<Text style={{marginTop: 100}}>My First React App! Sike </Text>
<IconTextButton iconFont="ionicons" iconName="pencil" iconSize={25} text="Add Items"/>
</View>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'powderblue',
},
});
iconTextButton.js
import React from 'react';
import { StyleSheet, TouchableOpacity, Text, View } from 'react-native';
import Ionicon from 'react-native-vector-icons/Ionicons';
export default function IconTextButton({ iconFont, iconName, iconSize, text, onPress }) {
const getIconFont = (iconFont) => {
switch (iconFont) {
case "ionicons":
return Ionicon;
}
};
const FontIcon = getIconFont(iconFont);
return (
<TouchableOpacity onPress={onPress} style={styles(iconSize).container>
<FontIcon name={iconName} size={iconSize} style={styles(iconSize).buttonIcon}>
<Text style={styles(iconSize).buttonText}>{text}</Text>
</FontIcon>
</TouchableOpacity>
)
}
const styles = (size) => StyleSheet.create({
container: {
backgroundColor: 'pink',
},
buttonIcon: {
backgroundColor: 'yellow',
width: size,
},
buttonText: {
backgroundColor: 'green'
},
})
Along with the code I've tried, I've also tried to keep and as seperate contents whilst adding a flexDirection: 'row' inside styles.container. This keeps the contents in the same line but it still makes the whole row clickable. I've also tried putting everything in a and moving the styles.container to the component and adding a height: size into styles.container. This makes the clickable component limited however, the component is hidden underneath due to the restricted height. I have also tried simply just using instead of making a reusable const that its an input. The same thing applies.
You can wrap your Icon and Text Component in a View component and then wrap it inside a TouchableOpacity Component
Try this or do something like this :
import React from 'react';
import { StyleSheet, TouchableOpacity, Text, View } from 'react-native';
import Ionicon from 'react-native-vector-icons/Ionicons';
export default function IconTextButton({ iconFont, iconName, iconSize, text, onPress }) {
const getIconFont = (iconFont) => {
switch (iconFont) {
case "ionicons":
return Ionicon;
}
};
const FontIcon = getIconFont(iconFont);
return (
<TouchableOpacity onPress={onPress} style={styles(iconSize).container}>
<View style={styles(iconSize).iconTextContainer}>
<FontIcon name={iconName} size={iconSize} style={styles(iconSize).buttonIcon} />
<Text style={styles(iconSize).buttonText}>{text}</Text>
</View>
</TouchableOpacity>
)
}
const styles = (size) => StyleSheet.create({
container: {
backgroundColor: 'pink',
},
iconTextContainer: {
flexDirection: 'row',
alignItems: 'center',
},
buttonIcon: {
backgroundColor: 'yellow',
width: size,
},
buttonText: {
backgroundColor: 'green'
},
})

Why does my image not show when wrapped in an Animated.View?

I am new to programming and my task is to animate the height of my image on scroll. i.e. decrease height on scroll down and increase height to original when scrolling up. However, following the React Native documentation on Animated, I replaced Text component with my Image and I'm unable to get the image showing. It shows when I don't wrap it in <Animated.View>, can anyone explain why and tell me how to fix? Thank you.
This is my current code just trying to do the sample fade animation before I try to animate height and running into issues getting the TopImage component showing:
import React, { useRef } from 'react';
import {
Animated,
View,
ScrollView,
StyleSheet,
} from 'react-native';
import { useHeaderHeight } from '#react-navigation/stack';
import TopImage from '../components/TopImage';
import GroceryList from '../components/GroceryList';
const App = () => {
const { theme } = useContext(ThemeContext);
const styles = createStyleSheet();
const fadeAnim = useRef(new Animated.Value(0)).current;
const fadeIn = () => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 5000,
useNativeDriver: true,
}).start();
};
const fadeOut = () => {
Animated.timing(fadeAnim, {
toValue: 0,
duration: 3000,
useNativeDriver: true,
}).start();
};
return (
<View style={styles.container}>
<Animated.View
style={
{
// Bind opacity to animated value
opacity: fadeAnim,
},
}>
<TopImage />
</Animated.View>
<ScrollView
style={styles.scrollContainer}
onScrollBeginDrag={() => fadeIn()}>
<GroceryList />
</ScrollView>
</View>
);
};
const createStyleSheet = () => {
const headerHeight = useHeaderHeight();
return StyleSheet.create({
container: { flex: 1 },
scrollContainer: { paddingTop: headerHeight + 100 },
});
};
export default App;
and this is the code for my TopImage component:
import React from 'react';
import { Image, StyleSheet } from 'react-native';
const topImage = require('../images/top.png');
const TopImage = () => {
const styles = createStyleSheet();
return <Image source={topImage} style={styles.image} />;
};
const createStyleSheet = () => {
return StyleSheet.create({
image: {
position: 'absolute',
width: '100%',
height: '50%',
},
});
};
export default TopImage;
The reason it's not showing is because your image is 'absolute' positioned and as such, the outer container (Animated.View) has no height.
If you apply a height to the Animated.View like below. You'll see that the opacity is actually working, you just couldn't see it before because the Animated.View was 0 pixels tall.
<Animated.View
style={
{
// Bind opacity to animated value
opacity: fadeAnim,
height: 300,
},
}>
<TopImage />
</Animated.View>
Alternatively you could make the image position relative, height 0 and animate it to the correct size.

Try to make my SVG element rotating using 'Animated' but it doesn't work

In my react-native project, I am trying to have my spinner rotating. My spinner is a SVG component <Spinner />. It is like this:
import * as React from 'react';
import Svg, {Path} from 'react-native-svg';
function Spinner(props) {
return (
<Svg width={24} height={24} fill="none" {...props}>
<Path
...
/>
</Svg>
);
}
export default Spinner;
Since it is a static SVG element, my plan is to create a component that can have rotating animation & apply it to any screens which needs a loading spinner. So I firstly created a LoadingSpinner component which is supposed to have the rotation animation with the <Spinner />:
import React, {Component, useState, useEffect} from 'react';
import {View, Animated, Easing} from 'react-native';
import Spinner from './Spinner';
const LoadingSpinner = () => {
const [spinAnim, setSpinAnim] = useState(new Animated.Value(0));
useEffect(() => {
Animated.loop(
Animated.timing(spinAnim, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true,
}),
).start();
});
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Animated.View>
<Spinner />
</Animated.View>
</View>
);
};
export default LoadingSpinner;
Then, in my screen that needs a rotating spinner I just render the LoadingSpinner,
import LoadingSpinner from '../component/LoadingSpinner'
...
const MyScreen = () => {
...
return (
<View>
{status==='loading' ? <LoadingSpinner /> : null}
</View>
)
}
When I run my app, I can see the spinner is rendered on the screen but it is not rotating. What am I missing?
You are correctly calculating the value of spinAnim (i.e. it gradually changes from 0 to 1), but you are not using that value anywhere.
You need to connect that calculated value to some style to see the results.
In your case, since you're working with rotation, you want the values to go from 0deg to 360deg instead of from 0 to 1. That can be accomplished using the interpolate function.
Once you have the rotation value in degrees, you can construct a style object, which will apply that value as transform.rotate (see animatedStyle in code below).
Finally, you assign that style object to the <Animated.View> to connect it all:
import React, {Component, useState, useEffect} from 'react';
import {View, Animated, Easing} from 'react-native';
import Spinner from './Spinner';
const LoadingSpinner = () => {
const [spinAnim, setSpinAnim] = useState(new Animated.Value(0));
const interpolateRotation = spinAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
});
const animatedStyle = {
transform: [
{ rotate: interpolateRotation }
]
}
useEffect(() => {
Animated.loop(
Animated.timing(spinAnim, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
})
).start();
});
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Animated.View style={animatedStyle}>
<Spinner />
</Animated.View>
</View>
);
};
export default LoadingSpinner;
If the object you want to rotate is not centered in the <Animated.View>, you can change the center of rotation by moving the object, rotating it and then moving it back, e.g.:
const animatedStyle = {
transform: [
{ translateX: -50},
{ translateY: -50},
{ rotate: interpolateRotation },
{ translateX: 50},
{ translateY: 50},
],
}
A quick demo of the difference that it makes: rotated around center vs rotated around a different point.

React Native - Using objects from Custom Hooks

I'm trying to get screen dimensions from a custom hook but I don't know how to use the results from the imported component.
Obviously I could use {MyDims.Width} and {MyDims.Height} to display the results but for my final script I need to use the 2 objects as strings, hence the useState().
Here is the imported component: getdimensions.js
import React, {
useState,
useEffect
} from "react";
import {
Dimensions
} from "react-native";
function App(props) {
const [Width, setWidth] = useState(0)
const [Height, setHeight] = useState(0)
const getDims = () => {
setWidth(Dimensions.get("screen").width)
setHeight(Dimensions.get("screen").height)
}
useEffect(() => {
getDims()
}, []);
return {Width, Height}
}
export default App;
And the main screen: App.js
import React, {
useState,
useEffect,
} from "react";
import { Text, View, StyleSheet } from 'react-native';
import useDimensions from './components/getdimensions';
export default function App() {
const MyDims = useDimensions()
const [ShowMyDims, setShowMyDims] = useState({
width: MyDims.Width,
height: MyDims.Height
})
return (
<View style={styles.container}>
<Text style={styles.paragraph}>
width: {ShowMyDims.width} and
height: {ShowMyDims.height}
</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: 50,
backgroundColor: '#ecf0f1',
padding: 8,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
You can return it as an array to avoid it being object. To get the updates from your custom hook to your component useState, add useEffect and trigger state updates upon change in Width, Height from your custom hook.
// In your hook component
...
return [Width, Height];
// In main component
...
const [Width, Height] = useDimensions();
const [ShowMyDims, setShowMyDims] = useState({ width: Width, height: Height });
// Add a useEffect hook to listen the updates
useEffect(() => {
setShowMyDims({ width: Width, height: Height });
}, [Width, Height])
....
You have an option of directly using Width, Height from your custom hook into your component without having an intermediate useState.

Reanimated TapGestureHandler callback with block

What would be the way to achieve this.
callback with Animated.event works fine
callback with block gives no feedback :(
import React, { useState, useEffect } from "react";
import { View, Text, FlatList, Dimensions, Image } from "react-native";
import Animated from "react-native-reanimated";
import { PanGestureHandler, TapGestureHandler, State } from "react-native-gesture-handler";
const { Value, event, call, set, add, block } = Animated;
const bottom = new Value(100);
//want to achieve something like this
const onHandlerStateChange = block([set(bottom, add(bottom, 10))]);
// the line below Does work
// const onHandlerStateChange = event([{ nativeEvent: { y: bottom } }]);
const Comp = () => {
return (
<TapGestureHandler onHandlerStateChange={onHandlerStateChange}>
<Animated.View style={{ flex: 1}}>
<Animated.View style={{ flex: 1, position: "absolute", bottom: bottom }}>
<Text> some text</Text>
</Animated.View>
</Animated.View>
</TapGestureHandler>
);
};
export default Comp;
*const onHandlerStateChange = block([set(bottom, add(bottom, 10))]); * Doesn't work here
Here's the solution that works
Tip :
Understanding how reanimated evaluates nodes will be very useful
Here's the link to offical doc that might help you understand
https://software-mansion.github.io/react-native-reanimated/about-reanimated.html#at-most-once-evaluation-the-algorithm-
import React, { useState, useEffect } from "react";
import { View, Text, FlatList, Dimensions, Image } from "react-native";
import Animated from "react-native-reanimated";
import {
PanGestureHandler,
TapGestureHandler,
State,
} from "react-native-gesture-handler";
const { Value, event, call, set, add, cond, eq, block } = Animated;
const gstate = new Value(-1);
const onHandlerStateChange = event([{ nativeEvent: { state: gstate } }]);
let bottom = new Value(100);
bottom = cond(eq(gstate, State.END), [set(bottom, add(bottom, 10))], bottom);
const Comp = () => {
return (
<TapGestureHandler onHandlerStateChange={onHandlerStateChange}>
<Animated.View style={{ flex: 1, backgroundColor: "pink" }}>
<Animated.View
style={{ flex: 1, position: "absolute", bottom: bottom }}
>
<Text> some text</Text>
</Animated.View>
</Animated.View>
</TapGestureHandler>
);
};
export default Comp;