I'm trying to retrieve the current color from a react-native animation. It's mapped through interpolate to a set of color strings.
class IconTransition extends React.Component<Props, State> {
protected _param: number = 0;
constructor(props: Props) {
super(props);
this.state = {
param: new Animated.Value(0)
};
this.state.param.addListener(param => {
this._param = param.value;
});
}
componentDidMount() {
Animated.spring(this.state.param, {
mass: 1,
stiffness: 10,
damping: 10,
toValue: 1
});
}
componentWillReceiveProps() {
// I want to do something like this. Would be awesome
// if I could avoid the listener in the constructor.
//
// const currentColor = Animated.interpolate.get({
// currentInput: this._param,
// outputRange: ["#FFFFFF", "#000000"]
// });
}
render() {
return (
<AnimatedIcon
{...this.props}
color={this.state.param.interpolate({
inputRange: [0, 1],
outputRange: ["#FFFFFF", "#000000"]
})}
/>
);
}
}
I want to retrieve the color, as interpolated, should the animation not finish. I'm aware I could probably use an external library such a chroma-js (in particular, the chroma.mix function) to achieve this - but there are different ways to interpolate through two different colors and I'd rather not depend on an external library if I can avoid it.
So... the greater question remains, how can I imperatively request an output value from the interpolation API? Can we not listen on interpolated values, just as we do with Animated.Value()?
I was trying to do the same for a while now and there's a few things you need to keep in mind:
Since you're trying to update a prop that is not a style prop your
best bet is to use the addListener and setNativeProps methods
https://facebook.github.io/react-native/docs/animations#setnativeprops
The interpolation has a __getValue method to check the current value.
This is the function you want to call in the listener to check the
current interpolated value
https://github.com/facebook/react-native/blob/master/Libraries/Animated/src/nodes/AnimatedInterpolation.js#L327
Colors cannot be passed to props like that when set from setNative
props, it has to be passed through processColor
https://github.com/facebook/react-native/blob/master/Libraries/StyleSheet/processColor.js
If you put that all together you can get somthing like the following, which worked in my case:
import React from 'react';
import {View, processColor} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
class BackgroundColorLinearGradientText extends React.Component {
/**
* Class constructor.
*/
constructor(props, context) {
super(props, context);
this.backgroundColor = new Animated.Value(0);
this.backgroundColor.addListener(i => {
let interpolated = this.backgroundColor.interpolate({
inputRange: [0, 1],
outputRange: ['#FF0000', '#00FF00'],
}).__getValue();
if (this.background) {
this.background.setNativeProps({colors: [processColor(interpolated), processColor(this.background.props.colors[1])]})
}
});
}
componentDidMount() {
Animated.timing(this.backgroundColor, {
toValue: 1,
duration: 3000,
}).start();
}
render() {
return (
<LinearGradient ref={i => this.background = i} colors={['red', 'blue']} style={{flex: 1}}>
<View style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
Content
</View>
</LinearGradient>
);
}
}
This will create a screen which has a red to blue gradient background, transitioning to green to blue in three seconds.
Related
I'm looking a way to make animated flatlist item after item. when one item finish his animation so next item(from the flatlist) will be on the screen
class AnimatedFlatList extends React.PureComponent {
state = {selected: (new Map(): Map<string, boolean>)};
let data = {[
{"first_name":"ltorrejon0#si.edu"},
{"first_name":"ichadbourne1#icq.com"},
{"first_name":"ascorthorne2#mediafire.com"},
{"first_name":"jlathwood3#xing.com"},
{"first_name":"molkowicz4#ftc.gov"},
{"first_name":"motridge5#tiny.cc"},
{"first_name":"rcess6#hostgator.com"},
{"first_name":"mmaundrell7#php.net"},
{"first_name":"ufairburne8#instagram.com"},
{"first_name":"pangel9#biglobe.ne.jp"}]
};
_keyExtractor = (item, index) => item.id;
_onPressItem = (id: string) => {
// updater functions are preferred for transactional updates
this.setState((state) => {
// copy the map rather than modifying state.
const selected = new Map(state.selected);
selected.set(id, !selected.get(id)); // toggle
return {selected};
});
};
_renderItem = (item) => (
<View style={Styles.viewItem}}>
<Text style={Styles.textItem>{item.text}</Text>
</View>
);
render() {
return (
<FlatList
data={data}
extraData={this.state}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
/>
);
}
}
When I did animatedView into the renderItem it runs all together and it not what I'm looking for.
Kind of this way (but without press on the button, it will load automatically)
Maybe this is not the best solution but I am using the delay property of Animated.timing() and it works well for me.
My item component looks like this:
export default class CustomItem extends Component {
constructor(props) {
super(props);
this.state = {
scaleValue: new Animated.Value(0)
}
}
componentDidMount() {
Animated.timing(this.state.scaleValue, {
toValue: 1,
duration : 600,
delay: this.props.index * 350
}).start();
}
render() {
return (
<Animated.View style={{ opacity: this.state.scaleValue }}>
{ this.props.children }
</Animated.View>
);
}
}
And here is the flatlist:
...
renderItem(item) {
return (
<CustomItem index={ item.index } >
<Text>{ item.first_name }</Text>
</CustomItem>
);
}
render() {
return (
<FlatList
keyExtractor={this._keyExtractor}
data={data }
renderItem={ this.renderItem.bind(this) }
/>
);
}
So, every single item will delay 350 milliseconds more than the item before it.
Of course, you can change the duration of the animation and the delay property and find the perfect animation for you :)
You need to be careful with the number of the items because you can wait too much time to see the last item :)
Checkout Animated.Stagger. Runs Animation parallelly but with successively specified delay.
What is the best pattern, in react native, to animate components on state change?
For example I have a list of elements and tapping on one I want it to disappear and the ones below him to 'get up' filling the missing space
How can I make the transition smooth?
React-natives own animated API works really well.
Basically you have a value in state, which you connect with a style props, and change that value over time. (for examples follow link)
For smooth animations use usenativedriver (not always possible) and also, make sure you don't have debugger runnning in emulated/real device
EDIT: 2018-05-31
This is an example of how I've used it. Probably exist other ways of doing it
import { Animated, Text} from 'react-native';
class ShowCaseAnimation extends Component {
state = {
animations: {
height: new Animated.Value(0),
fade: new Animated.Value(0),
}
}
componentDidMount() {
const { height, fade } = this.state.animations;
if (this.props.animate) {
doneAnimation({ height, fade }).start(() => {
// Do stuff after animations
});
}
}
render() {
const { animations } = this.state;
return (
<Animated.View
style={{
height: animate? animations.height : 300,
opacity: animate? animations.fade: 1,
// other styling
}}
>
<Text> All your base are belong to us </Text>
</Animated.View>
);
}
}
*doneAnimation: *
import { Animated, Easing } from 'react-native';
export const doneAnimation = ({ height, fade }) => Animated.parallel([
Animated.timing(height, {
toValue: 300,
easing: Easing.elastic(),
duration: 500,
delay: 1500,
}),
Animated.timing(fade, {
toValue: 1,
easing: Easing.ease,
duration: 1000,
delay: 1500,
}),
]);
export default doneAnimation;
doneAnimation will change the state and perform the described animations.
This is how you can trigger an animation on state change in a functional component.
Say you have a Button that changes state with onPress:
<Button title="toggle" onPress={() => setValue(!Value)} />
then you can trigger the animation inside a useEffect with the Value
that changes in the dependency array:
const [Value, setValue] = useState(true);
useEffect(() => {
// Input your animation here
// ...
}, [Value]);
I'm using react native v0.49 and I'm trying to implement custom transition when navigate to other page.
what I'm trying to do is to make transition only for one scene from scene 2 to scene3. but not for all the app.
this example I found it's for all web so I want to make just for one screen and for all the app because if I do that way it will effect for all the app and it's not what I'm looking for. any idea?
class SceneOne extends Component {
render() {
return (
<View>
<Text>{'Scene One'}</Text>
</View>
)
}
}
class SceneTwo extends Component {
render() {
return (
<View>
<Text>{'Scene Two'}</Text>
</View>
)
}
}
let AppScenes = {
SceneOne: {
screen: SceneOne
},
SceneTwo: {
screen: SceneTwo
},
SceneThree: {
screen: SceneTwo
},
}
let MyTransition = (index, position) => {
const inputRange = [index - 1, index, index + 1];
const opacity = position.interpolate({
inputRange,
outputRange: [.8, 1, 1],
});
const scaleY = position.interpolate({
inputRange,
outputRange: ([0.8, 1, 1]),
});
return {
opacity,
transform: [
{scaleY}
]
};
};
let TransitionConfiguration = () => {
return {
// Define scene interpolation, eq. custom transition
screenInterpolator: (sceneProps) => {
const {position, scene} = sceneProps;
const {index} = scene;
return MyTransition(index, position);
}
}
};
class App extends Component {
return (
<View>
<AppNavigator />
</View>
)
}
Here's an example of how we do it, you can add your own transitions to make it your own. Our goal was simply to expose the baked-in transition configurations to have more control over the animations. Our transition configuration: https://gist.github.com/jasongaare/db0c928673aec0fba7b4c8d1c456efb6
Then, in your StackNavigator, add that config like so:
StackNavigator(
{
LoginScreen: { screen: LoginScreen },
HomeScreen: { screen: HomeScreen },
},
{
stateName: 'MainStack',
initialRouteName: 'HomeScreen',
initialRouteParams: { transition: 'fade' },
transitionConfig: TransitionConfig,
}
);
Finally, when you navigate, just add your params when you navigate:
this.props.navigation.navigate('HomeScreen', { transition: 'vertical' })
I have an array of texts that I want to flash on a blank screen, one after the other with animations. Something like:
state = {
meditations: ["Take a deep breath", "embrace this feeling", "breath
deeply", ...]
}
I want to show only one string at a time, and animate their opacity. So a string fades in and fades out, then the next string, and so on.
I am new to react native and quite confused about how to go about this. Please, how may I approach this, I have read the docs but still not clear how to.
Below is what I have tried, I modified this from the docs but it shows everything at once. I'm still trying to see how I can make them animate one after the other, showing only one at a time. Thanks for your help in advance.
import React from 'react';
import { Animated, Text, View } from 'react-native';
class FadeInView extends React.Component {
state = {
fadeAnim: new Animated.Value(0), // Initial value for opacity: 0
}
renderMeditations() {
let { fadeAnim } = this.state;
return this.props.meditations.map((meditation, index) => {
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 2, // Animate to opacity: 1 (opaque)
duration: 10000, // Make it take a while
}
).start(() => {
this.setState({ fadeAnim: new Animated.Value(0) })
}); // Starts the animation
return (
<Animated.Text // Special animatable View
key={index}
style={{
...this.props.style,
opacity: fadeAnim, // Bind opacity to animated value
}}
>
{meditation}
</Animated.Text>
)
})
}
render() {
return (
<View style={{flex: 1}}>
{this.renderMeditations()}
</View>
);
}
}
export default class App extends React.Component {
state = {
meditations: ["Take a deep breath", "Calm down", "Relax", "Tell yourself all will be fine"]
}
render() {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<FadeInView meditations={this.state.meditations} style={{fontSize: 28, textAlign: 'center', margin: 10}} />
</View>
)
}
}
After much toil with this, I was able to solve it with react-native-animatable like so:
import React from "react";
import {
View,
Text,
Animated
} from "react-native";
import * as Animatable from 'react-native-animatable';
class VideoScreen extends React.Component {
state = {
meditations: ["Take a deep breath", "embrace this feeling", "breath
deeply"],
index: 0
};
render() {
const { meditations, index } = this.state;
return (
<View style={{flex: 1}}>
<Animatable.Text
key={index}
animation={'fadeIn'}
iterationCount={2}
direction="alternate"
duration={2000}
onAnimationEnd={() => {
if (this.state.index < this.state.meditations.length - 1) {
this.setState({ index: this.state.index + 1});
}
}}
style={{
position: "absolute",
left: 0, right: 0,
bottom: 40
}}>
{meditations[index]}
</Animatable.Text>
</View>
);
}
}
export default VideoScreen;
The map function executes all at once so basically you are rendering/returning all 3 items at the same time. I understand that your issue is that the animation is working tho.
If what you want is to show one text, then the other and so on I suggest iterating the index of your text array instead of using the map function.
Something like:
Execute Animation
Increase Index
Index = 0 if you are at the end of the array.
In a loop. Check setInterval, it might help you.
For the function components:-
we can use the above-metioned solutions. I am writing a function hopefully it will help you display a looping text with the animation
We will use this package for the animation https://github.com/oblador/react-native-animatable.
import {StyleSheet} from 'react-native';
import React, {useState} from 'react';
import * as Animatable from 'react-native-animatable';
const UserMessage = () => {
const [index, setIndex] = useState(0);
const meditations = [
'Take a deep breath',
'embrace this feeling',
'breath deeply',
];
return (
<Animatable.Text
key={index}
animation={'fadeIn'}
iterationCount={2}
direction="alternate"
duration={2000}
onAnimationEnd={() => {
if (index < meditations.length - 1) {
setIndex(index + 1);
} else {
setIndex(0);
}
}}
style={styles.messageStyle}>
{meditations[index]}
</Animatable.Text>
);
};
export default UserMessage;
const styles = StyleSheet.create({
messageStyle: {
textAlign: 'center',
fontSize: 18,
fontWeight: '500',
width: '80%',
color: '#1C1C1C',
marginBottom: 20,
minHeight: 50,
alignSelf: 'center',
},
});
I'm new to react-native and need some help with Animate.
Goal: to animate Image, so that it looks like its slowly breathing.(getting a little larger then smaller, then back again, constantly, like someone breathing in and out)
My images are stored in an array, inside newOrder() method:
newOrder(timeAsProp) {
const hour = timeAsProp.slice(0, 2);
let returnValue = [];
const yud = <Image key="yud" source={require('./img/yud.png')} style={Style.image} />;
const hey1 = <Image key="hey1" source={require('./img/hey1.png')} style={Style.image} />;
const vav = <Image key="vav" source={require('./img/vav.png')} style={Style.image} />;
const hey2 = <Image key="hey2" source={require('./img/hey2.png')} style={Style.image} />;
return (
<View style={Style.displayContainer}>{returnValue}</View>
);
called in the render method, like this:
{this.newOrder(parsedTime)}
its four seperate images, which are rendered and displayed together on one line.
it looks like this:
letters being rendered to one word:
its important that the Image as a whole, should be breathing together in unison, and not each image on its own.
heres a screen pic so you see what the image looks like, if that will help you understand the best method to make it look alive:
edit:
something that would add to the animation i think, would be two things:
1)size getting larger and smaller
2)actual color patch on the letters slightly moving, maybe closer and further, like zooming in and out or something like that.
i think those two together would make the breathing 3d.
so im interested in hearing peoples opinions how to do this...
thnks!
Use a sequence of animations in a loop.
In this example I am breathing a text.
First change the opacity from 1 to 0, then, change the opacity back to 1.
You can use this principle to change other properties, like width and height.
import React, {Component} from 'react'
import {
Animated,
Easing
} from 'react-native'
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
opacity: new Animated.Value(1)
}
}
componentDidMount() {
Animated.loop(
Animated.sequence([
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 1000,
ease: Easing.linear,
useNativeDriver: true
}),
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1000,
ease: Easing.linear,
useNativeDriver: true
})
])
).start();
}
render() {
return(
<Animated.View style={{opacity: this.state.opacity}}>
<Text>I'm breathing</Text>
</Animated.View>
);
}
}
So for an infinite animation (that you can stop on your own), you can set the width and height of all of the images to the same interpolated Animated value. To generate a breathing effect, one possible way to do this is to tie two animation functions together with one increasing and the other decreasing. For example:
import React, { Component } from 'react';
import { View, StyleSheet, Animated, Image, Easing } from 'react-native';
import { Constants } from 'expo';
const AnimatedImage = Animated.createAnimatedComponent(Image);
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
size: new Animated.Value(1)
}
}
componentDidMount () {
this._loopAnimationUp();
}
// The animation functions. Initial and end values can be anything (not just 1, 10, but remember to use the same value and flip them:
_loopAnimationUp() {
this.state.size.setValue(1);
Animated.timing(this.state.size, {
toValue: 10,
duration: 5000,
easing: Easing.linear
}).start((o) => {
if (o.finished) {
this._loopAnimationDown();
}
});
}
_loopAnimationDown() {
this.state.size.setValue(10);
Animated.timing(this.state.size, {
toValue: 1,
duration: 5000,
easing: Easing.linear
}).start((o) => {
if (o.finished) {
this._loopAnimationUp();
}
});
}
render() {
const size = this.state.size.interpolate({
inputRange: [1, 10],
outputRange: [10, 50],
extrapolate: 'clamp',
});
return (
<View style={styles.container}>
<AnimatedImage
style={[styles.image, {
width: size,
height: size,
}]}
source={{uri: 'http://placekitten.com/g/200/200'}}
/>
<AnimatedImage
style={[styles.image, {
width: size,
height: size,
}]}
source={{uri: 'http://placekitten.com/g/200/200'}}
/>
<AnimatedImage
style={[styles.image, {
width: size,
height: size,
}]}
source={{uri: 'http://placekitten.com/g/200/200'}}
/>
<AnimatedImage
style={[styles.image, {
width: size,
height: size,
}]}
source={{uri: 'http://placekitten.com/g/200/200'}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
flexDirection: 'row',
},
image: {
justifyContent:'center',
backgroundColor:'transparent'
},
});
If you need to later stop the animation, you can use:
this.state.size.stopAnimation();
You can see a working implementation of it here using placeholder images.
For the more math inclined, there is probably a way to accomplish this with a single looping animation and using interpolation in a more complex manner.