I'm totally new to animations in react-native and I'm trying to create an animated pulsating button using the react-native-reanimated library.
Animation concepts are really not that clear for me yet but by modifying someone else code, I got pretty close to what I want to create.
I would like to make this pulsating animation continuous. Currently, it pulsates and then stops. I'd appreciate some help with this. I'm including both the code and the snack for you to see a running sample. Please keep in mind that I simply modified someone else's code so I'm sure there are things in this code that are NOT necessary. I'm learning as I work on this button.
Here's a link to the snack: https://snack.expo.io/#imsam67/reanimated-test
And here's the code:
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import Animated from 'react-native-reanimated';
const {
divide,
set,
cond,
startClock,
stopClock,
clockRunning,
block,
spring,
debug,
Value,
Clock,
} = Animated;
function runSpring(clock, value, dest) {
const state = {
finished: new Value(0),
velocity: new Value(0),
position: new Value(0),
time: new Value(0),
};
const config = {
toValue: new Value(0),
damping: 10,
mass: 5,
stiffness: 101.6,
overshootClamping: false,
restSpeedThreshold: 0.001,
restDisplacementThreshold: 0.001,
};
return block([
cond(clockRunning(clock), 0, [
set(state.finished, 0),
set(state.time, 0),
set(state.position, value),
set(state.velocity, -2500),
set(config.toValue, dest),
startClock(clock),
]),
spring(clock, state, config),
cond(state.finished, debug('stop clock', stopClock(clock))),
state.position,
]);
}
export default class Example extends Component {
constructor(props) {
super(props);
const clock = new Clock();
this._trans = runSpring(clock, 10, 150);
}
componentDidMount() {}
render() {
return (
<View style={styles.container}>
<Animated.View
style={[styles.circle, { borderWidth: divide(this._trans, 5) }]}>
</Animated.View>
</View>
);
}
}
const BOX_SIZE = 100;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'black',
},
circle: {
backgroundColor: "white",
borderColor: "red",
borderRadius: 150,
height: 150,
width: 150
}
});
A quick way to get this animation to loop is to set the damping to 0. This will keep the spring animation going indefinitely.
const config = {
toValue: new Value(0),
damping: 0, // changed to 0
mass: 5
stiffness: 101.6,
overshootClamping: false,
restSpeedThreshold: 0.001,
restDisplacementThreshold: 0.001,
};
But you would want to change the borderWidth style to divide by a larger number to keep the border radius from overshooting.
<Animated.View
style={[styles.circle, { borderWidth: divide(this._trans, 25) }]}>
</Animated.View>
You can find the modified Snack here.
For a repeating animtion like this, you could also look at using Lottie, which is much simpler to implement, but less flexible.
Also, you could look at using loop from react native Animations which should allow you to set the border radius.
Another way to do it a bit more controlled with reanimated 1.x.x is:
const [clock] = useState(() => new Clock());
const loopingValue = useMemo(() => {
const state = {
finished: new Value(0),
position: new Value(0),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: new Value(2000),
toValue: new Value(1),
easing: Easing.linear,
};
const value = block([
// start right away
startClock(clock),
// process your state
timing(clock, state, config),
// when over (processed by timing at the end)
cond(state.finished, [
// we stop
stopClock(clock),
// set flag ready to be restarted
set(state.finished, 0),
// same value as the initial defined in the state creation
set(state.position, 0),
// very important to reset this ones !!! as mentioned in the doc about timing is saying
set(state.time, 0),
set(state.frameTime, 0),
// and we restart
startClock(clock),
]),
state.position,
]);
return interpolate(value, {
inputRange: [0, 0.5, 1],
outputRange: [0, 1, 0],
});
}, [clock]);
A lot of this code is copied from a github thread here: https://github.com/software-mansion/react-native-reanimated/issues/162
Related
I am very new to reanimated and have it working to a certain extent but I can not seem to add the duration of the animation. I would also like to add a delay but that is not as important as the duration. I am trying to change the opacity and its Y position. I can change the duration of the opacity but not the Y position. I have tried messing with the settings like damping, stiffness etc but that does not change the actual duration.
Am I using it in the wrong way?
I am currently trying this :
const offset = useSharedValue(400);
const imgOpacity= useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateY: withSpring(offset.value, { mass: 0.1, damping: 100, stiffness: 100 }),
},
],
opacity: withTiming(imgOpacity.value, { duration: 1000 }),
};
});
I am changing the offset like this :
React.useEffect(() => {
if (show) {
offset.value = withSpring(10);
imgOpacity.value =1;
} else {
// alert("No Show")
}
}, [show]);
I have tried this to add withTiming but it is basically the same. Any advice would be appreciated.
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateY : withTiming(offset.value, {
duration: 1,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}),
},
],
opacity: withTiming(imgOpacity.value, { duration: 1000 }),
};
});
The element I am trying to animate is this, is only has 3 images in it.
<Animated.View style={[animatedStyle]}>
<Image style={styles.BImage} source={{ uri: imgb }} />
<Image style={styles.AImage} source={{ uri: imga }} />
<Image style={styles.CImage} source={{ uri: imgc }} />
</Animated.View>
I have managed to achieve this but I am not sure this is the only and best way to do it. I added a config that is added to the Timing animation that adds a away to control the timing. I have also managed to add a delay before animation starts. I have added this in case anyone is starting out with reanimated and is having the same issue.
The code :
const offset = useSharedValue(90); // The offset is the Y value
const config = { // To control the lengh of the animation
duration: 500,
easing: Easing.bounce,
};
const animatedImg = useAnimatedStyle(() => { // The actual animation
return {
transform: [{ translateY: withTiming(offset.value, config) }],
};
});
<Animated.View style={[animatedImg2]}> // Object to animate
<Image
style={[styles.BImage]}
source={{
uri: 'https://image.tmdb.org/t/p/w440_and_h660_face/t6HIqrRAclMCA60NsSmeqe9RmNV.jpg',
}}
/>
</Animated.View>
The animated object can be moved by settting
offset.value = 200 // Method to change the Y value
Too add a delay before animation :
transform: [{ translateY: withDelay(1000, withTiming(offset.value, config)) }],
I want an icon to bounce (or other animations) when I click the icon. Im trying to implement it looking at the example on expo but nothing works and I also have problems understanding:
interpolate : how do I know what numbers I need for inputRange&outputRange? I cannot find any info about this.
I also dont really get how to use animatedStyles in order to achieve the bounce effect.
Ive looked up tutorials but I still dont get it. An help would be really great. Thank you!!
import { Ionicons } from '#expo/vector-icons';
const AnimatedIconComponent = Animated.createAnimatedComponent(Ionicons);
const AnimatedIcon = () => {
let bounce = new Animated.Value(60);
const animate = () => {
bounce.setValue(0);
Animated.timing(bounce,
{
toValue: 100,
duration: 1200,
easing: Easing.bounce,
useNativeDriver: true
})
.start()
}
const size = bounce.interpolate({
inputRange: [0, 1],
outputRange: [0, 80]
});
const animatedStyles = [
styles.icon,
{
bounce,
width: size,
height: size
}
];
return (
<View>
<TouchableHighlight onPress={()=> animate()}>
<AnimatedIconComponent
name="checkmark-circle-outline"
size={64} style={animatedStyles}
/>
</TouchableHighlight>
</View>
)
};
export default AnimatedIcon;
What Currently Happens
There is an image of a gun. You click the gun, an animation fires with a single bullet.
You click the gun again. The original bullet animation disappears and new bullet comes out of the gun.
What I Want to Happen
You click the gun multiple times and multiple bullets are seen animating. The original bullet does not go away until the animation has run its course.
Is this possible with React Native? Can you point me in the right direction?
Here's the snippet of the animation code if it helps...
import React from 'react';
import { View, Animated, Easing, Dimensions, Image } from 'react-native'
let animatedValue = new Animated.Value(0)
export let animateHappy = () => {
console.log('animate happy run')
animatedValue.setValue(0)
Animated.timing(
animatedValue,
{
toValue: 1,
duration: 800,
useNativeDriver: true,
easing: Easing.linear
}
)
.start()
}
export function HappyAnimation() {
let windowWidth = Dimensions.get('window').width
let windowHeight = (Dimensions.get('window').height)
const movingMargin = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, (windowHeight/2.4)]
})
const movingXHappy = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, -90]
})
return (
<Animated.Image
source={require('./assets/happy.png')}
style={{
transform: [
{translateY: (movingMargin)},
{translateX: (movingXHappy)}
],
zIndex: 0,
marginLeft: 144,
marginTop: (windowHeight*.35),
height: 14,
width: 14,}}
/>
)
}
export default HappyAnimation
You need to instantiate, render, and trigger a new HappyAnimation component each time the gun fires. Consider using some type of delay to delete the component after the animation finishes.
I want an animation to be executed when i open my keyboard and revert back when I close it.
The problem is:
undefined is not an object (evaluating 'this.state.scaleValue')
I have Keyboard.listener which works good.
State:
this.state = {
scaleValue: new Animated.Value(0),
}
Animated View
<Animated.View style={styles.logoContainer,
{
transform: [
{scale: logoScale}
]
}
}>
<Image source={require('./someimage.png')} style={{width: 64, height: 64}} />
</Animated.View>
Interpolate
const logoScale = this.state.scaleValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [1, 0.5, 0]
});
Trigger
_keyboardDidShow () {
this.state.scaleValue.setValue(0);
Animated.timing(
this.state.ScaleValue,
{
toValue: 1,
duration: 300,
easing: Easing.easeOutBack
}
).start();
}
So, when I put the code from trigger into a function and call it onPress with Touchable, it works.
this does not have the same context inside the function supplied to Animated.timing.
You need to bind the method:
this._keyboardDidShow = this._keyboardDidShow.bind(this)
If that doesn't work you could probably even set let me = this above the call to timing (and refer to it as me within the body of the timing method call).
Can someone give me an idea of how this can be done, e.g. animate the height from 0 when added and back to 0 when removed?
Animation when added is easy, just use Animated in componentDidMount with your listRow , for example:
componentDidMount = ()=> {
Animated.timing(this.state._rowOpacity, {
toValue: 1,
duration: 250,
}).start()
}
Animate a component before unmount is much harder in react-native. You should set a handler for ListView. When dataSource changed, diff the data, start Animated to hide removed row, and set new dataSource for ListView.
Here you can get full working example for opacity animation:
import React from 'react-native';
export default class Cell extends React.Component {
constructor(props) {
super(props);
this.state = {
opacity: new React.Animated.Value(0)
};
}
componentDidMount() {
React.Animated.timing(this.state.opacity, {
toValue: 1,
duration: 250,
}).start();
}
render() {
return (
<React.Animated.View style={[styles.wrapper, {opacity: this.state.opacity}]}>
<React.Image source={{uri: 'http://placehold.it/150x150'}} style={styles.image}/>
<React.Text style={styles.text}>
Text
</React.Text>
</React.Animated.View>
);
}
}
const styles = React.StyleSheet.create({
wrapper: {
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
},
image: {
height: 40,
width: 40,
marginRight: 16,
backgroundColor: '#C9D5E6'
},
text: {
fontSize: 20
}
});
In case you need for removing an item from the list, here's how to do the ListRow component:
class DynamicListRow extends Component {
// these values will need to be fixed either within the component or sent through props
_defaultHeightValue = 60;
_defaultTransition = 500;
state = {
_rowHeight : new Animated.Value(this._defaultHeightValue),
_rowOpacity : new Animated.Value(0)
};
componentDidMount() {
Animated.timing(this.state._rowOpacity, {
toValue : 1,
duration : this._defaultTransition
}).start()
}
componentWillReceiveProps(nextProps) {
if (nextProps.remove) {
this.onRemoving(nextProps.onRemoving);
} else {
// we need this for iOS because iOS does not reset list row style properties
this.resetHeight()
}
}
onRemoving(callback) {
Animated.timing(this.state._rowHeight, {
toValue : 0,
duration : this._defaultTransition
}).start(callback);
}
resetHeight() {
Animated.timing(this.state._rowHeight, {
toValue : this._defaultHeightValue,
duration : 0
}).start();
}
render() {
return (
<Animated.View
style={{height: this.state._rowHeight, opacity: this.state._rowOpacity}}>
{this.props.children}
</Animated.View>
);
}
}
i've posted a complete tutorial to this question in this blog post. And it's explaining step by step what you need to do to accomplish both adding and removing an item and animate this process.
For adding is pretty straight forward, but for removing looks like it's a little bit more complex.
http://moduscreate.com/react-native-dynamic-animated-lists/
Here's a full example for height and opacity animation. It supports both adding and removing an element. The key point is that you need to reset the height and opacity after the disappearing animation completes. Then you immediately delete the item from the source.
export const ListItem = (props: ListItemProps) => {
// Start the opacity at 0
const [fadeAnim] = useState(new Animated.Value(0));
// Start the height at 0
const [heightAnim] = useState(new Animated.Value(0));
/**
* Helper function for animating the item
* #param appear - whether the animation should cause the item to appear or disappear
* #param delay - how long the animation should last (ms)
* #param callback - callback to be called when the animation finishes
*/
const _animateItem = (appear: boolean = true, delay: number = 300, callback: () => void = () => null) => {
Animated.parallel([
Animated.timing(
fadeAnim,
{
toValue: appear ? 1 : 0,
duration: delay,
}
),
Animated.timing(
heightAnim,
{
toValue: appear ? 100 : 0,
duration: delay,
}
),
]).start(callback);
};
// Animate the appearance of the item appearing the first time it loads
// Empty array in useEffect results in this only occuring on the first render
React.useEffect(() => {
_animateItem();
}, []);
// Reset an item to its original height and opacity
// Takes a callback to be called once the reset finishes
// The reset will take 0 seconds and then immediately call the callback.
const _reset = (callback: () => void) => {
_animateItem(true,0, callback);
}
// Deletes an item from the list. Follows the following order:
// 1) Animate the item disappearing. On completion:
// 2) Reset the item to its original display height (in 0 seconds). On completion:
// 3) Call the parent to let it know to remove the item from the list
const _delete = () => {
_animateItem(false, 200, () => _reset(props.delete));
};
return (
<Animated.View
style={{height: heightAnim, opacity: fadeAnim, flexDirection: 'row'}}>
<Text>{props.text}</Text>
<Button onPress={() => _delete()}><Text>Delete</Text></Button>
</Animated.View>
);
}