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
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)
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',
},
});
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.
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>
);
}
}
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}