Interpolate function from Animated return is not a function
I am following this, but I am currently doing it as function component
(chapter : "Designing the Tinder Cards Movement"):
https://www.instamobile.io/react-native-controls/react-native-swipe-cards-tinder/
I don't understand why interpolate return "is not a function". I spent a lot of time on it and I didn't find something.
There is the card component I have written :
import React, { useState, useEffect } from 'react'
import { Text, View, Animated, Image, StyleSheet, PanResponder } from 'react-native'
function Card(props){
const { screen_height, screen_width, image, Data, currentIndex, Index } = props;
const [pan, setPan] = useState(new Animated.ValueXY());
const [panResponder, setPanResponder] = useState(PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderMove: (evt, gestureState) => {
setPan({ x: gestureState.dx, y: gestureState.dy });
},
onPanResponderRelease: (evt, gestureState) => {
}
}));
let value = Math.round(pan.x);
if(value !== NaN || value !== undefined){
console.log(value.interpolate({ inputRange: [0, 1], outputRange: [150, 0]}));
}
if (Index < currentIndex) { return null }
else if(Index == currentIndex){
return(
<Animated.View style={[{ transform:[{ translateX: pan.x }, { translateY: pan.y }] },{ height:screen_height - 120, width:screen_width, padding:10, position:'absolute' }]} {...panResponder.panHandlers}>
<Image style={[{
flex:1,
height:null,
width:null,
resizeMode:"cover",
borderRadius:20,
}]} source={image.uri}/>
</Animated.View>)
} else {
return(
<Animated.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={image.uri}/>
</Animated.View>)
}
}
export default Card;
setPan is wrongly used.pan is now a object not an Animated.Value anymore.You should call setValue or using Animated.event on the animated value Instead.
onPanResponderMove: Animated.event([
null,
{
dx: pan.x, // x,y are Animated.Value
dy: pan.y,
},
]),
Related
Can't RN do style changes using JS?
...
const PR = PanResponder.create({
onStartShouldSetPanResponder: (e, gestureState) => true,
onPanResponderStart: (e, gestureState) => {
console.log("start");
},
onPanResponderMove: (e, gestureState) => {
const dx = Math.abs(gestureState.dx);
**target.current.style.backgroundColor** = `rgba(${dx},${dx / 2},106,1)`;
},
onPanResponderEnd: (e, gestureState) => {
const dx = Math.abs(gestureState.dx);
if (!dx) {
target.current.style.backgroundColor = "green";
}
console.log("End");
},
});
...
As above, it is difficult to change the style on mobile.
Thank you for your reply.
The useState hook was not what I was expecting.
help me..
In the code above it looks like you were storing the style with useRef, which doesnt trigger component updates when its value changes. Here's a useState example
import * as React from 'react';
import { Text, View, StyleSheet, PanResponder } from 'react-native';
import Animated, {
useSharedValue,
interpolateColor,
} from 'react-native-reanimated';
import Constants from 'expo-constants';
export default function App() {
const [style, setStyle] = React.useState({
borderWidth: 1,
width: '100%',
height: 280,
backgroundColor: 'lightgreen',
});
const PR = PanResponder.create({
onStartShouldSetPanResponder: (e, gestureState) => true,
onPanResponderStart: (e, gestureState) => {
console.log('start');
},
onPanResponderMove: (e, gestureState) => {
const dx = Math.abs(gestureState.dx);
console.log(dx);
setStyle((prev) => ({
...prev,
backgroundColor: `rgba(${dx},${dx / 2},106,1)`,
}));
},
onPanResponderEnd: (e, gestureState) => {
const dx = Math.abs(gestureState.dx);
console.log(dx);
if (!dx) {
setStyle((prev) => ({ ...prev, backgroundColor: 'green' }));
}
console.log('End');
},
});
return (
<View style={styles.container}>
<View style={style} {...PR.panHandlers} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
While it works, the best results would come from using Animated.View and some Animated values
Look into this panResponder . It has some example of how to use
PanResponderGestureState
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',
},
});
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 implemented PanResponder in my project but it only works when I touch non touchable elements . When I touch touchable elements like TouchableOpacity, PanReponder does not responds.But when I move my finger on TouchableOpacity PanResponder responds.
Same thing happening for Button also
Please tell me what might be the problem.
Expo Link : https://snack.expo.io/SyYrtq87W
import React, { Component } from 'react';
import { Button, PanResponder, View, StyleSheet,TouchableOpacity } from 'react-native';
import { Constants } from 'expo';
export default class App extends Component {
state = {
show : false
};
_panResponder = {};
componentWillMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => {
alert('clicked')
console.log('clicked')
return true
},
onMoveShouldSetPanResponder: () => {
alert('moved')
console.log('moved')
return true
},
onStartShouldSetPanResponderCapture: () => false,
onMoveShouldSetPanResponderCapture: () => false,
onPanResponderTerminationRequest: () => true,
onShouldBlockNativeResponder: () => false,
});
}
render() {
return (
<View
style={styles.container}
collapsable={false}
{...this._panResponder.panHandlers}>
{/*********************PanResponder does not respond***************************/}
<TouchableOpacity>
<View style={{width:200, height:200,backgroundColor:'red'}}>
</View>
</TouchableOpacity>
<Button
title="Here is a button for some reason"
onPress={() => {}}
/>
{/*****************************************************************************/}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
}
});
I had a similar issue. Basically because you always return true in onStartShouldSetPanResponder and/or onMoveShouldSetPanResponder they will take command over that touch event.
My workaround: Don't consider the touch "yours" (PanResponder's) unless the user moved it a little first by a threshold you set.
const touchThreshold = 20;
const panResponder = PanResponder.create({
onStartShouldSetPanResponder : () => false,
onMoveShouldSetPanResponder : (e, gestureState) => {
const {dx, dy} = gestureState;
return (Math.abs(dx) > touchThreshold) || (Math.abs(dy) > touchThreshold);
},
...
});
Finally It hit me. It was a very silly mistake.
forgot to add alert('clicked') at
onStartShouldSetPanResponderCapture: ()
Now,
onStartShouldSetPanResponderCapture: () => {alert('clicked') ; return false},
Expo Link : https://snack.expo.io/ryWx7lBrb
Now, it takes touch everywhere, including Touchables and Buttons.
import React, { Component } from 'react';
import { Button, PanResponder, View, StyleSheet,TouchableOpacity } from 'react-native';
import { Constants } from 'expo';
export default class App extends Component {
state = {
show : false
};
_panResponder = {};
componentWillMount() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => {
alert('clicked')
return true
},
onMoveShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: () => {alert('clicked') ; return false},
onMoveShouldSetPanResponderCapture: () => false,
onPanResponderTerminationRequest: () => true,
onShouldBlockNativeResponder: () => false,
});
}
render() {
return (
<View
style={styles.container}
collapsable={false}
{...this._panResponder.panHandlers}>
{/*********************PanResponder now responds***************************/}
<TouchableOpacity>
<View style={{width:200, height:200,backgroundColor:'red'}}>
</View>
</TouchableOpacity>
<Button
title="Here is a button for some reason"
onPress={() => {}}
/>
{/*****************************************************************************/}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
}
});
Touchables will not work if you set this
onStartShouldSetPanResponder: () => true
This will entirely take control over the touches. It is always a good option to take control only if the user starts dragging the component.
onMoveShouldSetPanResponder: () => true
Here is an example for PanResponder.
this.panResponder = PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderMove: (event, gesture) => {
if (gesture.dy > 0 && this.state.size._value < width) {
this.state.size.setValue(40 + gesture.dy)
}
},
onPanResponderRelease: () => {
Animated.spring(this.state.size, {
toValue: 40
}).start()
}
})
This is my JSX code.
<Animated.View {...this.panResponder.panHandlers}>
<TouchableWithoutFeedback onPress={this.openImage}>
<Animated.Image style={profileImage}
source={{uri: 'https://example.com/face.jpg'}}/>
</TouchableWithoutFeedback>
</Animated.View>