Enable drag and drop of item in ScrollView after long press

I applied drag n drag list with panResponder and ScrollView. I want to scroll through the list even when I touch an item. The problem is that the item moves when I make a gesture to scroll. Of course, I also want to be able to move the item, but now it has the same gesture as the scroll. I want to overcome it, allowing me to drag an element only after a long press (1.5 sec). How to implement it? I thought of using Touchable as an element with onPressIn / onPressOut, as described here: http://browniefed.com/blog/react-native-press-and-hold-button-actions/ and somehow enable panResponder after a period of time, but I don’t know how to enable it programmatically.

Now this is my code for the item 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;
+4
source share
2 answers

I met the same problem with you. My solution is to define 2 different handlers panResponderfor onLongPressnormal behavior.

  _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 a function _onLongPress:

  _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 rendering, 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 panHandlersto your view. {...panHandlers} You can even change the css for different panHandlers to show a different effect.

+2
source

, ScrollView , . :

//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() 
}

, (, onPanResponderTerminate ..)

0

Source: https://habr.com/ru/post/1660002/


All Articles