React Native: Propagate Pan Responder event from view to inner scroll view

后端 未结 3 1200
栀梦
栀梦 2021-02-04 11:08

I have a ScrollView inside an animated View that has panning.


    
    ...
             


        
3条回答
  •  孤独总比滥情好
    2021-02-04 11:23

    Apparently the problem is in the Native layer.

    https://github.com/facebook/react-native/issues/9545#issuecomment-245014488

    I found that onterminationrequest not being triggerred is caused by Native Layer.

    Modify react-native\ReactAndroid\src\main\java\com\facebook\react\views\scroll\ReactScrollView.java , comment Line NativeGestureUtil.notifyNativeGestureStarted(this, ev); and then build from the source, you will see your PanResponder outside ScrollView takes the control as expected now.

    PS: I couldn't build from source yet. Building from source apparently is way harder than I thought.

    EDIT 1:

    Yes, it worked. I deleted the react-native folder from node_modules, then git cloned the react-native repository directly into node_modules. And checked out to version 0.59.1. Then, followed this instructions. For this example, I didn't have to set any PanReponder or Responder to the ScrollView.

    However, it doesn't work as expected, of course. I had to hold the press gesture up and down. If you scroll all the way up, then try to move it down, it will panResponde to snap the blue area down. The content will remain up.

    Conclusion: Even after removing the strong locking from ScrollView, it's quite complex to implement the full desired behaviour. Now we have to combine the onMoveShouldSetPanResponder to the ScrollView's onScroll, and deal with the initial press event, to take the delta Y so that we can finally move the parent view properly, once it's reached top.

    /**
     * Sample React Native App
     * https://github.com/facebook/react-native
     *
     * @format
     * @flow
     */
    
    import React, { Component } from 'react';
    import { Platform, StyleSheet, Text, View, Dimensions, PanResponder, Animated, ScrollView } from 'react-native';
    
    const instructions = Platform.select({
      ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
      android:
        'Double tap R on your keyboard to reload,\n' +
        'Shake or press menu button for dev menu',
    });
    
    export default class App extends Component {
    
      constructor(props) {
        super(props);
        
        const {height, width} = Dimensions.get('window');
    
        const initialPosition = {x: 0, y: height - 70}
        const position = new Animated.ValueXY(initialPosition);
    
        const parentResponder = PanResponder.create({
          onMoveShouldSetPanResponderCapture: (e, gestureState) => {
            return false
          },
          onStartShouldSetPanResponder: () => false,
          onMoveShouldSetPanResponder: (e, gestureState) =>  {
            if (this.state.toTop) {
              return gestureState.dy > 6
            } else {
              return gestureState.dy < -6
            }
          },
          onPanResponderTerminationRequest: () => false,
          onPanResponderMove: (evt, gestureState) => {
            let newy = gestureState.dy
            if (this.state.toTop && newy < 0 ) return
            if (this.state.toTop) {
              position.setValue({x: 0, y: newy});
            } else {
              position.setValue({x: 0, y: initialPosition.y + newy});
            }
          },
          onPanResponderRelease: (evt, gestureState) => {
            if (this.state.toTop) {
              if (gestureState.dy > 50) {
                this.snapToBottom(initialPosition)
              } else {
                this.snapToTop()
              }
            } else {
              if (gestureState.dy < -90) {
                this.snapToTop()
              } else {
                this.snapToBottom(initialPosition)
              }
            }
          },
        });
    
        this.offset = 0;
        this.parentResponder = parentResponder;
        this.state = { position, toTop: false };
      }
    
      snapToTop = () => {
        Animated.timing(this.state.position, {
          toValue: {x: 0, y: 0},
          duration: 300,
        }).start(() => {});
        this.setState({ toTop: true })
      }
    
      snapToBottom = (initialPosition) => {
        Animated.timing(this.state.position, {
          toValue: initialPosition,
          duration: 150,
        }).start(() => {});
        this.setState({ toTop: false })
      }
    
      hasReachedTop({layoutMeasurement, contentOffset, contentSize}){
        return contentOffset.y == 0;
      }
    
      render() {
        const {height} = Dimensions.get('window');
    
        return (
          
            Welcome to React Native!
            To get started, edit App.js
            {instructions}
            
              =
              
                Lorem Ipsum
                dolor sit amet
                consectetur adipiscing elit.
                In ut ullamcorper leo.
                Sed sed hendrerit nulla,
                sed ullamcorper nisi.
                Mauris nec eros luctus
                leo vulputate ullamcorper
                et commodo nulla.
                Nullam id turpis vitae
                risus aliquet dignissim
                at eget quam.
                Nulla facilisi.
                Vivamus luctus lacus
                eu efficitur mattis
              
            
          
        );
      }
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
      },
      welcome: {
        fontSize: 20,
        textAlign: 'center',
        margin: 10,
      },
      instructions: {
        textAlign: 'center',
        color: '#333333',
        marginBottom: 5,
      },
      draggable: {
          position: 'absolute',
          right: 0,
          backgroundColor: 'skyblue',
          alignItems: 'center'
      },
      dragHandle: {
        fontSize: 22,
        color: '#707070',
        height: 60
      },
      scroll: {
        paddingLeft: 10,
        paddingRight: 10
      }
    });

提交回复
热议问题