Is this doable? Check this link for example.
I've been trying to make it work. This is my current code:
constructor(props) {
super(props);
this.state = {
animationValue: new Animated.Value(0)
};
}
<Animated.View
style={{ flex: 0.15, backgroundColor: `rgba(0,0,0,${background}` }}
onPress={this.removeBackground}
>
<Text style={[{ textAlign: 'right' }>{hometeam.result} - {awayteam.result}</Text>
</Animated.View>
The background is a variable that looks like this:
const background = this.state.animationValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1]
});
What I want to happen is when you click on the animated view, I want the background color to fade. So I was trying to use rgba and change the alpha based on the background variable, which gave me an error.
This is my function to remove the background:
removeBackground = () => {
this.state.animationValue.setValue(0);
Animated.timing(this.state.animationValue, {
toValue: 1,
duration: 2000,
}).start();
}
Any clues how I could do this?
Related
I want to interpolate the backgroundColor of a View.
This is my animation:
const [myAnimation] = useState(new Animated.Value(0));
const setInteraction = (focused) => {
if (focused) {
Animated.timing(myAnimation, {
toValue: 1,
duration: 160,
useNativeDriver: false,
}).start();
}
if (!focused) {
Animated.timing(myAnimation, {
toValue: 0,
duration: 160,
useNativeDriver: false,
}).start();
}
return;
};
And here is how I use it to interpolate the backgroundColor:
<Animated.View
style={{
backgroundColor: myAnimation.interpolate({
inputRange: [0, 1],
outputRange: [
'rgba(27,43,89,0.06)',
'rgba(255,255,255,1)',
],
}),
}}
>
{...}
</Animated.View>
The problem is that since the initial color has a 6% opacity it doesn't work 100% as I expected. It goes from the initial color to an intermediate one and finally ends up in the target color (white):
https://recordit.co/czrVd5Q38I
If the color was 100% opaque it will work, for example:
<Animated.View
style={{
backgroundColor: myAnimation.interpolate({
inputRange: [0, 1],
outputRange: [
'rgba(27,43,89,1)',
'rgba(255,255,255,1)',
],
}),
}}
>
{...}
</Animated.View>
https://recordit.co/HSvJNV4ldg
Any ideas? I've tried playing with the inputRange property but still doesn't work.
UPDATE:
I've realised that the problem is the white color as a target. If I try playing with the alpha value of the same initial / target color it works:
outputRange: [
'rgba(27,43,89,0.06)',
'rgba(27,43,89,0.1)',
],
So Ideally, When i scroll down, I want the header to disappear(slide down) and when I scroll up I want it to show (slide up). Idc where im at in the page. I just want the animation to fire when those 2 events occur. I see some apps have this but I can't think of how to replicate it. please help me set a basis for this
You can use Animated.FlatList or Animated.ScrollView to make the scroll view, and attach a callback to listen onScroll event when it is changed. Then, using interpolation to map value between y-axis and opacity.
searchBarOpacityAnim is a component's state. By using Animated.event, the state will be updated when a callback is called. Also, don't forget to set useNativeDriver to be true. I've attached the link to document below about why you have to set it.
<Animated.FlatList
...
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: searchBarOpacityAnim } } }],
{ useNativeDriver: true },
)}
...
/>
Then, use Animated.View wraps your component which you want to animate it. Use .interpolate to map value between the state and component's opacity like the example below...
<Animated.View
style={{
opacity: searchBarOpacityAnim.interpolate({
inputRange: [213, 215],
outputRange: [0, 1],
}),
}}
>
<SearchBar />
</Animated.View>
You can read more information about useNativeDriver, .interpolate, and Animated.event here.
https://facebook.github.io/react-native/docs/animated#using-the-native-driver
https://facebook.github.io/react-native/docs/animations#interpolation
https://facebook.github.io/react-native/docs/animated#handling-gestures-and-other-events
You can use Animated from 'react-native'
here an example changing the Topbar height:
import { Animated } from 'react-native';
define maxHeight and minHeight topbar
const HEADER_MAX_HEIGHT = 120;
const HEADER_MIN_HEIGHT = 48;
initialize a variable with the scrollY value
constructor(props) {
super(props);
this.state = {
scrollY: new Animated.Value(
Platform.OS === 'ios' ? -HEADER_MAX_HEIGHT : 0,
),
};
}
on render you can interpolate a value acording the scrollY Value
render() {
const { scrollY } = this.state;
// this will set a height for topbar
const headerHeight = scrollY.interpolate({
inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT],
outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT],
extrapolate: 'clamp',
});
// obs: the inputRange is the scrollY value, (starts on 0)
// and can go until (HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT)
// outputRange is the height that will set on topbar
// obs: you must add a onScroll function on a scrollView like below:
return (
<View>
<Animated.View style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
backgroundColor: '#2e4265',
height: headerHeight,
zIndex: 1,
flexDirection: 'row',
justifyContent: 'flex-start',
}}>
<Text>{title}</Text>
</Animated.View>
<ScrollView
style={{ flex: 1 }}
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
)}>
<View style={{ height: 1000 }} />
</ScrollView>
</View>
);
}
I originally used setInterval() to make a looped image background by having two images that one starts at x:0 and another starts at x: imageWidth, then update them in the following way:
_updateBackgroundImage = () => {
this.setState({
background1Left: this.state.background1Left > (-this.backgroundImageWidth) ? this.state.background1Left-3 : this.backgroundImageWidth,
background2Left: this.state.background2Left > (-this.backgroundImageWidth) ? this.state.background2Left-3 : this.backgroundImageWidth,
})
}
It worked just fine but setInterval() was causing conflicts with another component from an online library, so I switched to using Animated API and have the following code:
this.translateValue = new Animated.Value(0)
translate() {
this.translateValue.setValue(0)
Animated.timing(
this.translateValue,
{
toValue: 1,
duration: 14000,
easing: Easing.linear
}
).start(()=>this.translate())
}
const translateBackgroundImage1 = this.translateValue.interpolate({
inputRange: [0, 1],
outputRange: [0, -this.backgroundImageWidth]
})
const translateBackgroundImage2 = this.translateValue.interpolate({
inputRange: [0, 1],
outputRange: [this.backgroundImageWidth, -this.backgroundImageWidth]
})
return (
<View style={{flex:1}}>
<Animated.Image
style={{
flex: 1,
position: 'absolute',
left: translateBackgroundImage1,
}}
resizeMode={Image.resizeMode.cover}
source={this.backgroundImage}
/>
<Animated.Image
style={{
flex: 1,
position: 'absolute',
left: translateBackgroundImage2,
}}
resizeMode={Image.resizeMode.cover}
source={this.backgroundImage}
/>
To apply the same logic I used for setInterval() I would have translateBackgroundImage1 to start at x:0 in the first loop and then starts at x: ImageWidth
I'm not sure how to implement this with Animated
I eventually found a solution. It's not very clean but works.
I essentially have two Animated.image loaded from the same image. Then I have a translateValue1, which controls the left position of the first image. Then based on translateValue1, we have translateValue2 that has an offset of the imagewidth. translateValue2 controls the left position of the second image.
When the first image about to exit from the screen, it will go to the far right of the screen, and at the same time the second image will be moved to the front of the first image, so we need to change the offset to -imagewidth. Therefore there's a setState method in each of the two animation functions.
Inside the constructor I have these variables:
constructor(props) {
super(props);
this.backgroundImage = require('../assets/images/graidenttPastel.jpg');
this.backgroundImageWidth = resolveAssetSource(this.backgroundImage).width;
this.translateXValue1 = new Animated.Value(-1);
this.translateXValue2 = new Animated.Value(0);
this.animationLength = 20000;
this.state = {
translateXValue2Offset: this.backgroundImageWidth,
stopAnimation: false,
}
Then I have these two functions, each controls half of the loop:
translateXFirstHalfLoop() {
this.translateXValue1.setValue(-1);
this.setState({translateXValue2Offset: this.backgroundImageWidth});
this.firstHalfLoop = Animated.timing(
this.translateXValue1,
{
toValue: -this.backgroundImageWidth,
duration: this.animationLength/2,
easing: Easing.linear
}
).start(() => {
if(this.state.stopAnimation === false) {
this.translateXSecondHalfLoop()
}
})
}
translateXSecondHalfLoop() {
this.translateXValue1.setValue(this.backgroundImageWidth);
this.setState({translateXValue2Offset: -this.backgroundImageWidth});
this.secondHalfLoop = Animated.timing(
this.translateXValue1,
{
toValue: 0,
duration: this.animationLength/2,
easing: Easing.linear
}
).start(() => {
if(this.state.stopAnimation === false) {
this.translateXFirstHalfLoop()
}
})
}
Finally in the render() method, I have two Animated.Image like below:
render() {
this.translateXValue2 = Animated.add(this.translateXValue1, this.state.translateXValue2Offset);
return (
<SafeAreaView
style={[{backgroundColor: THEME_COLOR, flex: 1}]}
forceInset={{ bottom: 'never' }}>
<Animated.Image
style={{
position: 'absolute',
left: this.translateXValue1,
}}
resizestate={Image.resizeMode.cover}
source={this.backgroundImage}
/>
<Animated.Image
style={{
position: 'absolute',
left: this.translateXValue2,
}}
resizestate={Image.resizeMode.cover}
source={this.backgroundImage}
/>
<View style={{flex:1}}>
{this._renderScreenContent()}
</View>
</SafeAreaView>
);
}
Since each half loop function calls the other, we need to stop them before this component is unmounted, so we have this additional step below:
//clean up animation first
this.setState({stopAnimation: true}, () => {
this.props.navigation.goBack()
})
If you are looking to animate an ImageBackground, try
var AnimatedImage = Animated.createAnimatedComponent(ImageBackground)
Seems like I did everything in the documentation and referenced other examples.
Trying to animate a text component rotation:
this.state = {
rotateAnimation: new Animated.Value(0),
};
spin = () => {
this.state.rotateAnimation.setValue(0);
Animated.timing(this.state.rotateAnimation, {
toValue: 1,
duration: 3000,
easing: Easing.linear
}).start((animation) => {
if (animation.finished) {
this.spin();
}
});
};
render() {
return (
<Content>
<View>
<Text style={{
transform: [
{
rotate:
this.state.rotateAnimation.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"]
})
}
]
} ]}>{this.FAIcons.circleONotch}</Text>
</View>
</Content>
);
}
This works fine if I manually enter in any degree i.e rotate: "90deg"
However, when I use interpolate(), I get this error: Transform with key of "rotate" must be a string: {"rotate":"0deg"}
Seems like Interpolate is not returning a string. I tried to typecast it using "toString()" but then I get this error: Rotate transform must be expressed in degrees (deg) or radians (rad): {"rotate":"[object Object]"}
I followed this documentation: https://facebook.github.io/react-native/docs/animations.html
And referenced this example: https://gist.github.com/levynir/5962de39879a0b8eb1a2fd77ccedb2d8
What am I doing wrong here?
**** EDIT ****
Thanks to #Guilherme Cronemberger for pointing me in the right direction, you need to create the component like this.
render() {
const StyledAnimatedText = Animated.createAnimatedComponent(Text);
}
Then utilize it like this:
return (
<StyledAnimatedText
style={{
fontFamily: 'FontAwesome',
backgroundColor: 'transparent',
transform: [{
rotate: this.state.rotateAnimation.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"]
})
},
{ perspective: 1000 }]
}}>
{this.FAIcons.circleONotch}
</StyledAnimatedText>
)
Interpolate is a function which results are used only in declared "Animated" classes, so you'd add "Animated." to your Text class.
render() {
var rotateProp = this.state.rotateAnimation.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"]
})
console.log(rotateProp) //just to check if it's returning what you want
return (
<Content>
<View>
<Animated.Text style={{
transform: [
{
rotate: rotateProp
}
]
} ]}>{this.FAIcons.circleONotch}</Animated.Text>
</View>
</Content>
);
}
I had the same problem
Just use <Animated.Text> instead of <Text />
I had the same issue
I fixed it by using <Animated.SomeComponent> instead of <SomeComponent>
As an example if you want to animate View component :
import { Animated, View } from 'react-native';
<Animated.View>
<ChildComponents/>
</Animated.View>
Thanks.
I had the same problem and I fix it by doing this.Convert PI in degree = (PI = 180deg) & (2 * PI= 360geg),
The answer is :
{ rotate: progress.interpolate(
{
inputRange: [0.5, 1],
outputRange: ['180deg', '360deg'],
}
),
},
I have some graphs and need to show the drawing path.
In the iOS, i can stroke the graph in CALayer with CABasicAnimation
How to do animation for stroking in React Native
To render an animated Line in a chart, you can use a charts library. One of the simplest and best-documented charts libraries in react native is - react-native-gifted-charts
It will not only make the animations easy to implement, but also let you easily customise and beautify your chart. Here is an animated Line chart made using this library. And below is the code for the same-
import { LineChart } from "react-native-gifted-charts"
const App = () => {
const lineData = [
{value: 0, dataPointText: '0'},
{value: 20, dataPointText: '20'},
{value: 18, dataPointText: '18'},
{value: 40, dataPointText: '40'},
{value: 36, dataPointText: '36'},
{value: 60, dataPointText: '60'},
{value: 54, dataPointText: '54'},
{value: 85, dataPointText: '85'}
];
return (
<View style={{backgroundColor: '#1A3461'}}>
<LineChart
initialSpacing={0}
data={lineData}
spacing={30}
textColor1="yellow"
textShiftY={-8}
textShiftX={-10}
textFontSize={13}
thickness={5}
hideRules
hideYAxisText
yAxisColor="#0BA5A4"
showVerticalLines
verticalLinesColor="rgba(14,164,164,0.5)"
xAxisColor="#0BA5A4"
color="#0BA5A4"
/>
</View>
);
};
it is an old question but I post the answer for the reference. You can use Animated from "react-native". you have to consider three directions: Horizontal, Vertical and diagonal. I added the comments to clarify the code
import React, { ReactElement, useRef, useEffect } from "react";
import { StyleSheet, Animated } from "react-native";
// size prop is to determine the size of graph
export default function Line({ size }): ReactElement {
// Pythagorean theorem to calculate the diagonal height
const diagonalHeight = Math.sqrt(Math.pow(size, 2) + Math.pow(size, 2));
// if we dont use ref, animation will be reinitialized everytime we rerender the component
// we want animation to be rendered when component reredners so we use useeffect
const animationRef = useRef<Animated.Value>(new Animated.Value(0));
useEffect(() => {
// timing is a linear animation
Animated.timing(animationRef.current, {
toValue: 1,
duration: 700,
// true might not work with the all properties that you need to animate. true might improve animation performance
useNativeDriver: false
}).start();
}, []);
return (
<>
{conditionForVerticalDisplay && (
// if we are using Animated, it has to be inside Animated.View not View
<Animated.View
style={[
// you can pass multiple style objects
{
// interpolate maps the animated value to another value
height: animationRef.current.interpolate({
inputRange: [0, 1],
outputRange: ["0%", "100%"]
})
}
]}
></Animated.View>
)}
{conditionForHorizontalDisplay && (
<Animated.View
style={[
{
height: animationRef.current.interpolate({
inputRange: [0, 1],
outputRange: ["0%", "100%"]
})
}
]}
></Animated.View>
)}
{ conditionForDiagonalDisplay && (
<Animated.View
style={[
{
height: diagonalHeight,
transform: [
{
// negative will shift it upward.
// translateY: -(diagonalHeight - size) / 2
translateY: animationRef.current.interpolate({
inputRange: [0, 1],
outputRange: [size / 2, -(diagonalHeight - size) / 2]
})
},
{
rotateZ: conditionToPosOrNegDiagonal === "MAIN" ? "-45deg" : "45deg"
}
]
}
]}
></Animated.View>
)}
</>
);
}