i want move a ball on x and y axis, but the animate not is soft,
the movement is tremulous, and if i move more fast it don't move on diagonally exact, but do a angle how i can move the ball with soft? here are the exemple:
https://snack.expo.io/HJvm5WI5N
the code is it:
import React from 'react';
import {Animated, StyleSheet, Text, TouchableOpacity, View} from 'react-native';
export default class App extends React.Component {
constructor(props) {
super(props)
this.ball = new Animated.ValueXY({x: 30, y: 30})
}
moveBall = () => {
Animated.timing(this.ball, {
toValue: {x: 250, y: 350},
duration: 2000
}).start()
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.moveBall}>
<Animated.View style={[styles.ball, this.ball.getLayout()]}>
<Text style={styles.text}>+</Text>
</Animated.View>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
ball: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: 'red',
alignItems: 'center',
justifyContent: 'center',
},
text: {
fontWeight: 'bold',
color: 'white',
fontSize: 32
}
});
You can use useNativeDriver for better performance. Use it with translateX and translateY. Because you can't use useNativeDriver with left and right properties of style.
export default class App extends React.Component {
constructor(props) {
super(props);
this.ball = new Animated.Value(0);
}
moveBall = () => {
Animated.timing(this.ball, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
};
render() {
const xVal = this.ball.interpolate({
inputRange: [0, 1],
outputRange: [0, 250],
});
const yVal = this.ball.interpolate({
inputRange: [0, 1],
outputRange: [0, 350],
});
const animStyle = {
transform: [
{
translateY: yVal,
translateX: xVal,
},
],
};
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.moveBall}>
<Animated.View style={[styles.ball, animStyle]}>
<Text style={styles.text}>+</Text>
</Animated.View>
</TouchableOpacity>
</View>
);
}
}
UPDATE
with hooks
const App = () => {
const ballAnimatedValue = useRef(new Animated.Value(0)).current;
const moveBall = () => {
Animated.timing(ballAnimatedValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
};
const xVal = ballAnimatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 250],
});
const yVal = ballAnimatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 350],
});
const animStyle = {
transform: [
{
translateY: yVal,
translateX: xVal,
},
],
};
return (
<View style={styles.container}>
<TouchableOpacity onPress={moveBall}>
<Animated.View style={[styles.ball, animStyle]}>
<Text style={styles.text}>+</Text>
</Animated.View>
</TouchableOpacity>
</View>
);
};
Related
I have this example: https://snack.expo.dev/#skjones90/0b47db
...of a card-flipping animation in RN. It seems to work fine (although a bit outdated). But I can't figure out how to get the card to slide off the screen to the left while another slides in from the right.
Is there a simple way to do this without some external library?
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
TouchableOpacity,
Animated
} from 'react-native';
export default class animatedbasic extends Component {
componentWillMount() {
this.animatedValue = new Animated.Value(0);
this.value = 0;
this.animatedValue.addListener(({ value }) => {
this.value = value;
})
this.frontInterpolate = this.animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg'],
})
this.backInterpolate = this.animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ['180deg', '360deg']
})
this.frontOpacity = this.animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [1, 0]
})
this.backOpacity = this.animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [0, 1]
})
}
flipCard() {
if (this.value >= 90) {
Animated.spring(this.animatedValue,{
toValue: 0,
friction: 8,
tension: 10
}).start();
} else {
Animated.spring(this.animatedValue,{
toValue: 180,
friction: 8,
tension: 10
}).start();
}
}
render() {
const frontAnimatedStyle = {
transform: [
{ rotateY: this.frontInterpolate }
]
}
const backAnimatedStyle = {
transform: [
{ rotateY: this.backInterpolate }
]
}
return (
<View style={styles.container}>
<View>
<Animated.View style={[styles.flipCard, frontAnimatedStyle, {opacity: this.frontOpacity}]}>
<Text style={styles.flipText}>
This text is flipping on the front.
</Text>
</Animated.View>
<Animated.View style={[styles.flipCard, styles.flipCardBack, backAnimatedStyle, {opacity: this.backOpacity}]}>
<Text style={styles.flipText}>
This text is flipping on the back.
</Text>
</Animated.View>
</View>
<TouchableOpacity onPress={() => this.flipCard()}>
<Text>Flip!</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>Next card</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
flipCard: {
width: 200,
height: 200,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'blue',
backfaceVisibility: 'hidden',
},
flipCardBack: {
backgroundColor: "red",
position: "absolute",
top: 0,
},
flipText: {
width: 90,
fontSize: 20,
color: 'white',
fontWeight: 'bold',
}
});
AppRegistry.registerComponent('animatedbasic', () => animatedbasic);
I'm trying to create a Flip card, a simple one, but when I press to flip it won't, got the example from https://www.youtube.com/watch?v=ghLuM0ANOk4.
When I click the Flip button nothing happens, not even an error message
Here is the code
const [isFlipped, setIsFlipped] = useState(false);
const animate = useRef(new Animated.Value(0));
const frontRef = useRef();
const backRef = useRef();
const doAFlip = () => {
Animated.timing(animate.current, {
duration: 300,
toValue: isFlipped ? 0 : 180,
useNativeDriver: true,
}).start(() => {
setIsFlipped(!isFlipped);
});
};
const interpolateFront = animate.current.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg'],
});
const interpolateBack = animate.current.interpolate({
inputRange: [0, 180],
outputRange: [ '180deg', '360deg'],
});
const rotateFront = {
transform: [
{
rotateY: interpolateFront,
},
],
};
const rotateBack = {
transform: [
{
rotateY: interpolateBack,
},
],
};
and here
<View>
<Animated.View style={[rotateFront], {backfaceVisibility: 'hidden'}}>
<View style={{backgroundColor: 'green', height: 60, width: 60}}/>
</Animated.View>
<Animated.View style={[rotateBack], {backfaceVisibility: 'hidden', position: 'absolute', top: 0}}>
<View style={{backgroundColor: 'blue', height: 60, width: 60}}/>
</Animated.View>
<Button title='Flip' onPress={doAFlip}>Flip</Button>
</View>````
I am trying to create a CustomBottomTabNavigator in react native. By now I have applied the linear gradient and added the icons on top of the tab. My goal is to move the icon upwards when the focus is on it, but for some reason, all the icons are moving upwards when the focus is on only one icon.
Here is the code:
import React, { useRef } from "react";
import {
View,
Text,
StyleSheet,
Animated,
TouchableOpacity,
} from "react-native";
import * as Icons from "#expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
const CustomTabBar = ({ state, descriptors, navigation }) => {
let icons_name = ["home", "search", "tv", "user"];
const animatedValueHome = useRef(new Animated.Value(0)).current;
const translateY = animatedValueHome.interpolate({
inputRange: [50, 100, 150],
outputRange: [25, 50, 75],
});
const animationHome = (focus, name) => {
console.log("name", name);
navigation.navigate(name);
if (focus === true) {
Animated.timing(animatedValueHome, {
toValue: -25,
duration: 1000,
useNativeDriver: false,
}).start();
} else {
Animated.timing(animatedValueHome, {
toValue: 0,
duration: 1000,
useNativeDriver: false,
}).start();
}
};
return (
<LinearGradient
colors={["#181823", "#3A3A46", "#3A3A46"]}
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
locations={[0.2, 0.6, 0.3]}
style={styles.container}
>
<View style={styles.tabs}>
{state.routes.map((route, index) => {
const isFocused = state.index === index;
return (
<Animated.View
key={index}
style={{
flex: 1,
flexDirection: "row",
transform: [{ translateY }],
}}
>
<Icons.Feather
name={icons_name[`${index}`]}
size={24}
color="#fff"
onPress={() => animationHome(isFocused, route.name)}
/>
</Animated.View>
);
})}
</View>
</LinearGradient>
);
};
export default CustomTabBar;
const styles = StyleSheet.create({
container: {
position: "absolute",
height: 40,
bottom: 20,
right: 30,
left: 20,
elevation: 2,
borderRadius: 20,
},
tabs: {
flex: 1,
flexDirection: "row",
alignItems: "center",
marginLeft: 48,
},
});
Here is the gif of the animation that is happening
Gif. I am using animated API from react-native to achieve this animation.
Let each of the child components have their own animation values.
// In the parent component
{state.routes.map((route, index) => {
const isFocused = state.index === index;
return <Child isFocused={isFocused} />;
})}
// Then for each child
const Child = ({ isFocused }) => {
const animatedValueHome = useRef(new Animated.Value(0)).current;
const translateY = animatedValueHome.interpolate({
inputRange: [50, 100, 150],
outputRange: [25, 50, 75],
});
const animationHome = (focus, name) => {
console.log("name", name);
navigation.navigate(name);
if (focus === true) {
Animated.timing(animatedValueHome, {
toValue: -25,
duration: 1000,
useNativeDriver: false,
}).start();
} else {
Animated.timing(animatedValueHome, {
toValue: 0,
duration: 1000,
useNativeDriver: false,
}).start();
}
};
return (
<Animated.View
style={{
transform: [{ translateY }],
}}
>
<Icons.Feather onPress={() => animationHome(isFocused, route.name)}/>
</Animated.View>
);
}
I am trying to make a tinder like swipe functionality using a functional component.
I've essentially made a functional version of the below tutorial
https://www.instamobile.io/react-native-controls/react-native-swipe-cards-tinder/
However I am having a weird issue where it would appear the state of the card to display doesn't get updated properly. The first time it seems to work but from then on wards it always seems to return 0
I've made a video to illustrate the weird behaviour
https://youtu.be/CRSH36FdhlY
import React, {useState, useEffect, useRef} from "react";
import { StyleSheet, View, Text, TouchableOpacity, Image, ImageBackground, Animated, PanResponder,Dimensions } from "react-native";
import { LinearGradient } from 'expo-linear-gradient';
import { StatusBar } from 'expo-status-bar';
import QuestionBox from "../Components/questionBox.js"
import {styles} from "./questionStyles.js"
const SCREEN_HEIGHT = Dimensions.get('window').height
const SCREEN_WIDTH = Dimensions.get('window').width
function QuestionScreen({route, navigation}) {
const {players, mode} = route.params;
const [questions, setQuestions]=useState([
{title:"Dare", text:"boooooooooooo"},
{title:"Question", text:"fdoooooo"},
{title:"Question", text:"fdfsfsfsfsfsfsoooooo"},
{title:"Dare", text:"bodfooo"}
]);
const pan = useRef(new Animated.ValueXY()).current;
const [currentIndex, setIndex] =useState(0);
const [times, setTimes]=useState(0)
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderMove: Animated.event([null, { dx: pan.x, dy: pan.y }],{useNativeDriver: false}) ,
onPanResponderRelease: (evt, gestureState) => {
pan.flattenOffset();
},
onPanResponderRelease: (evt, gestureState) => {
if (gestureState.dx > 120 ) {
Animated.spring(pan, {
toValue: { x: SCREEN_WIDTH + 100, y: gestureState.dy },
useNativeDriver: true
}).start(()=>{
setIndex(currentIndex+1)
pan.setValue({ x: 0, y: 0 })
console.log("value of current index is: ".concat(currentIndex) )
})
}
else {
Animated.spring(pan, {
toValue: { x: 0, y: 0 },
useNativeDriver: true,
friction: 4
}).start()
}
}
})
).current;
const rotate = pan.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: ['-10deg', '0deg', '10deg'],
extrapolate: 'clamp'
})
const rotateAndTranslate= {
transform: [{rotate:rotate},{ translateX: pan.x }, { translateY: pan.y }]
}
const nextOpacity = pan.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [0, 0, 1],
extrapolate: 'clamp'
})
const nextCardOpacity = pan.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [1, 0, 1],
extrapolate: 'clamp'
})
const nextCardScale = pan.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [1, 0.8, 1],
extrapolate: 'clamp'
})
const renderQuestions = () => {
return questions.map((item, i) => {
if (i < currentIndex) {
return null
} else if (i === currentIndex) {
return (
<Animated.View
key={i}
style={[rotateAndTranslate,{
height: "100%",
width: "100%",
flex:1 ,
zIndex:1000,
elevation:10,
position:"absolute",
flexDirection:"column"}]}
{...panResponder.panHandlers}
>
<Animated.View
style={{
transform: [{ rotate: "20deg" }],
position: "absolute",
top: 20,
right: 20,
zIndex: 1000,
elevation:100,
opacity:nextOpacity
}}
>
<Text
style={{
borderWidth: 1,
borderColor: "#dd3fac",
color: "#dd3fac",
fontSize: 32,
fontWeight: "800",
padding: 10
}}
>
NEXT >>
</Text>
</Animated.View>
<QuestionBox title={item.title} text={item.text} />
</Animated.View>
);
}else {
return (
<Animated.View
key={i}
style={{
height: "100%",
width: "100%",
flex:1 ,
position:"absolute",
flexDirection:"column",
opacity: nextCardOpacity,
transform: [{ scale: nextCardScale }]
}}
>
<QuestionBox title={item.title} text={item.text} />
</Animated.View>
);
}
}).reverse();
};
return(
<>
<StatusBar
translucent={true} />
<ImageBackground source={vertBackground} style={styles.image}/>
<View style={styles.container}>
<View style={styles.spacer1} />
<View style={{flex:4.6, width:"100%", height:"100%"}}>
{renderQuestions()}
</View>
<View style={styles.spacer1} / >
</View>
</>
);
}
export default QuestionScreen;
Try set the index like this:
setIndex(currentIndexValue => currentIndexValue+1);
I tried making a carousel by watching a tutorial but I cannot get it to work for an event driven animation. Instead of animating it just updates the position to new location.
This does not happen if I use only one type of animation for transition, mentioning just one value to transform rotate instead of passing an expression.
what it looks like
what it should look like
const cards = ["tomato", "teal", "pink"]
const alpha = Math.PI/6
const Trans = () => {
const value = React.useRef(new Animated.Value(0)).current
const [toggled, setToggled] = React.useState(false)
const animationFn = () => {
Animated.spring(value, {
toValue: 1,
friction: 10,
useNativeDriver: true
}).start()
setToggled(toggled => !toggled)
}
const rotateOpen = (rotate) => {
return value.interpolate({
inputRange: [0, 1],
outputRange: ['0rad', `${rotate}rad`]
})
}
const rotateClose = (rotate, maxValues) => {
return value.interpolate({
inputRange: [0, 1],
outputRange: [`${maxValues}rad`, `${rotate}rad`]
})
}
return(
<>
{cards.map((card, index) => {
const rotate = toggled ? (index - 1) * alpha : 0
const maxValues = (index-1) * alpha
return (
<Animated.View key={card} style={{transform: [
{translateY: -50},
{translateX: -100},
{rotate: !toggled ? rotateOpen(rotate) : rotateClose(rotate, maxValues) },
{translateX: 100},
], borderRadius: 15, position: 'absolute', backgroundColor: card, height: 100, width: 200}} />
)
})}
<View style={{paddingTop: 100}}>
<TouchableOpacity onPress={() => { animationFn() }}>
<Text style={{fontSize: 30}}> Animate </Text>
</TouchableOpacity>
</View>
</>
)
}
Your interpolation values shouldn't change between the open and close functions. The animation library knows that when you go from 0 to 1, you're rotating the block "out" and then when you go from 1 back to 0, you're applying the same interpolation in reverse
so this code appears to work correctly for me:
const Trans = () => {
const value = React.useRef(new Animated.Value(0)).current;
const [toggled, setToggled] = React.useState(false);
useEffect(() => {
Animated.spring(value, {
toValue: toggled ? 0 : 1,
friction: 10,
useNativeDriver: false,
}).start();
}, [toggled, value]);
return (
<>
{cards.map((card, index) => {
const rotate = (index - 1) * alpha;
return (
<Animated.View
key={card}
style={{
transform: [
{ translateY: -50 },
{ translateX: -100 },
{
rotate: value.interpolate({
inputRange: [0, 1],
outputRange: ['0rad', `${rotate}rad`],
}),
},
{ translateX: 100 },
],
borderRadius: 15,
position: 'absolute',
backgroundColor: card,
height: 100,
width: 200,
}}
/>
);
})}
<View style={{ paddingTop: 100 }}>
<TouchableOpacity
onPress={() => {
setToggled(!toggled);
}}>
<Text style={{ fontSize: 30 }}> Animate </Text>
</TouchableOpacity>
</View>
</>
);
};