Multiple drag and drop simultaneous in React Native - 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',
},
});

Related

dont share state between reusable component react native

Im creating a reusable Text component with a onFocus and onBlur animation, but when I put this in a form; the focus and blur event triggers the animation for every Input in the form... can you help me to avoid this behavior?
Here is the code if you need more details, but I think this is very clear
import React, { Component } from 'react';
import { TextInput, View, Text, Animated, StyleSheet } from 'react-native';
const animatedPlaceholder = new Animated.Value(30);
class Input extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
isFocused: false,
textLength: 0
};
}
secureTextEntry = this.props.secureTextEntry || false;
autoCapitalize = this.props.autoCapitalize || 'sentences';
keyboardType = this.props.keyboardType || 'default';
focus = () => {
this.setState({isFocused: true});
Animated.timing(animatedPlaceholder, {
toValue: 0,
duration: 300
}).start();
}
blur = () => {
this.setState({isFocused: false});
Animated.timing(animatedPlaceholder, {
toValue: 30,
duration: 300
}).start();
}
render() {
return(
<View {...this.props}>
<Animated.Text style={
this.state.isFocused ? styles.usedValue : styles.emptyValue
} > {this.props.placeholder} </Animated.Text>
<TextInput
onFocus={this.focus}
onBlur={this.blur}
autoCapitalize={this.autoCapitalize}
secureTextEntry={this.secureTextEntry}
keyboardType={this.keyboardType}
style={
styles.textInput
}
/>
</View>
);
}
}
export default Input;
I didn't quite got your question, but i created a component which animates the place holder when its focused, animated back if value is empty,
check this snack example https://snack.expo.io/#ashwith00/frowning-cookie
Code
import React, { Component } from 'react';
import { TextInput, View, Text, Animated, StyleSheet } from 'react-native';
export default class Input extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
isFocused: false,
textLength: 0,
};
this.animatedPlaceholder = new Animated.Value(0);
}
secureTextEntry = this.props.secureTextEntry || false;
autoCapitalize = this.props.autoCapitalize || 'sentences';
keyboardType = this.props.keyboardType || 'default';
focus = () => {
Animated.timing(this.animatedPlaceholder, {
toValue: -40,
duration: 300,
}).start();
};
blur = () => {
if (!this.props.value) {
Animated.timing(this.animatedPlaceholder, {
toValue: 0,
duration: 300,
}).start();
}
};
render() {
const {value, onChangeText} = this.props;
return (
<View style={[ {
justifyContent: 'center'
}]}>
<Animated.Text
style={{
position: 'absolute',
transform: [{translateY: this.animatedPlaceholder}]
}}>
{' '}
{this.props.placeholder}{' '}
</Animated.Text>
<TextInput
value={value}
onChangeText={onChangeText}
onFocus={this.focus}
onBlur={this.blur}
autoCapitalize={this.autoCapitalize}
secureTextEntry={this.secureTextEntry}
keyboardType={this.keyboardType}
style={styles.textInput}
/>
</View>
);
}
}
const styles = StyleSheet.create({
usedValue: {} ,
emptyValue: {},
textInput: {
alignSelf: 'stretch',
height: 50,
borderWidth: 0.4
}
})

React Native - Need to hide/show header with Animation on scroll regardless of scroll position

Currently I have this code:
import React, { Component, PureComponent } from 'react';
import { View, FlatList, RefreshControl, StatusBar, Animated, ScrollView, PanResponder } from 'react-native';
import { heightPercentageToDP as hp, widthPercentageToDP as wp } from 'react-native-responsive-screen';
import { connect } from 'react-redux';
import i18n from 'i18n-js';
import Post from '../components/Post';
import AppHeader from '../components/AppHeader';
class Posts extends PureComponent {
constructor(props) {
super(props);
this.state = {
curY: new Animated.Value(0),
height: 0
};
}
render() {
const { postsReducer } = this.props,
{ container } = styles;
return (
<View style={container}>
<Animated.View
style={{
transform: [{
translateY: this.state.curY.interpolate({
inputRange: [0, 1],
outputRange: [0, -1]
})
}], position: 'absolute', top: 0, width: wp('100%'), marginTop: StatusBar.currentHeight
}}
onLayout={({ nativeEvent }) => this.setState({ height: nativeEvent.layout.height })}
>
<AppHeader />
</Animated.View>
<Animated.ScrollView
scrollEventThrottle={16}
refreshControl={
<RefreshControl
onRefresh={this._onRefresh}
refreshing={refreshing}
tintColor='#5E81F4'
colors={["blue", "lightblue"]}
/>
}
contentContainerStyle={{ marginTop: this.state.height }}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.curY } } }],
{ useNativeDriver: true }
)}
>
{postsReducer.map((item, index) => (
<Post
postId={item._id}
userId={item.owner}
owner={item.owner}
title={item.title}
avatar={item.picture}
userName={item.userName}
updatedAt={item.updatedAt}
image={item.photo.split(",")}
description={item.description}
age={item.age}
time={item.time}
date={item.date}
location={item.location}
city={item.city}
commentCounter={item.commentCounter}
key={index}
/>
))}
</Animated.ScrollView>
</View>
);
}
}
const styles = {
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-start',
}
};
const mapStateToProps = ({ registrationReducer, postsReducer, usersReducer, }) => (
{
registrationReducer,
postsReducer,
usersReducer
}
);
export default connect(mapStateToProps, { setPosts })(Posts);
When I start scrolling down header hides, and when I scroll up again appears. But header appears only when I get to the beginning of list.
And I need such solution: when I'm for example in the middle of the list and start scroll up - header should appear, and when started scroll down it hides again. So it should be independently from position.
It work this way in Facebook mobile app for example.
I found solution. Just need to use Animated.diffClamp.
Here is the final code. Maybe will be useful for someone:
import React, { Component, PureComponent } from 'react';
import { View, FlatList, RefreshControl, StatusBar, Animated, ScrollView, PanResponder } from 'react-native';
import { heightPercentageToDP as hp, widthPercentageToDP as wp } from 'react-native-responsive-screen';
import { connect } from 'react-redux';
import i18n from 'i18n-js';
import Post from '../components/Post';
import AppHeader from '../components/AppHeader';
class Posts extends PureComponent {
constructor(props) {
super(props);
this.state = {
curY: new Animated.Value(0),
height: 0
};
}
render() {
const { postsReducer } = this.props,
{ container } = styles;
const headerDistance = Animated.diffClamp(this.state.curY, 0, 60).interpolate({
inputRange: [0, 1],
outputRange: [0, -1]
});
return (
<View style={container}>
<Animated.ScrollView
scrollEventThrottle={16}
refreshControl={
<RefreshControl
onRefresh={this._onRefresh}
refreshing={refreshing}
tintColor='#5E81F4'
colors={["blue", "lightblue"]}
/>
}
contentContainerStyle={{ marginTop: this.state.height }}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.curY } } }],
{ useNativeDriver: true }
)}
>
{postsReducer.map((item, index) => (
<Post
postId={item._id}
userId={item.owner}
owner={item.owner}
title={item.title}
avatar={item.picture}
userName={item.userName}
updatedAt={item.updatedAt}
image={item.photo.split(",")}
description={item.description}
age={item.age}
time={item.time}
date={item.date}
location={item.location}
city={item.city}
commentCounter={item.commentCounter}
key={index}
/>
))}
</Animated.ScrollView>
<Animated.View
style={{
transform: [{
translateY: headerDistance
}], position: 'absolute', top: 0, width: wp('100%'), marginTop: StatusBar.currentHeight
}}
onLayout={({ nativeEvent }) => this.setState({ height: nativeEvent.layout.height })}
>
<AppHeader />
</Animated.View>
</View>
);
}
}
const styles = {
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-start',
}
};
const mapStateToProps = ({ registrationReducer, postsReducer, usersReducer, }) => (
{
registrationReducer,
postsReducer,
usersReducer
}
);
export default connect(mapStateToProps, { setPosts })(Posts);

React Native - Animated View is shaking on Android

I'm trying to create a Animated header similar to this one:
https://raw.githubusercontent.com/maphongba008/react-native-animated-header/master/demo/android-gif.gif
After accomplish this, I realise that works fine on IOS but on Android, when I scroll the header and the body start shaking
https://user-images.githubusercontent.com/1559822/29156651-a664489e-7dc0-11e7-93f8-eb9878b47924.gif
Here is the code:
import React, { Component } from "react";
import { Animated } from "react-native";
import { query } from "src/api";
import PostList from "src/components/PostList/PostList";
import ContentSwitcher from "src/components/ContentSwitcher/ContentSwitcher";
import User from "src/components/User/User";
import {
HeaderCardHeight,
AnnouncementCardHeight
} from "src/screens/ScreenDimensions";
class Explore extends Component {
constructor(props) {
super();
this.HEADER_MAX_HEIGHT = HeaderCardHeight;
this.HEADER_MIN_HEIGHT = 120;
this.HEADER_SCROLL_DISTANCE =
this.HEADER_MAX_HEIGHT - this.HEADER_MIN_HEIGHT;
this.state = {
scrollY: new Animated.Value(0),
selectedType: "news"
};
this.headerHeight = this.state.scrollY.interpolate({
inputRange: [0, this.HEADER_SCROLL_DISTANCE],
outputRange: [this.HEADER_MAX_HEIGHT, this.HEADER_MIN_HEIGHT],
extrapolate: "clamp"
});
this.queries = {
news: query.newsList(props.user),
events: query.eventsList(props.user)
};
}
render() {
const { selectedType, scrollY } = this.state;
return (
<>
<Animated.View
style={{
height: this.headerHeight
}}
>
<ContentSwitcher
type={selectedType}
contentSelector={a => this.setState({ selectedType: a })}
/>
</Animated.View>
<PostList
style={{ position: "absolute", top: 0 }}
margin={this.headerHeight}
scrollEventThrottle={0}
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: scrollY } } }
])}
layout={selectedType}
query={this.queries[selectedType]}
/>
</>
);
}
}
export default User(Explore);
Any idea to solve this issue?
Thank you
This is caused by the bouncing effect.
You can try to add:
alwaysBounceVertical={false}
bounces={false}
<PostList
style={{ position: "absolute", top: 0 }}
margin={this.headerHeight}
scrollEventThrottle={0}
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: scrollY } } }
])}
layout={selectedType}
query={this.queries[selectedType]}
alwaysBounceVertical={false}
bounces={false}
/>

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.

React Native: `Image` does show the `uri` source

Image doesn't show the uri source.
Image can show the require('') source.
I don't know the reason.
import React, {Component} from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
ListView,
Image,
Dimensions,
ScrollView,
} from 'react-native';
const deviceWidth = Dimensions.get('window').width;
export default class AwesomeProject extends Component {
// Initialize the hardcoded data
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
imageArray1: [
{
url: 'http://insights.ubuntu.com/wp-content/uploads/8063/react-native-logo.jpg',
description: 'React Logo'
},
],
imageArray2: [
{
url: 'http://insights.ubuntu.com/wp-content/uploads/8063/react-native-logo.jpg',
description: 'React Logo'
},
{
url: 'http://insights.ubuntu.com/wp-content/uploads/8063/react-native-logo.jpg',
description: 'React Logo'
},
],
};
}
renderRow = (tempArray) => {
const imageStyleNumber = tempArray.length;
let imageStyleString;
switch (imageStyleNumber) {
case 1:
imageStyleString = `imgView1`;
break;
default:
imageStyleString = `imgView9`;
}
return tempArray.map((item, index) => {
if(index > 8){
return ;
}
return (
<View key={index} style={styles[imageStyleString]}>
<Image style={styles.imgIstyle} source={{uri: item.url}}/>
<Text style={styles.imgTDesc}>{item.description}</Text>
</View>
)
})
}
render() {
return (
<ScrollView style={styles.container}>
<View style={styles.rowVImageBox}>
{this.renderRow(this.state.imageArray1)}
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#ebf0f2',
},
rowVImageBox: {
flexDirection:'row',
flexWrap:'wrap',
width: 300,
height: 300,
marginBottom:100,
},
rowVImageBox2: {
flexDirection:'row',
flexWrap:'wrap',
width: 300,
height: 300,
marginBottom:100,
backgroundColor:'blue',
},
imgView1: {
width:290,
height:290,
marginRight:5,
marginBottom:5,
},
imgView2: {
width:140,
height:290,
marginRight:5,
marginBottom:5,
},
imgIstyle:{
width:'100%',
height:'70%',
backgroundColor:'yellow',
},
imgTDesc: {
flex:2,
backgroundColor:'white'
}
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
Do you run it well?
I have asked my friend. He doesn't know the reason too.
modify the Info.plist.
iOS can load the http resource.