Flipping card, and sliding off/on screen in React Native - react-native

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);

Related

Why all the Icons of my custom bottom tab bar navigator are moving at the same time?

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>
);
}

State doesn't seem to update within Animated.spring()

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);

React Native - Stack Navigator - Passing stack to API

I recently started trying to make my first app, have some experience in web design and with JS.
I'm trying to use a Stack Navigator to move around my screens, but I'm struggling to pass the Stack to an API that handles the onPress input, or I believe thats the issue.
I have my home screen, in the centre is a circle button api I installed, and the main.js for the API contains the onPress event. However, whatever I try to plug into the button, it just errors out, so I must be missing something rudimentary. I did try following a few guides and troubleshooting myself but I just can't figure out why.
App.js
import { StatusBar } from 'expo-status-bar';
import React, {Component} from 'react';
import { Button, Image, StyleSheet, Text, View, Dimensions, Alert, ImageBackground } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator, Screen } from '#react-navigation/stack';
import CircleButton from 'react-native-circle-button';
import { LinearGradient } from 'expo-linear-gradient';
import logomain from './assets/LogoMain.png';
import logobackground from './assets/1024.png';
import { HomeScreen } from './Home'
import { CreateScreen } from './Create'
import { ReadScreen } from './Read'
import { SavedScreen } from './Saved'
import { SettingsScreen } from './Settings'
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
headerMode="none">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Create" component={CreateScreen} />
<Stack.Screen name="Read" component={ReadScreen} />
<Stack.Screen name="Saved" component={SavedScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
Home.js
import { StatusBar } from 'expo-status-bar';
import React, { Component } from 'react';
import { Button, Image, StyleSheet, Text, View, Dimensions, Alert, ImageBackground } from 'react-native';
import CircleButton from 'react-native-circle-button';
import { LinearGradient } from 'expo-linear-gradient';
import logomain from './assets/LogoMain.png';
import logobackground from './assets/1024.png';
export function HomeScreen(props: any) {
return (
<View style={styles.container}>
<LinearGradient
colors={['rgba(0,212,255,0.4) 0%', 'rgba(112,128,144,1) 50%', 'rgba(30,40,70,1) 100%']}
start={{ x: 0, y: 0 }}
end={{x: 1, y: 1 }}
paddingLeft={(Dimensions.get('window').width/2)-1}
paddingRight={(Dimensions.get('window').width/2)-1}
>
<View style={styles.containerlogo}>
<Image style={styles.containerbackground} source={logobackground} />
<Image style={styles.logomain} source={logomain} />
</View>
<View style={styles.buttoncontainer}>
<CircleButton
size={Dimensions.get('window').width/3.5}
primaryColor={'#000'}
secondaryColor={'rgba(30,40,70,1)'}
/>
</View>
<StatusBar style="auto" />
</LinearGradient>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
//backgroundColor: '#7488a1',
alignItems: 'center',
justifyContent: 'center',
},
containerbackground: {
width: Dimensions.get('window').width,
resizeMode: 'cover',
alignItems: 'center',
justifyContent: 'center',
opacity: 0.25,
zIndex: 0,
position: 'absolute',
top: -46,
},
containerlogo: {
flex: 2,
alignItems: 'center',
justifyContent: 'center',
paddingTop: 50,
zIndex: 1,
},
logomain: {
height: Dimensions.get('window').width-50,
//backgroundColor: 'rgba(150,150,150,0.7)',
//borderWidth: 7,
//borderColor: 'rgba(150,150,150,0.7)',
width: Dimensions.get('window').width-50,
resizeMode: 'contain',
position: 'absolute',
top: 20,
zIndex: 2,
},
buttoncontainer: {
flex: 1,
alignItems: 'center',
paddingTop: 50,
paddingBottom: 150,
opacity: 1,
zIndex: 1,
},
});
Read.js (placeholder page)
import { StatusBar } from 'expo-status-bar';
import React, {Component} from 'react';
export function ReadScreen(props: any) {
return (
<View style={styles.container}>
<LinearGradient
colors={['rgba(0,212,255,0.4) 0%', 'rgba(112,128,144,1) 50%', 'rgba(30,40,70,1) 100%']}
start={{ x: 0, y: 0 }}
end={{x: 1, y: 1 }}
paddingLeft={(Dimensions.get('window').width/2)-1}
paddingRight={(Dimensions.get('window').width/2)-1}
>
<StatusBar style="auto" />
</LinearGradient>
</View>
);
}
API main.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
View,
TouchableOpacity,
StyleSheet,
Image,
Animated,
Easing,
} from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import iconAdd from './images/add.png';
import iconSetting from './images/setting.png';
import iconScan from './images/view.png';
import iconPerson from './images/person.png';
import iconAttach from './images/list.png';
class CircleButton extends Component {
constructor() {
super();
this.state = {
isClicked: false,
};
this.rotateAnimated = new Animated.Value(0);
this.scaleAnimated = new Animated.Value(0);
this.bringToBackAnimated = new Animated.Value(0);
this.bringToLeftAnimated = new Animated.Value(0);
this.bringToRightAnimated = new Animated.Value(0);
this.bringToTopAnimated = new Animated.Value(0);
this.bringToBottomAnimated = new Animated.Value(0);
this.fadeAnimated = new Animated.Value(0);
this._buttonCenter = this._buttonCenter.bind(this);
this._buttonTop = this._buttonTop.bind(this);
this._buttonRight = this._buttonRight.bind(this);
this._buttonBottom = this._buttonBottom.bind(this);
this._buttonLeft = this._buttonLeft.bind(this);
}
createAnimation(obj, toValue, duration, easing) {
return Animated.timing(obj, {
toValue,
duration,
easing: Easing.linear,
useNativeDriver: false,
}).start();
}
startAnimation() {
this.createAnimation(this.rotateAnimated, 1, 200);
this.createAnimation(this.scaleAnimated, 1, 200);
this.createAnimation(this.bringToBackAnimated, 1, 150);
this.createAnimation(this.bringToLeftAnimated, 1, 200);
this.createAnimation(this.bringToRightAnimated, 1, 200);
this.createAnimation(this.bringToTopAnimated, 1, 200);
this.createAnimation(this.bringToBottomAnimated, 1, 200);
this.createAnimation(this.fadeAnimated, 1, 200);
}
endAnimation() {
this.createAnimation(this.rotateAnimated, 2, 200);
this.createAnimation(this.scaleAnimated, 0, 200);
this.createAnimation(this.bringToBackAnimated, 0, 2000);
this.createAnimation(this.bringToLeftAnimated, 0, 200);
this.createAnimation(this.bringToRightAnimated, 0, 200);
this.createAnimation(this.bringToTopAnimated, 0, 200);
this.createAnimation(this.bringToBottomAnimated, 0, 200);
this.createAnimation(this.fadeAnimated, 0, 200);
}
_buttonCenter() {
this.startAnimation();
this.setState({isClicked: !this.state.isClicked});
if (this.state.isClicked) this.endAnimation();
}
_buttonTop() {
this.setState({isClicked: !this.state.isClicked});
this.endAnimation();
this.props.onPressButtonTop();
}
_buttonRight() {
this.setState({isClicked: !this.state.isClicked});
this.endAnimation();
this.props.onPressButtonRight();
}
_buttonBottom() {
this.setState({isClicked: !this.state.isClicked});
this.endAnimation();
this.props.onPressButtonBottom();
}
_buttonLeft() {
this.setState({isClicked: !this.state.isClicked});
this.endAnimation();
this.props.onPressButtonLeft();
}
render() {
const {size, primaryColor, secondaryColor} = this.props;
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
},
buttonWrapper: {
right: size * 2 - 10,
},
buttonLeft: {
alignItems: 'center',
justifyContent: 'center',
width: size - 5,
height: size - 5,
borderRadius: 360,
},
buttonRight: {
alignItems: 'center',
justifyContent: 'center',
width: size - 5,
height: size - 5,
borderRadius: 360,
},
buttonCenter: {
alignItems: 'center',
justifyContent: 'center',
width: size,
height: size,
borderRadius: 360,
backgroundColor: primaryColor,
},
buttonTop: {
right: -size * 2 + 7,
alignItems: 'center',
justifyContent: 'center',
width: size - 5,
height: size - 5,
borderRadius: 360,
},
buttonBottom: {
right: size - 7,
alignItems: 'center',
justifyContent: 'center',
width: size - 5,
height: size - 5,
borderRadius: 360,
},
text: {
color: '#EECE69',
fontWeight: 'bold',
letterSpacing: 1,
},
centerImage: {
width: size - 5,
height: size - 5,
},
childImage: {
width: size - 15,
height: size - 15,
},
circle: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 360,
backgroundColor: secondaryColor,
},
});
const rotateMe = this.rotateAnimated.interpolate({
inputRange: [0, 1, 2],
outputRange: ['0deg', '45deg', '0deg'],
});
const scaleMe = this.scaleAnimated.interpolate({
inputRange: [0, 1],
outputRange: [size, size * 3],
});
const bringToBack = this.bringToBackAnimated.interpolate({
inputRange: [0, 1],
outputRange: [99, -1],
});
const bringMeToLeft = this.bringToLeftAnimated.interpolate({
inputRange: [0, 1],
outputRange: [size, 0],
});
const bringMeToRight = this.bringToRightAnimated.interpolate({
inputRange: [0, 1],
outputRange: [0, size],
});
const bringMeToTop = this.bringToTopAnimated.interpolate({
inputRange: [0, 1],
outputRange: [0, -size + 2],
});
const bringMeToBottom = this.bringToBottomAnimated.interpolate({
inputRange: [0, 1],
outputRange: [0, size - 2],
});
const fadeInOut = this.fadeAnimated.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
});
return (
<View style={styles.container}>
<Animated.View
style={[styles.circle, {width: scaleMe, height: scaleMe}]}>
<Animated.View style={{top: bringMeToTop}}>
<TouchableOpacity
onPress={this._buttonTop}
activeOpacity={1}
style={styles.buttonTop}>
<Animated.Image
source={this.props.iconButtonTop}
style={[styles.childImage, {opacity: fadeInOut}]}
/>
</TouchableOpacity>
</Animated.View>
<Animated.View style={{left: bringMeToLeft}}>
<TouchableOpacity
onPress={this._buttonLeft}
activeOpacity={1}
style={styles.buttonLeft}>
<Animated.Image
source={this.props.iconButtonLeft}
style={[styles.childImage, {opacity: fadeInOut}]}
/>
</TouchableOpacity>
</Animated.View>
<Animated.View style={{left: bringMeToRight}}>
<TouchableOpacity
onPress={this._buttonRight}
activeOpacity={1}
style={styles.buttonRight}>
<Animated.Image
source={this.props.iconButtonRight}
style={[styles.childImage, {opacity: fadeInOut}]}
/>
</TouchableOpacity>
</Animated.View>
<Animated.View style={{top: bringMeToBottom}}>
<TouchableOpacity
onPress={this._buttonBottom}
activeOpacity={1}
style={styles.buttonBottom}>
<Animated.Image
source={this.props.iconButtonBottom}
style={[styles.childImage, {opacity: fadeInOut}]}
/>
</TouchableOpacity>
</Animated.View>
<Animated.View
style={[styles.buttonWrapper, {transform: [{rotate: rotateMe}]}]}>
<TouchableOpacity
onPress={this._buttonCenter}
activeOpacity={1}
style={styles.buttonCenter}>
<Animated.Image
source={this.props.iconButtonCenter}
style={styles.centerImage}
/>
</TouchableOpacity>
</Animated.View>
</Animated.View>
</View>
);
}
}
CircleButton.defaultProps = {
size: 40,
onPressButtonTop: () => {navigation.navigate('Create')},
onPressButtonRight: () => {navigation.navigate('Read')},
onPressButtonBottom: () => {navigation.navigate('Saved')},
onPressButtonLeft: () => {navigation.navigate('Settings')},
iconButtonCenter: iconAdd,
iconButtonTop: iconAdd,
iconButtonRight: iconScan,
iconButtonBottom: iconSetting,
iconButtonLeft: iconAttach,
primaryColor: '#41727E',
secondaryColor: '#459186',
};
CircleButton.propTypes = {
size: PropTypes.number,
onPressButtonTop: PropTypes.func,
onPressButtonRight: PropTypes.func,
onPressButtonBottom: PropTypes.func,
onPressButtonLeft: PropTypes.func,
iconButtonCenter: PropTypes.number,
iconButtonTop: PropTypes.number,
iconButtonRight: PropTypes.number,
iconButtonBottom: PropTypes.number,
iconButtonLeft: PropTypes.number,
primaryColor: PropTypes.string,
secondaryColor: PropTypes.string,
};
module.exports = CircleButton;
Believed problem area in API main.js:
onPressButtonTop: () => {navigation.navigate('Create')},
onPressButtonRight: () => {navigation.navigate('Read')},
onPressButtonBottom: () => {navigation.navigate('Saved')},
onPressButtonLeft: () => {navigation.navigate('Settings')},
Solved: Navigation prop isn't passed into the component directly, therefore the onPressButton props need to be declared in the function body instead
<CircleButton
size={Dimensions.get('window').width/3.5}
primaryColor={'#000'}
secondaryColor={'rgba(30,40,70,1)'}
onPressButtonTop={() => {navigation.navigate('Create')}}
onPressButtonRight={() => {navigation.navigate('Read')}}
onPressButtonBottom={() => {navigation.navigate('Saved')}}
onPressButtonLeft={() => {navigation.navigate('Settings')}}
/>

React Native : Bad performance animating the height of a view using react-native-reanimated

Animating the height of a view when scrolling through a list is very slow and choppy,
it's work fine for IOS, but not for Android :
import * as React from "react";
import { StyleSheet, Text } from "react-native";
import Animated from "react-native-reanimated";
const { Value, View, ScrollView, interpolate, Extrapolate } = Animated;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "black"
},
listView: {
flex: 1
}
});
const IMAGE_MIN_HEIGHT = 300;
export default () => {
const animation = new Value(0);
const height = interpolate(animation, {
inputRange: [0, 125],
outputRange: [IMAGE_MIN_HEIGHT, 125],
extrapolate: Extrapolate.CLAMP
});
return (
<View style={styles.container}>
<View
style={{
backgroundColor: "red",
height: height,
width: "100%"
}}
></View>
<ScrollView
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
y: animation
}
}
}
])}
scrollEventThrottle={1}
style={[styles.listView]}
>
{[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].map((elem, index) => (
<View
style={{ width: "100%", height: 100, marginBottom: 20 }}
key={index}
>
<Text style={{ color: "white", fontSize: 30 }}>{elem}</Text>
</View>
))}
</ScrollView>
</View>
);
};
Is there anything that can be done to make it smoother?
the solution is to set Header position to absolute, i don't know why but it's working fine :
import * as React from "react";
import { StyleSheet, Text, Platform } from "react-native";
import Animated from "react-native-reanimated";
const { Value, View, ScrollView, interpolate, Extrapolate } = Animated;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "black"
},
listView: {
flex: 1
}
});
const IMAGE_MIN_HEIGHT = 300;
export default () => {
const animation = new Value(0);
const height = interpolate(animation, {
inputRange: [0, 125],
outputRange: [IMAGE_MIN_HEIGHT, 125],
extrapolate: Extrapolate.CLAMP
});
return (
<View style={styles.container}>
<ScrollView
contentContainerStyle={{ paddingTop: IMAGE_MIN_HEIGHT }}
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
y: animation
}
}
}
])}
scrollEventThrottle={1}
style={[styles.listView]}
>
{[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].map((elem, index) => (
<View
style={{ width: "100%", height: 100, marginBottom: 20 }}
key={index}
>
<Text style={{ color: "white", fontSize: 30 }}>{elem}</Text>
</View>
))}
</ScrollView>
<View
style={{
backgroundColor: "red",
height: height,
width: "100%",
position: "absolute",
top: Platform.OS == "ios" ? 20 : 0,
left: 0,
right: 0
}}
></View>
</View>
);
};
Try using NativeDriver for Android.
Also, for better performance, you should use FlatList or SectionList here instead of ScrollView.
<Animated.ScrollView
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
y: animation
}
}
}
],
{ useNativeDriver: true } // Add this line
)}
scrollEventThrottle={1}
style={[styles.listView]}
>
//
</Animated.ScrollView>
For more details, please see this

animate move x and y with soft move react native

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>
);
};