简体   繁体   中英

React-native: scrollview inside of panResponder

I am using a ScrollView inside of a PanResponder. On Android it works fine but on iOS the ScrollView will not scroll. I did some investigation and here are some facts:

  1. If I put a break point in PanResponder.onMoveShouldSetPanResponder() , before I step over, the scrollView will scroll as normal but once I release the break point, the scrollView stops working.

  2. If I modify ScrollResponder.js, and return true in scrollResponderHandleStartShouldSetResponderCapture() - it used to return false at runtime; and return false in scrollResponderHandleTerminationRequest() , the scrollView works OK but of course, since it swallows the event the outer PanResponder will not get the event.

So the questions are:

  1. I want to make the scrollview to work, and not to swallow the event. Any one know what's the approach?
  2. How the responding system works on iOS? The react-native responder system doc does not explain that to me.

I finally solve this by wrap the scrollview inside a view ,and set the style of scrollview a limited height.

import * as React from 'react';
import { Text, View, StyleSheet,PanResponder,Animated,ScrollView,Dimensions} from 'react-native';
import { Constants } from 'expo';

const WINDOW_WIDTH = Dimensions.get("window").width;
const WINDOW_HEIGHT = Dimensions.get("window").height;
// You can import from local files

export default class App extends React.Component {
  constructor(props){
    super(props);
    this.minTop = 100;
    this.maxTop = 500;
    this.state={
      AnimatedTop:new Animated.Value(0),
    }
  }

  componentWillMount(){
    let that = this;
    this._previousTop = 100;
    this._panResponder = PanResponder.create({
      onMoveShouldSetPanResponder(){
        return true;
      },
      onPanResponderGrant(){
        that._previousTop = that.state.AnimatedTop.__getValue();
        return true;
      },
      onPanResponderMove(evt,gestureState){
        let currentTop = that._previousTop + gestureState.dy;
        that.state.AnimatedTop.setValue(that._previousTop+gestureState.dy);

      },
      onPanResponderRelease(){

      }
    })
  }


  render() {
    return (
      <View style={styles.container}>
        <Animated.View
          style={[styles.overlay,{top:this.state.AnimatedTop}]}
          {...this._panResponder.panHandlers}
        >
          <View style={{height:200,backgroundColor:"black"}}></View>
          <View>
            <ScrollView
              style={{height:500}}
            >
              <View style={{backgroundColor:"blue",height:200}}></View>
              <View style={{backgroundColor:"yellow",height:200}}></View>
              <View style={{backgroundColor:"pink",height:200}}></View>
              <View style={{backgroundColor:"red",height:200}}></View>
            </ScrollView>
          </View>
        </Animated.View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  overlay:{
    position:"absolute",
    width:WINDOW_WIDTH,
    height:WINDOW_HEIGHT-100,
  }
});

enter link description here I spent plenty of time to solve this.Hope this will help someone confused with the same problem.

Within PanResponder is an event that returns the current touch position. You can use that to compare 2 values to perform a scroll.

I don't know if it's usefull right now but you can add that line,

return !(gestureState.dx === 0 && gestureState.dy === 0)

in 'onMoveShouldSetPanResponder' property of PanResponder with evt and gestureState as parameters.

Your PanResponder should look like this :

this._panResponder = PanResponder.create({
  onMoveShouldSetPanResponder(evt, gestureState){
    return !(gestureState.dx === 0 && gestureState.dy === 0)
  },
  onPanResponderGrant(){
    that._previousTop = that.state.AnimatedTop.__getValue();
    return true;
  },
  onPanResponderMove(evt,gestureState){
    let currentTop = that._previousTop + gestureState.dy;
    that.state.AnimatedTop.setValue(that._previousTop+gestureState.dy);

  },
  onPanResponderRelease(){

  }
})

我已经通过向 ScrollView 的所有内容添加onPress处理程序解决了这个问题 - 如果处理程序是无操作的,滚动视图仍然可以正常工作。

I solved this issue by doing it

onMoveShouldSetPanResponder: (event, gesture) => {
  if (gesture?.moveX > gesture?.moveY) {
    return false;
  }
  return true;
},

On my case, I have a horizontal Flatlist inside a PanResponder...

To enable scrolling in a ScrollView that is a child of a parent with PanResponder, you have to make sure the ScrollView is the responder to any gesture inside of it. By default, gesture events bubble up from the deepest component to the parent component. In order to capture the event by the ScrollView, you can add a View with a PanResponder inside of it. See the (pseudo) example below, where ChildComponent is a child of a parent with PanResponder.

const ChildComponent = ({ theme }) => {
    const panResponder = React.useRef(
        PanResponder.create({
            // Ask to be the responder:
            onStartShouldSetPanResponder: (evt, gestureState) => true,
            onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
        })
    ).current;

    return (
        <ScrollView style={{ height: 500 }}>
            <View {...panResponder.panHandlers}>
                ...
            </View>
        </ScrollView>
    );
};

I'm not sure you still need this or not but I'll put to help others as well

If you put the panResponder on a sibling view of the ScrollView, the ScrollView behaves properly. then you can position that sibling view

here is an example of the workaround.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM