React Native - panResponder animation not working on iOS - ios

I use a panResponder to create draggable view in my app. It's working fine on android but on iOS, the drag animation stops after moving la little bit.
Vidéo here
Here is my code :
export default class Draggable extends React.Component {
constructor(props) {
super(props);
const { pressDragRelease, pressDragStart, reverse, initPosition } = props;
this.state = {
pan: new Animated.ValueXY({
x: initPosition.dragX,
y: initPosition.dragY
}),
_value: { x: initPosition.dragX, y: initPosition.dragY }
};
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (gesture) => {
console.log("inside panResponder grant");
if (reverse === false) {
this.state.pan.setOffset({x: this.state._value.x,y: this.state._value.y});
this.state.pan.setValue({ x: 0, y: 0 });
} else {
this.state.pan.setValue({ x: gesture.dx, y: gesture.dy });
}
},
onPanResponderMove: Animated.event([
null,
{
dx: this.state.pan.x,
dy: this.state.pan.y
}
]),
//Called on android at the end
onPanResponderRelease: () => {
if (pressDragRelease) {
pressDragRelease({ x: this.state._value.x, y: this.state._value.y });
}
if (reverse === false) this.state.pan.flattenOffset();
else this.reversePosition();
},
//Called on ios at the end
onPanResponderTerminate: () => {
if (pressDragRelease) {
pressDragRelease({ x: this.state._value.x, y: this.state._value.y
});
}
if(reverse === false) {
this.state.pan.flattenOffset();
} else {
this.reversePosition();
}
}
});
}
componentWillMount() {
if (this.props.reverse === false)
this.state.pan.addListener(c => this.setState({ _value: c }));
}
componentWillUnmount() {
this.state.pan.removeAllListeners();
}
reversePosition = () => {
const { initPosition } = this.props;
Animated.spring(this.state.pan, {
toValue: { x: initPosition.dragX, y: initPosition.dragY }
}).start();
};
render() {
return (
<Animated.View
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout()]}
>
{this.props.children}
</Animated.View>
);
}
}
I'm using :
"react-native": "0.57.7"
I tried lots of things related to panResponder, as it works fine on android I guest it's an issue of handler or of the Animated.event in onPanResponderMove handler ?
Any help appreciated, I'm struggling on it for several days ! :)

Also ran into this issue.
Deleted my node modules and pods with a reinstall which was able to resolve it.
I know in the past react-navigation conflicted.
https://github.com/react-navigation/react-navigation/issues/5497
Actually ran into this while using a slider library which was built using panResponder.

Related

BrowserView rendering below fold of BrowserWindow

Running into a really strange quirk and can't figure out what I'm doing wrong:
init.js
const os = require('os');
const path = require('path');
const { app, BrowserView, BrowserWindow, ipcMain } = require('electron');
let win = null;
// Init function
const init = function() {
return new Promise((resolve, reject) => {
try {
// BrowserWindow config
const config = {
backgroundColor: '#1d1e20',
show: false,
webPreferences: {
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js'),
}
};
// Make BrowserWindow frameless (win) or hide titlebar (mac)
if ('win32' === os.platform()) {
config.frame = false;
} else if ('darwin' === os.platform()) {
config.titleBarStyle = 'hidden';
}
// Create BrowserWindow
win = new BrowserWindow(config);
// Add listener for BrowserWindow 'ready-to-show' event
win.once('ready-to-show', () => {
// Set traffic light position (mac)
win.setTrafficLightPosition({ x: 10, y: 27 });
// Show browser window
win.show();
// Open DevTools
win.openDevTools({ mode: "detach" });
resolve();
});
// Load app html
win.loadFile('./app.html').then(() => {
// loaded
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// Add browser view
const addView = function(url) {
return new Promise((resolve, reject) => {
try {
// Get window size
const bounds = win.getSize();
// Create BrowserView
const view = new BrowserView({
backgroundColor: "#ffffff",
webPreferences: {
contextIsolation: true,
enableRemoteModule: false,
//preload: path.join(__dirname, 'preload.js'),
}
});
view.setBounds({
height: 375,
width: 375,
x: 0,
// Set "y" coordinate to 0
// (should be relative to BrowserWindow 0, 0)
y: 0
// Set "y" coordinate to a
// negative integer
//y: -200
// Instead, set "y" to inverse of
// BrowserWindow height
//y: bounds[1] * -1
});
// Load file
view.webContents.loadFile('./new.html').then(() => {
// Add to BrowserWindow
win.addBrowserView(view);
// Open DevTools
view.webContents.openDevTools({ mode: "detach" });
resolve();
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// init when ready
app.whenReady().then(() => {
init().then(() => {
addView().then(() => {
console.log("Everything should be working right");
}).catch((err) => {
console.log("Error loading BrowserView");
console.error(err);
});
}).catch((err) => {
console.log("Error loading BrowserWindow");
console.error(err);
});
});
Expected result: new BrowserView stacked squarely on top of BrowserWindow at defined width and height, positioned at 0, 0.
Actual result: new BrowserView displayed below the fold, with its "y" coordinate relative to bottom of BrowserWindow. To get BrowserView to show at BrowserWindow 0, 0, must set BrowserView "y" coordinate to (BrowserWindowHeight * -1)
Edit: P.S.
package.json
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "init.js",
"scripts": {
"start": "electron ."
},
"author": "",
"license": "",
"devDependencies": {
"electron": "^12.0.1"
}
}
preload.js
(no contents)
app.html
(no contents)
app.js
(no contents)
app.css
(no contents)
new.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test</title>
<style>
html {
margin: 0;
padding: 0;
}
body {
background: #ffffff;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Update #1
Delay call to view.setBounds() by moving inside promise callback of view.loadURL(). No change.
Update #2
Further delay call to view.setBounds() by additionally wrapping in setTimeout() with delay of 1000ms. No change.
Update #3
By adding the following, the BrowserView does snap to correct 0, 0 position within parent BrowserWindow, but only upon resize event:
win.on('will-resize', (event, newBounds) => {
win.getBrowserViews().forEach((view) => {
view.setBounds({
height: newBounds.height,
width: newBounds.width,
x: 0,
y: 0
});
});
});
Update #4
Replaced BrowserWindow.loadFile() and BrowserView.loadFile() calls with .loadURL() using google.com and stackoverflow.com, respectively, to eliminate the possibility of it being something to do with my local files. No change.
Update #5
By modifying the init.js as follows, it works as expected, however it's not pretty as it "snaps" into position. (Edit: to clarify, BrowserView defined with solid background which I expect to be displayed until .loadURL() completes, unless I've somehow misunderstood the purpose of the backgroundColor property) (Edit: I've just realized that backgroundColor is not a valid property of the BrowserView)
const os = require('os');
const path = require('path');
const { app, BrowserView, BrowserWindow, ipcMain } = require('electron');
let win = null;
// Init function
const init = function() {
return new Promise((resolve, reject) => {
try {
// BrowserWindow config
const config = {
backgroundColor: '#1d1e20',
show: false,
webPreferences: {
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js'),
}
};
// Make BrowserWindow frameless (win) or hide titlebar (mac)
if ('win32' === os.platform()) {
config.frame = false;
} else if ('darwin' === os.platform()) {
config.titleBarStyle = 'hidden';
}
// Create BrowserWindow
win = new BrowserWindow(config);
// Add listener for BrowserWindow 'ready-to-show' event
win.once('ready-to-show', () => {
// Set traffic light position (mac)
win.setTrafficLightPosition({ x: 10, y: 27 });
// Show browser window
win.show();
// Open DevTools
win.openDevTools({ mode: "detach" });
});
// Add listener for BrowserWindow 'show' event
win.once('show', () => {
resolve();
});
win.on('will-resize', (event, newBounds) => {
win.getBrowserViews().forEach((view) => {
view.setBounds({ height: newBounds.height, width: newBounds.width, x: 0, y: 0 });
});
});
// Load app html
win.loadURL("https://google.com").then(() => {
// loaded
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// Add browser view
const addView = function(url) {
return new Promise((resolve, reject) => {
try {
// Create BrowserView
const view = new BrowserView({
backgroundColor: "#edeef0",
webPreferences: {
contextIsolation: true,
enableRemoteModule: false
}
});
// Load file
view.webContents.loadURL('https://stackoverflow.com').then(() => {
setTimeout(() => {
// Get window size
const bounds = win.getSize();
// Set BrowserView bounds
view.setBounds({ height: bounds[1], width: bounds[0], x: 0, y: 0 });
}, 1);
// Add to BrowserWindow
win.addBrowserView(view);
// Open DevTools
view.webContents.openDevTools({ mode: "detach" });
resolve();
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// init when ready
app.whenReady().then(() => {
init().then(() => {
addView().then(() => {
console.log("Everything should be working right");
}).catch((err) => {
console.log("Error loading BrowserView");
console.error(err);
});
}).catch((err) => {
console.log("Error loading BrowserWindow");
console.error(err);
});
});
Update #6
I've come to the conclusion that in current version of Electron (12.0.1), a BrowserView is rendered with equivalent of CSS position: relative, but almost immediately thereafter, changed to equivalent of CSS position: absolute. Calling BrowserView.setBounds({ x: 0, y: 0 }) immediately upon creation of the BrowserView, the BrowserView is actually displayed at { x: 0, y: (BrowserWindow height) }, whereas calling BrowserView.setBounds({ x: 0, y: 0 }) within a setTimeout() after a 1ms delay, the BrowserView is actually displayed at { x: 0, y: 0}. I'm not sure why this is, but it seems the fix (tested only on macOS 10.14.6) is as follows:
const view = new BrowserView(...);
const size = win.getSize();
view.setBounds({ height: size[1], width: size[0], x: 0, y: size[0] * -1 });
and, on the BrowserWindow:
win.on('will-resize', (event, newBounds) => {
win.getBrowserViews().forEach((view) => {
view.setBounds({ height: newBounds.height, width: newBounds.width, x: 0, y: 0 });
});
});
Update #7
This still feels very unwieldy and I suspect this is not the intended behavior and I still may be doing something wrong. I'm going to leave this question open and hope that someone has a better solution than what I've found.
Update #8
Finally figured out that it was my sloppy implementation. After RTFM, adding some promises, and doing things in the proper order, there is no problem.
For educational purposes, I broke it down and reversed my steps to find the source of the problem so I know for future reference. For anyone else who may ever find this and have the same problem -- you must call BrowserWindow.addBrowserView() BEFORE calling BrowserView.setBounds(). That was the problem.
I was calling BrowserView.setBounds() immediately after creating the BrowserView -- at which point it was not added to the BrowserWindow yet, hence why the coords were off. I didn't call BrowserWindow.addBrowserView() until BrowserView.loadFile() resolved.
Must call BrowserWindow.addBrowserView() BEFORE calling BrowserView.setBounds() -- (see Update #8)

Admob reward ads don't work on ios on Expo App

On App.js I have initialized AdMobRewarded as following:
if (Platform.OS === 'ios') {
AdMobRewarded.setAdUnitID('ca-app-pub-xxx/xxx');
} else {
AdMobRewarded.setAdUnitID('ca-app-pub-xxx/xxx');
}
And here is the class:
export default class App extends React.Component {
state = {
fontsLoaded: false,
};
render() {
const { fontsLoaded } = this.state;
if (!fontsLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => this.setState({ fontsLoaded: true })}
/>
);
}
return (
<Provider store={store}>
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
<CommonComponents />
</Provider>
);
}
}
Inside the CommonComponents I have put the listener for AdMobRewarded:
useEffect(() => {
AdMobRewarded.addEventListener('rewardedVideoDidRewardUser', () => {
setState({
hintModalVisible: true,
adIsLoading: false,
mainMenuVisible: false,
});
});
return () => {
AdMobRewarded.removeAllListeners();
};
}, []);
setState is actually not React setState, it's a redux action I have implemented:
const setStateAction = (obj, sceneName) => {
const type = sceneName ? `${sceneName}_SET_STATE` : 'SET_STATE';
return { ...obj, type };
};
Without the rewardedVideoDidRewardUser listener, calling setState does open the Modal and everything is fine.
hintModalVisible is used for Modal isVisible prop, which opens and closes the Modal.
On Android everything works as expected, but there is a strange behavior on iOS. The ad shows for a second and automatically closes, and the Hint Modal doesn't open.
Here is the function that requests and shows the ad. It is present in all screens of the app:
showHint = async () => {
const { setState } = this.props;
try {
setState({
mainMenuVisible: false,
});
await AdMobRewarded.requestAdAsync();
await AdMobRewarded.showAdAsync();
} catch (e) {
setState({
hintModalVisible: true,
mainMenuVisible: false,
});
}
};
It's an open source project, so you can the code here
The problem was with React Native Modal.
setState({
hintModalVisible: true,
adIsLoading: false,
mainMenuVisible: false,
});
This block of code should have closed the main menu modal and open the hint modal. But it seems that on IOS you cannot do this simultaneously. So this is how I handled it.
useEffect(() => {
AdMobRewarded.addEventListener('rewardedVideoDidRewardUser', () => {
setTimeout(() => {
setState({
hintModalVisible: true,
});
}, 1000);
});
return () => {
AdMobRewarded.removeAllListeners();
};
}, []);
And took the closing of main menu modal in the ad requesting function:
const requestHint = useCallback(async () => {
try {
setState({
mainMenuVisible: false,
});
await AdMobRewarded.requestAdAsync();
await AdMobRewarded.showAdAsync();
} catch (e) {
setState({
mainMenuVisible: false,
});
setTimeout(() => {
setState({
hintModalVisible: true,
});
}, 500);
}
}, [setState, hintModalVisible]);
So this is not concerned to the Admob Rewarded. It is more a React Native Modal bug. https://github.com/react-native-community/react-native-modal/issues/192

How to navigate page immediately after rendering animation?

I currently have a loading screen that renders an animation. After doing so I would like it to immediately, based on firebase.auth().onAuthStateChange, navigate the user to a specific page.
I have already implemented the animation and part of the logic. I just need the ability to navigate immediately after the first render/animation has completed.
class LoadingScreen extends Component {
state = {
opacity: new Animated.Value(0),
}
onLoad = () => {
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1500,
delay: 1000,
useNativeDriver: true,
}).start();
}
render() {
return (
<Animated.Image
onLoad={this.onLoad}
{...this.props}
style={[
{
opacity: this.state.opacity,
transform: [
{
scale: this.state.opacity.interpolate({
inputRange: [0, 1],
outputRange: [0.85, 1],
})
},
],
},
this.props.style,
]}
/>
);
}
}
export default class App extends Component{
render()
{
return (
<View style={styles.container}>
<LoadingScreen
style={styles.image}
source= {require('../assets/images/logo.png')}
/>
</View>
)
}
checkIfLoggedIn = () => {
firebase.auth().onAuthStateChanged((user)=>
{
if(user)
{
this.props.navigation.navigate('Login');
}
else
{
this.props.navigation.navigate('Signup');
}
})
}
}
To do something on the end of the animation, you should add a callback to the start() function, so:
Pass your checkIfLoggedIn function as a prop to LoadingScreen component
<LoadingScreen
style={styles.image}
source= {require('../assets/images/logo.png')}
onAnimationEnd={this.checkIfLoggedIn}
/>
Use the function passed as a prop for the animation callback
onLoad = () => {
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1500,
delay: 1000,
useNativeDriver: true,
}).start(() => this.props.onAnimationEnd());
}

enable dragging of element in a ScrollView after long press

I have implemented drag n drop list with panResponder and ScrollView. I want to be able to scroll the list even when I touch the item. Problem is that the item moves when I do the gesture to scroll. Of course I also want to be able to move the item but now it has the same gesture as scroll. I want to overcome it by enabling dragging the element only after it was long pressed (1,5 sec). How to implement it? I thought to use Touchable as an element with onPressIn / onPressOut just like described here: http://browniefed.com/blog/react-native-press-and-hold-button-actions/
and somehow to enable panResponder after the time period, but I don't know how to enable it programmatically.
Right now this is my code for element in the list:
class AccountItem extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
zIndex: 0,
}
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderGrant: (e, gestureState) => {
this.setState({ zIndex: 100 });
this.props.disableScroll();
},
onPanResponderMove: Animated.event([null, {
dx: this.state.pan.x,
dy: this.state.pan.y,
}]),
onPanResponderRelease: (e, gesture) => {
this.props.submitNewPositions();
Animated.spring(
this.state.pan,
{toValue:{ x:0, y:0 }}
).start();
this.setState({ zIndex: 0 });
this.props.enableScroll();
}
})
}
meassureMyComponent = (event) => {
const { setElementPosition } = this.props;
let posY = event.nativeEvent.layout.y;
setElementPosition(posY);
}
render() {
const {name, index, onChangeText, onRemoveAccount} = this.props;
return (
<Animated.View
style={[this.state.pan.getLayout(), styles.container, {zIndex: this.state.zIndex}]}
{...this.panResponder.panHandlers}
onLayout={this.meassureMyComponent}
>
some other components...
</Animated.View>
)
}
}
export default AccountItem;
I met the same issue with you. My solution is to define 2 different panResponder handler for onLongPress and normal behaviour.
_onLongPressPanResponder(){
return PanResponder.create({
onPanResponderTerminationRequest: () => false,
onStartShouldSetPanResponderCapture: () => true,
onPanResponderMove: Animated.event([
null, {dx: this.state.pan.x, dy: this.state.pan.y},
]),
onPanResponderRelease: (e, {vx, vy}) => {
this.state.pan.flattenOffset()
Animated.spring(this.state.pan, { //This will make the draggable card back to its original position
toValue: 0
}).start();
this.setState({panResponder: undefined}) //Clear panResponder when user release on long press
}
})
}
_normalPanResponder(){
return PanResponder.create({
onPanResponderTerminationRequest: () => false,
onStartShouldSetPanResponderCapture: () => 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})
this.longPressTimer=setTimeout(this._onLongPress, 400) // this is where you trigger the onlongpress panResponder handler
},
onPanResponderRelease: (e, {vx, vy}) => {
if (!this.state.panResponder) {
clearTimeout(this.longPressTimer); // clean the timeout handler
}
}
})
}
Define your _onLongPress function:
_onLongPress(){
// you can add some animation effect here as wll
this.setState({panResponder: this._onLongPressPanResponder()})
}
Define your constructor:
constructor(props){
super(props)
this.state = {
pan: new Animated.ValueXY()
};
this._onLongPress = this._onLongPress.bind(this)
this._onLongPressPanResponder = this._onLongPressPanResponder.bind(this)
this._normalPanResponder = this._normalPanResponder.bind(this)
this.longPressTimer = null
}
Finally, before you render, you should switch to different panResponder handlers according to the state:
let panHandlers = {}
if(this.state.panResponder){
panHandlers = this.state.panResponder.panHandlers
}else{
panHandlers = this._normalPanResponder().panHandlers
}
Then attach the panHandlers to your view {...panHandlers}
You can even change the css for different panHandlers to show different effect.
If your only issue is that ScrollView scrolls when moving item, then I'll suggest simply to disable scrolling of parent for movement period.
Like:
//component with ScrollView:
...
constructor() {
super()
this.state = {scrolling: true}
this.enableScroll = this.enableScroll.bind(this)
this.disableScroll = this.disableScroll.bind(this)
}
// inject those methods into Drag&Drop item as props:
enableScroll() {
this.setState({scrolling: true})
}
disableScroll() {
this.setState({scrolling: false})
}
...
<ScrollView scrollEnabled={this.state.scrolling} ... />
...
//component with drag&drop item:
...
onPanResponderGrant() {
...
this.props.disableScroll()
...
}
onPanResponderRelease() {
this.props.enableScroll()
}
Make sure to cover all cases of release gesture (like onPanResponderTerminate etc.)
You can do this using a ref value to store if dragging should be enabled, and this can be accessed within the PanResponder. Using a ref ensures that the value won't be stale w.r.t the PanResponder; that is, the reference to the ref will be fixed at the initialisation of the PanResponder, but the underlying .current value can change. (This is unlike a useState value, which would be stale past the initialisation of the PanResponder.)
e.g.
const dragEnabledRef = useRef(false);
const pan = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => dragEnabledRef.current,
onStartShouldSetPanResponderCapture: () => dragEnabledRef.current,
onMoveShouldSetPanResponder: () => dragEnabledRef.current,
onMoveShouldSetPanResponderCapture: () => dragEnabledRef.current,
...
onPanResponderRelease: () => {
...
dragEnabledRef.current = false
}
})
).current
return (
<Pressable onLongPress={() => {
dragEnabledRef.current = true
}}>
{children}
</Pressable>
);

React Native multiple panresponders

With this code how would I add a second or multiple panresponders that can be moved independently of each other? If I use the same panresponder instance and code they move together as one. I want to know how to have several independently draggable panresponders.
'use strict';
var React = require('react-native');
var {
PanResponder,
StyleSheet,
View,
processColor,
} = React;
var CIRCLE_SIZE = 80;
var CIRCLE_COLOR = 'blue';
var CIRCLE_HIGHLIGHT_COLOR = 'green';
var PanResponderExample = React.createClass({
statics: {
title: 'PanResponder Sample',
description: 'Shows the use of PanResponder to provide basic gesture handling.',
},
_panResponder: {},
_previousLeft: 0,
_previousTop: 0,
_circleStyles: {},
circle: (null : ?{ setNativeProps(props: Object): void }),
componentWillMount: function() {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
onPanResponderGrant: this._handlePanResponderGrant,
onPanResponderMove: this._handlePanResponderMove,
onPanResponderRelease: this._handlePanResponderEnd,
onPanResponderTerminate: this._handlePanResponderEnd,
});
this._previousLeft = 20;
this._previousTop = 84;
this._circleStyles = {
style: {
left: this._previousLeft,
top: this._previousTop
}
};
},
componentDidMount: function() {
this._updatePosition();
},
render: function() {
return (
<View
style={styles.container}>
<View
ref={(circle) => {
this.circle = circle;
}}
style={styles.circle}
{...this._panResponder.panHandlers}
/>
</View>
);
},
_highlight: function() {
const circle = this.circle;
circle && circle.setNativeProps({
style: {
backgroundColor: processColor(CIRCLE_HIGHLIGHT_COLOR)
}
});
},
_unHighlight: function() {
const circle = this.circle;
circle && circle.setNativeProps({
style: {
backgroundColor: processColor(CIRCLE_COLOR)
}
});
},
_updatePosition: function() {
this.circle && this.circle.setNativeProps(this._circleStyles);
},
_handleStartShouldSetPanResponder: function(e: Object, gestureState: Object): boolean {
// Should we become active when the user presses down on the circle?
return true;
},
_handleMoveShouldSetPanResponder: function(e: Object, gestureState: Object): boolean {
// Should we become active when the user moves a touch over the circle?
return true;
},
_handlePanResponderGrant: function(e: Object, gestureState: Object) {
this._highlight();
},
_handlePanResponderMove: function(e: Object, gestureState: Object) {
this._circleStyles.style.left = this._previousLeft + gestureState.dx;
this._circleStyles.style.top = this._previousTop + gestureState.dy;
this._updatePosition();
},
_handlePanResponderEnd: function(e: Object, gestureState: Object) {
this._unHighlight();
this._previousLeft += gestureState.dx;
this._previousTop += gestureState.dy;
},
});
var styles = StyleSheet.create({
circle: {
width: CIRCLE_SIZE,
height: CIRCLE_SIZE,
borderRadius: CIRCLE_SIZE / 2,
backgroundColor: CIRCLE_COLOR,
position: 'absolute',
left: 0,
top: 0,
},
container: {
flex: 1,
paddingTop: 64,
},
});
module.exports = PanResponderExample;
You can use an array of PanResponders, created like so:
this._panResponders = yourObjectsArray.map((_, index) => (
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
...
})
));
yourObjectsArray is an array that you use for creating as many panResponders as you want, I imagine each object in that array will correspond to a data instance of whatever data structure you use to create the moveable Views.
Then to actually use it in your View:
render: function() {
return yourObjectsArray.map((_, index) => (
<View
style={styles.container}>
<View
... some stuff here ...
{...this._panResponders[index].panHandlers}
/>
</View>
)
};

Resources