how can I stop the wheel of fortune to particular point using Animated API in React Native? - react-native

I'm developing wheel of fortune where I want the wheel to iterate four times while spinning, and stop to a particularly given value. All I want is to spin the wheel four times and stop to a particular slice. I'm thinking below code can work my idea.
Animated.loop(
Animated.timing(this._angle, {
toValue: 920,
duration: 5000,
}),//.start();
{
iterations: 1
}
).start();
I'm using angle as value. My formula to get the 'toValue' is "targetAngle * 4". This works but I want to decelerate the spinning in its last iterate just like Animated.decay() does. Is there any better idea to resolve this issue?

you can do
const onPress = () => {
Animated.timing(RotateAnimeted, {
toValue: 6,
duration: 1000,
}).start();
};
const Rotate = RotateAnimeted.interpolate({
inputRange: [0, 1, 2, 3, 4, 5, 6],
outputRange: [
"0deg",
"360deg",
"720deg",
"840deg",
"940deg",
"1020deg",
"1080deg",
],
});
You can change the values to get the result you want
I did an example at the expo
Attaches a gif image

Related

How to do series/parallel animation in react-native-reanimated 2?

I'm looking for the equivalent of Animated.sequence and Animated.parallel from react-native. So far from the docs for v2, I could only see the withSequence function that changes the value of only on value and therefore the style of only one component in series.
What I was looking for was to trigger animations in two different components, either in series or in parallel.
For parallel, it seems changing values in statements one after another worked. Correct me if I'm wrong.
// these will run in parallel
val1Shared.value = withTiming(50);
val2Shared.value = withTiming(100);
But for series, I need to have each animation inside a useTiming callback. Which leads to kind of callback hell.
val1Shared.value = withTiming(50, undefined, () => {
val2Shared.value = withTiming(100);
});
Please help with the best practices in achieving this with reanimated 2.
Thanks.
I think there is no way of doing it like in Animated.
This library requires you to think a different way. If you want you run two animations (each one for a separate view) using interpolation, you can use a single shared value and a sufficient interpolation config.
Let's assume (according to your example) that you have two views you want to animate. The first animation is from 0 to 50, the second one is from 0 to 100.
Let's also assume you're transforming an X axis (I don't know what's your case, but it's going to be very similar) and we count the animation progress from 0 to 1, and both views will animate for the same period of time.
Initialize the shared value:
const animation = useSharedValue(0);
And do sth like this (of your choice):
animated.value = withTiming(1, { duration, easing });
For the first view, your transformation will look like this:
const animatedStyles1 = useAnimatedStyle(
() => ({
transform: [
{
translateX: interpolate(animation.value, [0, 0.5, 1], [0, 50, 50]),
},
],
}),
[mode, rectSize]);
and for the second one, like this:
const animatedStyles2 = useAnimatedStyle(
() => ({
transform: [
{
translateX: interpolate(animation.value, [0, 0.5, 1], [0, 0, 100]),
},
],
}),
[mode, rectSize]);
Now the explanation.
For the first view, we only work for the half of progress from the whole animation, so we from 0 to 0.5 we'll animate from 0 to 50. then, for the reset of the progress we'll stay in the same place (this is called a dead zone), to from 0.5 to 1 value of the progress does not change (50 to 50).
Now, for the second view, we wait for the progress to be in half, so from 0 to 0.5 we don't do anything (0 to 0). Then, when the progress is in half, we animate the view from 0 to 150.
In general, that way you can orchestrate the whole thing using just a single shared value.
Here you can find more info about the interpolation. The docs is from the react-native's Animated, but the idea is the same.
Good luck!

React Native - How to start another animation conditionally

I want to create my own loader using animation, my idea is using scale to handle it, so the step should be like:
show an image scale from (n) to 1 for the first time
check if conditional is accepted or not
if conditional accepted, show an image scale from 1 to (n)
I can achieve 1st condition, but when i try 2nd and 3rd condition, the animation start in a blink of eye,
anyone can help me how to fix this?
here's the code i've tried:
//animation function
playAnimation(){
this.setState({onLoad:true})
this._load.setValue(0)
this._scale.setValue(0)
Animated.timing(this._load, {
toValue: 100,
useNativeDriver: true,
duration:1000
}).start(()=>{
setTimeout(() => {
Animated.timing(this._scale, {
toValue: 100,
useNativeDriver: true,
duration:1000
}).start(()=>this.setState({onLoad:false}));
}, 2000);
});
onLoad = this._load.interpolate({
inputRange: [0, 50, 75, 100],
outputRange: [10, 1, 1.4, 1],
})
imageScale = this._scale.interpolate({
inputRange: [20, 35, 70],
outputRange: [1.2, 1, 20],
})
}
//on render
<Animated.Image
style={{
resizeMode:'contain',
position:'absolute',
zIndex:1,
width:width/2,
height:200,
transform:[{scale:this.state.onLoad?onLoad:imageScale}]
}}
source={{uri: "https://www.knittedhome.com/communities/5/004/012/872/235/images/4628207884.jpg"}}
/>
and the result:
How your code works right now is somewhere in your code, you're calling PlayAnimation().
PlayAnimation() will:
start and complete first animation
Wait 2 sec
start and complete second animation
What you're doing in your Animated.Image is setting the transforms to different values. This doesn't start or stop the animation, just tells Animated.Image what value to set transform to.
What you should do
Use one Animated.Value to keep track of the scale. Assign that value to the Animated.Image transform.
Write two Animation functions:
scaleDown, call this where you're currently calling PlayAnimation
scaleUp, use your onLoad conditional to execute this in render or somewhere appropriate.

react native animation , Easing.bounce force bounce only two times

Is it possible to for Easing.bounce to bounce only 2 times?
And not 3 times;
Animated.timing(
this.y_translate,
{
delay: 0,
toValue: 1,
easing: Easing.bounce,
duration: 1000,
}
).start();
});
Short answer is no.
Long answer is to look at the docs.
You'll see that Easing.bounce is based off of this mathematical definition that has the bounce happening 4 times. You can see exactly how they do this in their example code (note the easeInBounce, easeOutBounce, and easeInOutBounce functions). If you compare this with what is in the React Native source for Easing.bounce, you'll find the same mathematical calculations.
So if you want to have a two bounce effect, you'll need to do the math yourself. For example, you can create your own Easing class with it's own bounce method. Then use it in your animation in place of Easing.bounce.
// You can also extend Easing and just define a new method (or override)
// if you want access to Easing's other methods.
class MyEasing {
/**
* Provides a two bounce effect.
*/
static bounce(t: number): number {
/* Code Goes Here */
return something;
}
}
// ... in your code ...
Animated.timing(
this.y_translate,
{
delay: 0,
toValue: 1,
easing: MyEasing.bounce,
duration: 1000,
}
).start();
});
How you do this calculation though is outside the scope of the question, so I'll leave that for your to figure out.

Creating a parallax image in React Native

I'm trying to create an animated "parallax" image in React-Native. I have a static background image and two individual images as an overlay. The goal is to create something similar to the image found on this website http://www.digitalhands.net/ or https://www.galaxia.co/. Where do I even start with this? Initially I'd be happy with it moving just on itself from left to right and etc. Afterwards I want to make it so that it would use the gyroscope to get the x and y values for animating the image.
A parallax effect consists of images moving in different speeds and in the same direction, such that the 'closer' an object is, the faster it moves, which creates the illusion of three dimensions.
To achieve this effect in react-native, you can use the Animated library to interpolate the position of one image as a fraction of the position of another.
For the sake of an example, let's assume you want the parallax effect in the vertical direction, and that all images are positioned at 0 vertically.
First, you would need an animated value in your component's state:
this.state = {
...
imagePos: new Animated.Value(0)
}
Then, for each Image style you can add a transform on its y axis:
<Animated.Image src={...} style={[firstImageStyle, {
transform: [{translateY: this.state.imagePos.interpolate({
inputRange: [0, 1],
outputRange: [0, 100]
})}]
}]}
<Animated.Image src={...} style={[secondImageStyle, {
transform: [{translateY: this.state.imagePos.interpolate({
inputRange: [0, 1],
outputRange: [0, 50]
})}]
}]}
Note the use of Animated.Image instead of Image to make the image animatable.
This would cause the first image to move horizontally between 0-100 dp when imagePos has values between 0 and 1, and the second image to move between 0-50 dp.
To change the value of the animated value you can use any of the functions in the Animated library (timing, spring, decay, etc.) or you can attach it to some native event.
The animated library documentation has much more detail.
As for the use of the gyroscope, I haven't gone into the details, but you can probably use react-native-sensors or react-native-motion-manager to get the values you need, and attach them to the animation.
By using a package like https://github.com/react-native-sensors/react-native-sensors I managed to get the x, y, z values of the phones position and animate an image with it.
constructor() {
super();
this.state = {
x: 0,
y: 0,
z: 0,
};
}
componentWillMount() {
// Normal RxJS functions
accelerationObservable
.subscribe(({x, y, z}) => this.setState({
x: x,
y: y,
z: z,
}));
}
render() { return (
<Image style={{left: this.state.x + this.state.y, top: this.state.z}}
source={require('image.png')}/>
)
}

How to make draggable remember it's location in React Native

I'm learning about the animation and panresponder apis in react native. Following Animated Drag and Drop with React Native and the source
I want to extend the example so that you can drag the circle without having to reset it. Currently the code animates the circle back to the center of the screen and relies on the panresponders dX/dY values.
my best guess is I have to change something in onPanResponderMove
onPanResponderMove: Animated.event([null, {
dx: this.state.pan.x,
dy: this.state.pan.y,
}]),
What do I need to change in the source so if I comment out the onPanResponderRelease logic the circle properly drags around the screen?
getTranslateTransform()?
The logic in onPanResponderRelease is making it so that if the draggable is not in the dropzone when released, it is reset back to 0,0 (these are the lines causing it).
Removing that logic alone isn't all you should do though. You should set the offset and reset the value of this.state.pan in onPanResponderRelease. To do this you need to track the value.
In componentDidMount, add this:
this.currentPanValue = {x: 0, y: 0};
this.panListener = this.state.pan.addListener((value) => this.currentPanValue = value);
Then, in componentWillUnmount:
this.state.pan.removeListener(this.panListener);
Now that you have the current value, you just add this to the PanResponder:
onPanResponderRelease: (e, gestureState) => {
this.state.pan.setOffset({x: this.currentPanValue.x, y: this.currentPanValue.y});
this.state.pan.setValue({x: 0, y: 0});
},
It seems more complicated than it actually is. Basically you are just setting the offset from 0/0, then setting the value to 0/0 so that when you start moving it again after having released it before, it doesn't jump back to 0/0 before jumping back to where your finger is. It also makes sure you have its current position...which you will probably need at some point anyways.
Another way would be to track current offset and keep adding it to the pan. So declare this in the constructor currentPanValue : {x: 0, y: 0},
And edit the onPanResponderRelease to the following :
onPanResponderRelease : (e, gesture) => {
this.state.currentPanValue.x += this.state.pan.x._value;
this.state.currentPanValue.y += this.state.pan.y._value;
this.state.pan.setOffset({x: this.state.currentPanValue.x, y: this.state.currentPanValue.y});
this.state.pan.setValue({x: 0, y: 0});
}
Incase if you want the final coordinates, you should get it with this.state.currentPanValue
Actually the only thing you need to change is the onPanResponderRelease function. What you are currently doing is applying the delta of the gesture movement to the initial position of the component. What you want to do is applying it to the position at the end of the last gesture. So you somehow need to save the offset. AnimatedValueXY.setOffset to the rescue!
onPanResponderRelease : (e, gesture) => {
if(this.isDropZone(gesture)){
this.setState({
showDraggable : false
});
}else{
this.state.pan.setOffset({
x: this.state.pan.x._offset + e.dx,
y: this.state.pan.y._offset + e.dy,
})
this.state.pan.setValue({x: 0, y: 0})
}
I'm using the internal _offset variable here because I could find a way to access it via the API. You can off course also just keep track of the offset manually. I didn't test this code yet, but you get the idea.