I am trying to modify the standard RN panResponder example (which just moved a box around, into changing the width of a view. Here's the standard example code:
import React, { useRef } from 'react';
import { Animated, View, StyleSheet, PanResponder, Text } from 'react-native';
const Example = () => {
const pan = useRef(new Animated.ValueXY()).current;
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
pan.setOffset({
x: pan.x._value,
y: pan.y._value,
});
},
onPanResponderMove: Animated.event([null, { dx: pan.x, dy: pan.y }]),
onPanResponderRelease: () => {
pan.flattenOffset();
},
})
).current;
return (
<View style={styles.container}>
<Text style={styles.titleText}>Drag this box!</Text>
<Animated.View
style={{
transform: [{ translateX: pan.x }, { translateY: pan.y }],
}}
{...panResponder.panHandlers}
>
<View style={styles.box} />
</Animated.View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
titleText: {
fontSize: 14,
lineHeight: 24,
fontWeight: 'bold',
},
box: {
height: 150,
width: 150,
backgroundColor: 'blue',
borderRadius: 5,
},
});
export default Example;
I tried changing the transform: key to width, but I must admit I don't really know what I am doing, this example uses both Animated.View as well as panResponder, and I don't have experience with either.
Today I want to make an animation to be determined by the user. In my example is when the user moves the square close and hit the red rectangle for example to write something in console. But i don't know how to calculate distance to the rectangle and I can't determine when the square touched the rectangle? So my code sa far -
import React from "react";
import { StyleSheet, View } from "react-native";
import {
GestureHandlerRootView,
PanGestureHandler,
PanGestureHandlerGestureEvent,
} from "react-native-gesture-handler";
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming,
} from "react-native-reanimated";
const SIZE = 100.0;
const CIRCLE_RADIUS = SIZE * 2;
type ContextType = {
translateX: number;
translateY: number;
size: number;
};
export default function App() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const size = useSharedValue(100);
const panGestureEvent = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
ContextType
>({
onStart: (event, context) => {
context.translateX = translateX.value;
context.translateY = translateY.value;
size.value = 122;
},
onActive: (event, context) => {
translateX.value = event.translationX + context.translateX;
translateY.value = event.translationY + context.translateY;
},
onEnd: () => {
const distance = Math.sqrt(translateX.value ** 2 + translateY.value ** 2);
size.value = 100;
if (distance < CIRCLE_RADIUS + SIZE / 2) {
translateX.value = withSpring(30);
translateY.value = withSpring(50);
}
},
});
const rStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: translateX.value,
},
{
translateY: translateY.value,
},
],
width: withTiming(size.value, { duration: 160 }),
height: withTiming(size.value, { duration: 160 }),
};
});
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={styles.container}>
<View style={styles.circle}>
<PanGestureHandler onGestureEvent={panGestureEvent}>
<Animated.View
style={[
{
width: size.value,
height: size.value,
backgroundColor: "rgba(0, 0, 256, 0.5)",
borderRadius: 20,
},
rStyle,
]}
/>
</PanGestureHandler>
</View>
<View
style={{ width: 30, height: 50, backgroundColor: "red", top: 80 }}
></View>
</View>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
circle: {
width: CIRCLE_RADIUS * 2,
height: CIRCLE_RADIUS * 2,
alignItems: "center",
justifyContent: "center",
borderRadius: CIRCLE_RADIUS,
borderWidth: 5,
borderColor: "rgba(0, 0, 256, 0.5)",
},
});
I am trying to create an animation where the red bar will move along y-axis in an infinite & reverse animation. For some reason, I can not figure out and not able to achieve the desired behavior. Any assistance will be greatly appreciated.
import Animated, {
useAnimatedStyle,
useSharedValue,
interpolate,
withTiming,
withRepeat,
Extrapolate,
} from 'react-native-reanimated';
export default CustomMarker = () => {
const bar = useSharedValue(0);
const barStyle = useAnimatedStyle(() => {
const yValue = interpolate(bar.value, [0, 1], [0, 225]);
return {
transform: [{ translateY: yValue }],
};
});
useEffect(() => {
bar.value = withRepeat(withTiming(1, { duration: 800 }), -1, true);
});
return (
<View
style={{
backgroundColor: COLORS.darkOverlay,
height: 540,
alignItems: 'center',
justifyContent: 'center',
}}>
<View style={styles.rectangleStyle}>
<Animated.View style={[styles.redBarStyle, barStyle]} />
</View>
</View>
);
};
const styles = Stylesheet.create({
rectangleStyle: {
height: 230,
width: 400,
alignItems: 'center',
justifyContent: 'center',
},
redBarStyle: {
position: 'absolute',
top: 0,
height: 10,
width: 225,
backgroundColor: 'red',
borderRadius: 5,
},
})
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')}}
/>
I want to show a cancel button, on the focus TextInput animation.
I did the following code, but a cancel button does not display and follow the box when focused. It's only shown after the animation end.
And when cancel button displayed, it is not on the same line with textinput.
How do I fix this?
const { width } = Dimensions.get('window');
const PADDING = 16;
const SEARCH_FULL_WIDTH = width - PADDING * 2; //search_width when unfocused
const SEARCH_SHRINK_WIDTH = width - PADDING - 90; //search_width when focused
class Search extends React.Component {
constructor(props: IProps) {
super(props);
this.state = {
inputLength: new Animated.Value(SEARCH_FULL_WIDTH),
searchBarFocused: false,
}
}
private onFocus = () => {
Animated.timing(this.state.inputLength, {
toValue: SEARCH_SHRINK_WIDTH,
duration: 250,
}).start(() => this.setState({ searchBarFocused: true }));
}
private onBlur = () => {
Animated.timing(this.state.inputLength, {
toValue: SEARCH_FULL_WIDTH,
duration: 250,
}).start(() => this.setState({ searchBarFocused: false }));
}
<View style={styles.searchContainer}>
<Animated.View style={[
styles.search,
{
width: this.state.inputLength,
position: 'absolute',
left: 16,
alignSelf: 'center'
},
searchBarFocused === true ? undefined : { justifyContent: 'center' }
]}>
<Image source={searchIcon} style={styles.image} />
<TextInput
style={styles.searchInput}
....
onBlur={this.onBlur}
onFocus={this.onFocus}
/>
</Animated.View>
{searchBarFocused &&
<Touchable style={styles.cancelSearch} onPress={this.cancelSearch}>
<Text style={styles.cancelSearchText}>Cancel</Text>
</Touchable>
}
</View>
const styles = StyleSheet.create({
searchContainer: {
flexDirection: 'row',
height: 72,
borderBottomColor: SOLITUDE_COLOR,
},
search: {
flex: 1,
flexDirection: 'row',
height: 40,
borderRadius: 6,
},
cancelSearch: {
marginHorizontal: 16,
textAlign: 'center',
justifyContent: 'center'
}
});
gif: when unfocus and focused
Here is a slightly modified version of your code.
import React from "react";
import {
Dimensions,
View,
Animated,
TextInput,
TouchableOpacity,
StyleSheet,
} from "react-native";
const { width } = Dimensions.get("window");
const PADDING = 16;
const SEARCH_FULL_WIDTH = width - PADDING * 2; //search_width when unfocused
const SEARCH_SHRINK_WIDTH = width - PADDING - 90; //search_width when focused
const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputLength: new Animated.Value(SEARCH_FULL_WIDTH),
cancelPosition: new Animated.Value(0),
opacity: new Animated.Value(0),
searchBarFocused: false
};
}
onFocus = () => {
Animated.parallel([
Animated.timing(this.state.inputLength, {
toValue: SEARCH_SHRINK_WIDTH,
duration: 250
}),
Animated.timing(this.state.cancelPosition, {
toValue: 16,
duration: 400
}),
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 250
})
]).start();
};
onBlur = () => {
Animated.parallel([
Animated.timing(this.state.inputLength, {
toValue: SEARCH_FULL_WIDTH,
duration: 250
}),
Animated.timing(this.state.cancelPosition, {
toValue: 0,
duration: 250
}),
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 250
})
]).start();
};
render() {
const { searchBarFocused } = this.state;
return (
<View style={styles.searchContainer}>
<Animated.View
style={[
styles.search,
{
width: this.state.inputLength,
position: "absolute",
left: 16,
alignSelf: "center"
},
searchBarFocused === true ? undefined : { justifyContent: "center" }
]}
>
<TextInput
style={styles.searchInput}
onBlur={this.onBlur}
onFocus={this.onFocus}
placeholder="Type something"
/>
</Animated.View>
<AnimatedTouchable
style={[styles.cancelSearch, { right: this.state.cancelPosition }]}
onPress={() => null}
>
<Animated.Text
style={[styles.cancelSearchText, { opacity: this.state.opacity }]}
>
Cancel
</Animated.Text>
</AnimatedTouchable>
</View>
);
}
}
const styles = StyleSheet.create({
searchContainer: {
flexDirection: "row",
height: 72,
borderBottomColor: "#00000033",
paddingTop: 100
},
search: {
flex: 1,
flexDirection: "row",
height: 40,
borderRadius: 6,
backgroundColor: "red"
},
cancelSearch: {
position: "absolute",
marginHorizontal: 16,
textAlign: "center",
justifyContent: "center",
alignSelf: "center"
}
});
You're setting searchBarFocused only after your animation completes. Since the cancel button is conditionally rendered based on searchBarFocused, it only appears at the end of the animation.