Ad

How To SetOffset For PanResponder When Using Hooks?

I found a code example demonstrating the use of the panResponder for drag and drop actions within react native. You can try the code in the following snack:

https://snack.expo.io/S14RvxJ_L

The problem I face is that if you drop the item in the drop area and then afterwards touch it, the position gets reset all the time.

I want the user to be able to drag the item away from the drop area without this issues. So again to clarify: You drag the item to the drop area and it will be colored red. Now drag the item again and try to drag anywhere you want, the position will be reset. I tried setting the initial position of the circle with a hook, tried to set the y0 and x0 of the gesture at the beginning with start values. It didn't work out so far.

What I found out so far is, that I could use pan.setOffset() in onPanResponderGrant. But as the pan is created with useRef(), it ist mutable and cannot be changed, or better I don't know how.

How would I accomplish that the best way?

import React from 'react';
import { StyleSheet, View, Text, Dimensions, Animated, PanResponder } from 'react-native';

export default function Drag() {
  const dropZoneValues = React.useRef(null);
  const pan = React.useRef(new Animated.ValueXY());
  const [bgColor, setBgColor] = React.useState('#2c3e50');

  const isDropZone = React.useCallback((gesture) => {
    const dz = dropZoneValues.current;
    return gesture.moveY > dz.y && gesture.moveY < dz.y + dz.height;
  }, []);

  const onMove = React.useCallback((_, gesture) => {
    if (isDropZone(gesture)) setBgColor('red');
    else setBgColor('#2c3e50');
  }, [isDropZone]);

  const setDropZoneValues = React.useCallback((event) => {
    dropZoneValues.current = event.nativeEvent.layout;
  });

  const panResponder = React.useMemo(() => PanResponder.create({
    onStartShouldSetPanResponder: () => true,

    onPanResponderMove: Animated.event([null, {
      dx  : pan.current.x,
      dy  : pan.current.y
    }], {
      listener: onMove
    }),
    onPanResponderRelease: (e, gesture) => {
      if (!isDropZone(gesture)) {
        Animated.spring(
          pan.current,
          {toValue:{x:0,y:0}}
        ).start();
      }
    }
  }), []);

  return (
    <View style={styles.mainContainer}>
      <View
        onLayout={setDropZoneValues}
        style={[styles.dropZone, {backgroundColor: bgColor}]}
      >
        <Text style={styles.text}>Drop me here!</Text>
      </View>
      <View style={styles.draggableContainer}>
        <Animated.View
          {...panResponder.panHandlers}
          style={[pan.current.getLayout(), styles.circle]}
        >
          <Text style={styles.text}>Drag me!</Text>
        </Animated.View>
      </View>
    </View>
  );
}

let CIRCLE_RADIUS = 36;
let Window = Dimensions.get('window');
let styles = StyleSheet.create({
  mainContainer: {
    flex: 1
  },
  dropZone: {
    height  : 100,
    backgroundColor:'#2c3e50'
  },
  text        : {
    marginTop   : 25,
    marginLeft  : 5,
    marginRight : 5,
    textAlign   : 'center',
    color       : '#fff'
  },
  draggableContainer: {
    position    : 'absolute',
    top         : Window.height/2 - CIRCLE_RADIUS,
    left        : Window.width/2 - CIRCLE_RADIUS,
  },
  circle: {
    backgroundColor     : '#1abc9c',
    width               : CIRCLE_RADIUS*2,
    height              : CIRCLE_RADIUS*2,
    borderRadius        : CIRCLE_RADIUS
  }
});

(Code from: https://github.com/facebook/react-native/issues/25360#issuecomment-505241400)

Ad

Answer

I solved it finally

You can see the result here: https://snack.expo.io/Bky!LlqbI

You have to use setOffset and setValue in onPanResponderGrant. pan ist a mutable object but can still be changed with pan.current.setOffset() or pan.current.setValue(). Lastly I had to add the pan.current.flattenOffset to onPanResponderRelease so the position will be kept for the next drag in the drop zone.

Ad
source: stackoverflow.com
Ad