Animated element display not updated after position change - react-native

I'm fairly new to React-Native, so it's very likely I'm missing some core concepts.
I want to create a draggable element and be able to move it back to its original position.
The first part is ok, but when I try to update the position, it looks like it works (because when I click again, the element goes back to its original position), but the view isn't updated.
I tried calling setState and forceUpdate but it doesn't update the view.
Do you guys have any idea why ?
Here is a demo of what I have so far :
import React from 'react';
import {Button, StyleSheet, PanResponder, View, Animated} from 'react-native';
export default class Scene extends React.Component {
constructor(props) {
super(props)
const rectanglePosition = new Animated.ValueXY({ x: 0, y: 0 })
const rectanglePanResponder = this.createPanResponder();
this.state = {
rectanglePosition,
rectanglePanResponder
}
}
createPanResponder = () => {
return PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (event, gesture) => {
this.state.rectanglePosition.setValue({ x: gesture.dx, y: gesture.dy });
},
onPanResponderRelease: () => {
this.state.rectanglePosition.flattenOffset();
},
onPanResponderGrant: () => {
this.state.rectanglePosition.setOffset({
x: this.state.rectanglePosition.x._value,
y: this.state.rectanglePosition.y._value
});
}
});
}
resetPosition = () => {
const newPosition = new Animated.ValueXY({ x: 0, y: 0 })
this.setState({ rectanglePosition: newPosition }) // I thought updating the state triggered a re-render
this.forceUpdate() // doesn't work either
}
getRectanglePositionStyles = () => {
return {
top: this.state.rectanglePosition.y._value,
left: this.state.rectanglePosition.x._value,
transform: this.state.rectanglePosition.getTranslateTransform()
}
}
render() {
return (
<View style={styles.container}>
<Animated.View
style={[styles.rectangle, this.getRectanglePositionStyles()]}
{...this.state.rectanglePanResponder.panHandlers}>
</Animated.View>
<View style={styles.footer}>
<Button title="Reset" onPress={this.resetPosition}></Button>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
borderColor: 'red',
backgroundColor: '#F5FCFF',
},
footer: {
borderWidth: 1,
width: '100%',
position: 'absolute',
bottom: 0,
left: 0,
backgroundColor: 'blue',
},
rectangle: {
position: 'absolute',
height: 50,
width: 50,
backgroundColor: 'red'
}
});

If your only intention is to put it on upper left corner:
resetPosition = () => {
this.state.rectanglePosition.setValue({ x: 0, y: 0 });
};
Note! Refer to this snack to see how you do it without a state https://snack.expo.io/#ziyoshams/stack-overflow

Related

ReactNative ScrollView onScroll works fine for IOS but for Android not working

I am trying to make Animated.ScollView to behave like a bottomSheet in Reactnative by using onScroll function in which I am getting contentOffsetY and based on that I make scrollView unmount/drag-down (works fine for IOS). But for Android it is not event triggering onScroll.
import * as React from 'react';
import { StyleSheet, Dimensions, Animated, View } from 'react-native';
const { width, height } = Dimensions.get('window');
import { Icon } from 'react-native-elements';
export const BottomSheet = ({
indicatorMargin,
heightFactor,
children,
gmRef,
dragging = true,
}) => {
const [alignment] = React.useState(new Animated.Value(height));
const [onAnimate, setOnAnimate] = React.useState(false);
const [isOpen, setIsOpen] = React.useState(false);
const scrollRef = React.useRef();
React.useEffect(
() => dragSheetUp(),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const dragSheetUp = React.useCallback(() => {
setOnAnimate(true);
setIsOpen(true);
Animated.timing(alignment, {
toValue: 0,
duration: 500,
useNativeDriver: false,
}).start(() => setOnAnimate(false));
scrollRef?.current?.scrollTo({
y: 0,
animated: true,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const dragSheetDown = React.useCallback(() => {
setOnAnimate(true);
Animated.timing(alignment, {
toValue: height,
duration: 500,
useNativeDriver: false,
}).start(() => {
setOnAnimate(false);
setIsOpen(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React.useImperativeHandle(
gmRef,
() => ({
open: () => dragSheetUp(),
close: () => dragSheetDown(),
}),
[dragSheetDown, dragSheetUp]
);
const actionSheetBottomStyle = {
top: alignment,
marginTop: height * (heightFactor - 1),
};
const gestureHandler = (e) => {
if (onAnimate) return;
if (e.nativeEvent.contentOffset.y < -40 && dragging) {
dragSheetDown();
}
};
const calculatedHeight = height * (heightFactor - 1) - 100;
return (
<Animated.ScrollView
ref={scrollRef}
style={[styles.bottomSheetContainer, actionSheetBottomStyle]}
scrollEventThrottle={12}
showsVerticalScrollIndicator={false}
onScroll={(e) => {
gestureHandler(e);
}}
>
{isOpen && dragging ? (
<View
style={styles.closeBtn}
onStartShouldSetResponder={() => {
dragSheetDown();
}}
>
<Icon name="chevron-down" type="feather" color="black" size={28} />
</View>
) : (
<View
style={[
styles.indicator,
{
marginBottom: indicatorMargin ? indicatorMargin : 10,
},
]}
/>
)}
{children}
<View style={[styles.bottomSpace, { height: calculatedHeight }]} />
</Animated.ScrollView>
);
};
const styles = StyleSheet.create({
bottomSheetContainer: {
backgroundColor: '#fff',
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: height / 1.2,
width: width / 1,
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
paddingVertical: 10,
},
indicator: {
width: 30,
borderTopColor: 'black',
borderRadius: 15,
borderTopWidth: 4,
alignSelf: 'center',
},
bottomSpace: {
backgroundColor: '#fff',
},
closeBtn: {
width: 40,
alignSelf: 'center',
},
});
(Note: I want to handle onScroll even when we are at the top of ScrollView and it works fine for IOS but not for android)

Multiple drag and drop simultaneous in React Native

Hello i'm creating a game in react native and i'm stuck because i wan't both players can drag and drop horizontaly an element in same time on the same phone.
I have two components like that:
export class Player1 extends Component{
constructor(props){
super(props);
this.state = {
pan : new Animated.ValueXY()
};
}
componentWillMount(){
this.panResponder = PanResponder.create({
onMoveShouldSetResponderCapture : () => true,
onMoveShouldSetPanResponderCapture : () => true,
onPanResponderGrant : (e, gestureState) => {
this.state.pan.setOffset({x: this.state.pan.x._value, y: this.state.pan.y._value});
this.state.pan.setValue({x: 0, y: 0});
},
onPanResponderMove : Animated.event([null,{
dx : this.state.pan.x,
}]),
onPanResponderRelease: (e, {vx, vy}) => {
}
});
}
render(){
return (
<View style={styles.mainContainer}>
{this.renderDraggable()}
</View>
);
}
renderDraggable(){
return (
<View style={styles.draggableContainer}>
<Animated.View
style={[this.state.pan.getLayout(), styles.triangle]}
{...this.panResponder.panHandlers} >
</Animated.View>
</View>
);
}
}
And in my screen i call my components like that:
export default function HomeScreen() {
return (
<View>
<Player1></Player1>
<Player2></Player2>
</View>
);
}
Thanks for your help
I found a solution, i used react-native-gesture-handle like in the directory doubleDraggable of the example: https://kmagiera.github.io/react-native-gesture-handler/docs/example.html
My Code:
import React, { Component } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import {
PanGestureHandler,
ScrollView,
State,
} from 'react-native-gesture-handler';
export class Players extends Component {
constructor(props) {
super(props);
this._translateX = new Animated.Value(0);
this._translateY = new Animated.Value(0);
this._lastOffset = { x: 0, y: 0 };
this._onGestureEvent = Animated.event(
[
{
nativeEvent: {
translationX: this._translateX,
},
},
],
);
}
_onHandlerStateChange = event => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastOffset.x += event.nativeEvent.translationX;
this._translateX.setOffset(this._lastOffset.x);
this._translateX.setValue(0);
this._translateY.setOffset(this._lastOffset.y);
this._translateY.setValue(0);
}
};
render() {
return (
<PanGestureHandler
{...this.props}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
<Animated.View
style={[
styles.box,
{
transform: [
{ translateX: this._translateX },
{ translateY: this._translateY },
],
},
this.props.boxStyle,
]}
/>
</PanGestureHandler>
);
}
}
export default class Example extends Component {
render() {
return (
<View style={styles.scrollView}>
<DraggableBox />
</View>
);
}
}
const styles = StyleSheet.create({
scrollView: {
flex: 1,
},
box: {
position: 'absolute',
width: 0,
height: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderLeftWidth: 25,
borderRightWidth: 25,
borderBottomWidth: 50,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
},
});
And Screen:
<View styles={styles.container}>
<Players boxStyle={styles.player1}></Players>
<Players boxStyle={styles.player2}></Players>
</View>
I have been searching for something similar endlessly for a few days but I couldn't find these demos that react-native-gesture-handler provides. Thanks a lot for posting this here #Lillian Pacaud. Here is the link for several of their demos including the draggable component: https://snack.expo.dev/#adamgrzybowski/react-native-gesture-handler-demo
If you need any simultaneous presses/gesture/drags/etc... your best bet is to use react-native-gesture-handler because the native implementation of all touch/gesture-based components in react native don't allow for simultaneous interactions with each especially for Android.
I made a functional component that does the same thing as the accepted answer. Just pass whatever you want to be draggable as a child under the component. It can handle simultaneous drags as well like the accepted answer on both iOS and Android.
Example of using the draggable component:
import React from 'react';
import { View } from 'react-native';
import { DraggableTest } from '../components/test';
export default function Screen() {
return (
<View style={{ flex: 1 }}>
<DraggableTest>
<View
style={{ width: 150, height: 150, backgroundColor: 'lime' }}
/>
</DraggableTest>
</View>
);
}
The draggable component:
import React, { useRef } from 'react';
import { Animated, StyleSheet } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
export function DraggableTest({ children }) {
const pan = useRef(new Animated.ValueXY()).current;
const lastOffset = useRef({ x: 0, y: 0 }).current;
const onGestureEvent = Animated.event(
[{ nativeEvent: { translationX: pan.x, translationY: pan.y } }],
{ useNativeDriver: false },
);
const onHandlerStateChange = event => {
if (event.nativeEvent.oldState == State.ACTIVE) {
lastOffset.x += event.nativeEvent.translationX;
lastOffset.y += event.nativeEvent.translationY;
pan.setOffset({ x: lastOffset.x, y: lastOffset.y });
pan.setValue({ x: 0, y: 0 });
}
};
return (
<PanGestureHandler
onGestureEvent={onGestureEvent}
onHandlerStateChange={onHandlerStateChange}>
<Animated.View style={[pan.getLayout(), styles.animatedView]}>
{children}
</Animated.View>
</PanGestureHandler>
);
}
const styles = StyleSheet.create({
animatedView: {
position: 'absolute',
},
});

Glitches with draggable components using react native - implemented using Animated and PanResponder

Drawing inspiration from this question, I have implemented two draggable components as children in a view. The parent view is as follows:
import React, { Component } from "react";
import { Text, View, StyleSheet, Dimensions } from "react-native";
import Draggable from "./Draggable";
export default class FloorPlan extends Component {
constructor() {
super();
const { width, height } = Dimensions.get("window");
this.separatorPosition = (height * 2) / 3;
}
render() {
return (
<View style={styles.mainContainer}>
<View style={[...styles.dropZone, { height: this.separatorPosition }]}>
<Text style={styles.text}>Floor plan</Text>
</View>
<View style={styles.drawerSeparator} />
<View style={styles.row}>
<Draggable />
<Draggable />
</View>
</View>
);
}
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1
},
drawerSeparator: {
backgroundColor: "grey",
height: 20
},
row: {
flexDirection: "row",
marginTop: 25
},
dropZone: {
height: 700,
backgroundColor: "#f4fffe"
},
text: {
marginTop: 25,
marginLeft: 5,
marginRight: 5,
textAlign: "center",
color: "grey",
fontSize: 20
}
});
And the draggable component is implemented as follows:
import React, { Component } from "react";
import {
StyleSheet,
View,
PanResponder,
Animated,
Text,
Dimensions
} from "react-native";
export default class Draggable extends Component {
constructor() {
super();
const { width, height } = Dimensions.get("window");
this.separatorPosition = (height * 2) / 3;
this.state = {
pan: new Animated.ValueXY(),
circleColor: "skyblue"
};
this.currentPanValue = { x: 0, y: 0 };
this.panListener = this.state.pan.addListener(
value => (this.currentPanValue = value)
);
}
componentWillMount() {
this.state.pan.removeListener(this.panListener);
}
componentWillMount() {
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => false,
onMoveShouldSetPanResponder: (evt, gestureState) => false,
onMoveShouldSetPanResponderCapture: (evt, gesture) => {
return true;
},
onPanResponderGrant: (e, gestureState) => {
this.setState({ circleColor: "red" });
},
onPanResponderMove: (event, gesture) => {
Animated.event([
null,
{
dx: this.state.pan.x,
dy: this.state.pan.y
}
])(event, gesture);
},
onPanResponderRelease: (event, gesture) => {
this.setState({ circleColor: "skyblue" });
if (gesture.moveY < this.separatorPosition) {
this.state.pan.setOffset({
x: this.currentPanValue.x,
y: this.currentPanValue.y
});
this.state.pan.setValue({ x: 0, y: 0 });
// this.state.pan.flattenOffset();
} else {
//Return icon to start position
this.state.pan.flattenOffset();
Animated.timing(this.state.pan, {
toValue: {
x: 0,
y: 0
},
useNativeDriver: true,
duration: 200
}).start();
}
}
});
}
render() {
const panStyle = {
transform: this.state.pan.getTranslateTransform()
};
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[
panStyle,
styles.circle,
{ backgroundColor: this.state.circleColor }
]}
/>
);
}
}
let CIRCLE_RADIUS = 30;
let styles = StyleSheet.create({
circle: {
backgroundColor: "skyblue",
width: CIRCLE_RADIUS * 2,
height: CIRCLE_RADIUS * 2,
borderRadius: CIRCLE_RADIUS,
marginLeft: 25
}
});
A draggable component can be dragged onto the FloorPlan and it's location will be remembered for the next pan action. However, sometimes during dragging, a glitch occurs and the icon jumps at the beginning of the pan or completetely disappears.
What could be the problem? I am developing using React Native 0.55.2 and testing using a device running Android 7.

Absolute positioning not working inside FlatList

I am trying to create a tinder like swipe deck animation. I am using FlatList to render the images. To stack the images one above the other, I am using 'absolute' positioning. The issue with this is the images are not getting rendered and all I am seeing is a blank screen. I am not sure whether there is something wrong with using positioning inside FlatList.
The reason I went with FlatList is my stack will contain around 200 to 300 images. I think I can implement this without using FlatList by just rendering the images as batches (say render 10 images at once and then render the next 10 and so on).
I want to know whether it is possible to implement this using FlatList.
NOTE: The issue is in android and I am not sure about iOS
import React from "react";
import {
StyleSheet,
Text,
View,
FlatList,
Image,
Dimensions,
Animated,
PanResponder
} from "react-native";
const DATA = [
{
id: 1,
text: "Card #1",
uri: "http://www.fluxdigital.co/wp-content/uploads/2015/04/Unsplash.jpg"
},
{
id: 2,
text: "Card #2",
uri: "https://images.pexels.com/photos/247932/pexels-photo-247932.jpeg?h=350"
},
{
id: 3,
text: "Card #3",
uri: "http://www.fluxdigital.co/wp-content/uploads/2015/04/Unsplash.jpg"
}
];
const { width, height } = Dimensions.get("window");
export default class App extends React.Component {
constructor(props) {
super(props);
this.position = new Animated.ValueXY();
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (event, gestureState) => true,
onPanResponderMove: (event, gestureState) => {},
onPanResponderRelease: (event, gestureState) => {}
});
this.state = {
currentIndex: 0
};
}
extractKey = (item, index) => item.id.toString();
renderCard = ({ item }) => {
return (
<View style={styles.imageContainer}>
<Image
source={{ uri: item.uri }}
resizeMode="cover"
style={styles.image}
/>
</View>
);
};
render() {
return (
<FlatList
contentContainerStle={styles.container}
data={DATA}
keyExtractor={this.extractKey}
renderItem={this.renderCard}
scrollEnabled={true}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
imageContainer: {
width,
height: height - 20,
backgroundColor: "red",
padding: 10
position: 'absolute'
},
image: {
flex: 1,
width: null,
height: null,
borderRadius: 20
}
});
i create tinder swiper using following
might be help you
import React from "react";
import {
StyleSheet,
Text,
View,
FlatList,
Image,
Dimensions,
Animated,
PanResponder
} from "react-native";
const SCREEN_HEIGHT = Dimensions.get('window').height
const SCREEN_WIDTH = Dimensions.get('window').width
const DATA = [
{
id: 1,
text: "Card #1",
uri: "https://images.pexels.com/photos/247932/pexels-photo-247932.jpeg?h=350"
},
{
id: 2,
text: "Card #2",
uri: "http://www.fluxdigital.co/wp-content/uploads/2015/04/Unsplash.jpg"
},
{
id: 3,
text: "Card #3",
uri: "http://www.fluxdigital.co/wp-content/uploads/2015/04/Unsplash.jpg"
}
];
export default class App extends React.Component {
constructor(props) {
super(props);
this.position = new Animated.ValueXY();
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (event, gestureState) => true,
onPanResponderMove: (event, gestureState) => {},
onPanResponderRelease: (event, gestureState) => {}
});
this.state = {
currentIndex: 0
};
}
extractKey = (item, index) => item.id.toString();
renderUsers = () => {
return DATA.map((item,i)=>{
return(
<View style={ { height: SCREEN_HEIGHT - 120, width: SCREEN_WIDTH, padding: 10, position: 'absolute' }}>
<Image
style={{ flex: 1, height: null, width: null, resizeMode: 'cover', borderRadius: 20 }}
source={{uri:item.uri}} />
</View>
)
})
}
render() {
return (
<View style={{marginTop:24,flex:1,backgroundColor:'#eee'}}>
{
this.renderUsers()
}
</View>
);
}
}

How to pass onPress in a map function to a custom component?

I have a icons.js which consist of an array of objects:
const icons =[
{ name: 'Camera', image: <Icon name='device-camera' size={70} />, onPress: pickSingleWithCamera},
{ name: 'Earth', image: <SimpleLineIcons name='camera' size={70} />, onPress: 'bye' }
]
picksinglWithCamera is a function:
const pickSingleWithCamera = () => {
ImagePicker.openCamera({
cropping: true,
width: 500,
height: 500,
}).then(image => {
console.log('received image', image);
this.setState({
image: {uri: image.path, width: image.width, height: image.height},
images: null
});
}).catch(e => alert(e));
}
Now I have the main component in main.js which imports both the icons.js and title.js files.
The title component:
<Title
text={ focused ? focused.name : '' }
data={this.cells}
top={(SIZE + GUTTER) * 2}
visibility={this.text}
/>
What I am trying to do here is whenever user presses the text (name attribute of array icons), the respective function of it is called.
Unfortunately I am unsuccessful doing that, that's why I came onto SO to ask.
import React, { Component } from 'react'
import { Animated, Text, TouchableOpacity, Alert, View } from 'react-native'
import { HIDDEN, VISIBLE } from './animation-state'
export default class Title extends Component {
constructor(props) {
super(props)
this.state = {}
}
action(text, top, visibility, data) {
return data.map((cell, i) => {
return (
<Animated.View
key={i}
style={{
position: 'absolute',
top,
left: 0,
right: 0,
opacity: visibility.interpolate({
inputRange: [HIDDEN, VISIBLE],
outputRange: [0, 1],
}),
backgroundColor: 'transparent',
transform: [
{
translateY: visibility.interpolate({
inputRange: [HIDDEN, VISIBLE],
outputRange: [100, 0],
}),
},
],
}}
>
<TouchableOpacity onPress={() => cell.onPress}>
<Text
style={{
fontSize: 40,
fontFamily: 'GillSans-SemiBold',
textAlign: 'center',
}}
>
{text}
</Text>
</TouchableOpacity>
</Animated.View>
)
})
}
render() {
const { text, top, visibility, data } = this.props
return (
<View>
{this.action(text, top, visibility, data)}
</View>
)
}
}
Arrow functions have immediate returns, in your case what you are doing translates to the following:
onPress={function(){return cell.onPress;}}
This will do nothing because you are not executing the function, you have to tell the function to execute
onPress={() => cell.onPress()}
Another solution would be to just assign the function without arrow functions, this has the downside that onPress won't be binded to the component
onPress={cell.onPress}