简体   繁体   English

PanResponder内的ScrollView

[英]ScrollView inside an PanResponder

I am making my own implementation of a tab navigator with swiping in React-Native. 我正在使用React-Native中的滑动来创建自己的选项卡导航器。 It works fine, but when I have a ScrollView inside one of my tabs it seems to break. 它工作正常,但当我在我的一个标签内有一个ScrollView时,它似乎打破了。 Swiping left and right to change tabs works fine, and also scrolling down and up in scrollview. 向左和向右滑动以更改选项卡可以正常工作,并且还可以在滚动视图中向下和向上滚动。 It breaks when I click to drag the scrollView and then move sideways without releasing to swipe. 当我单击拖动scrollView然后向侧面移动而不释放滑动时它会中断。 Then the tab system just resets to the first tab. 然后选项卡系统只重置为第一个选项卡。

I made a hack where I disable swiping from inside a tab when the scrollview is scrolled. 当滚动滚动视图时,我做了一个黑客攻击,禁用从选项卡内部滑动。 This works, but feels like a bad solution because the tab content has to be aware that it is inside a tab. 这可行,但感觉就像一个糟糕的解决方案,因为标签内容必须知道它在选项卡内。

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { View, Animated, Dimensions, PanResponder } from 'react-native';
import Immutable from 'immutable';
import Tab1 from './Tab1';
import Tab2 from './Tab2';
import ScrollViewTab from './ScrollViewTab';


@connect(
    state => ({
        tabs: state.tabs
    })
)

export default class Tabs extends Component {
    static propTypes = {
        tabs: PropTypes.instanceOf(Immutable.Map).isRequired,
        dispatch: PropTypes.func.isRequired
    };
    constructor(props) {
        super(props);
        this.justLoaded = true;
        this.state = {
            left: new Animated.Value(0),
            tabs: [{ // Tabs must be in order, despite index.
                name: 'tab1',
                component: <Tab1 setTab={this.setTab} />,
                index: 0
            }, {
                name: 'tab2',
                component: <Tab2 setTab={this.setTab} />,
                index: 1
            }, {
                name: 'scrollViewTab',
                component: <ScrollViewTab setTab={this.setTab} />,
                index: 2
            }]
        };
        this.getIndex = this.getIndex.bind(this);
    }
    componentWillMount() {
        this.panResponder = PanResponder.create({
            onMoveShouldSetResponderCapture: () => true,
            onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
                if (Math.abs(gestureState.dx) > 10) {
                    return true;
                }
                return false;
            },
            onPanResponderGrant: () => {
                this.state.left.setOffset(this.state.left._value);
                this.state.left.setValue(0);
            },
            onPanResponderMove: (e, gestureState) => {
                if (this.isSwipingOverLeftBorder(gestureState) ||
                    this.isSwipingOverRightBorder(gestureState)) return;
                Animated.event([null, {
                    dx: this.state.left
                }])(e, gestureState);
            },
            onPanResponderRelease: (e, gestureState) => {
                this.state.left.flattenOffset();

                if (this.isSwipingOverLeftBorder(gestureState) ||
                    this.isSwipingOverRightBorder(gestureState)) {
                    return;
                }

                Animated.timing(
                    this.state.left,
                    { toValue: this.calcX(gestureState) }
                ).start();
            }
        });
    }
    componentDidMount() {
        this.justLoaded = false;
    }
    getStyle() {
        const oldLeft = this.state.left;
        let left = 0;
        const screenWidth = Dimensions.get('window').width;
        // Set tab carouselle coordinate to match the selected tab.
        this.state.tabs.forEach((tab) => {
            if (tab.name === this.props.tabs.get('tab')) {
                left = -tab.index * screenWidth;
            }
        });


        if (this.justLoaded) {
            Animated.timing(
                this.state.left,
                { toValue: left,
                    duration: 0
                }
            ).start();

            return { transform: [{ translateX: oldLeft }], flexDirection: 'row', height: '100%' };
        }
        Animated.timing(
                this.state.left,
            { toValue: left
            }
            ).start();
        return { transform: [{ translateX: oldLeft }], flexDirection: 'row', height: '100%' };
    }
    getIndex(tabN) {
        let index = 0;

        this.state.tabs.forEach((tab) => {
            if (tab.name === tabN) {
                index = tab.index;
            }
            return tab;
        });

        return index;
    }
    setTab(tab, props) {
        this.navProps = props;
        this.props.dispatch({ type: 'SET_TAB', tab });
    }
    isSwipingOverLeftBorder(gestureState) {
        return (this.props.tabs.get('tab') === this.state.tabs[0].name &&
                gestureState.dx > 0);
    }
    isSwipingOverRightBorder(gestureState) {
        return (this.props.tabs.get('tab') === this.state.tabs[this.state.tabs.length - 1].name &&
                gestureState.dx < 0);
    }
    calcX(gestureState) {
        const screenWidth = Dimensions.get('window').width;
        const activeTab = this.getIndex(this.props.tabs.get('tab'));
        let coord = 0;

        if (gestureState.dx > screenWidth * 0.2) {
            coord = (activeTab * screenWidth) - screenWidth;
        } else if (gestureState.dx < -(screenWidth * 0.2)) {
            coord = (activeTab * screenWidth) + screenWidth;
        } else {
            coord = activeTab * screenWidth;
        }

        this.updateTab(-coord, screenWidth);

        return -coord;
    }
    updateTab(coord, screenWidth) {
        // Update current tab according to location and screenwidth
        this.state.tabs.forEach((tab) => {
            if (coord === -tab.index * screenWidth) {
                this.props.dispatch({ type: 'SET_TAB', tab: tab.name });
            }
        });
    }
    render() {
        return (
          <View
            style={{ flex: 1 }}
          >
            <Animated.View
              style={this.getStyle()}
              {...this.panResponder.panHandlers}
            >
              {this.state.tabs.map(tab => tab.component)}
            </Animated.View>
          </View>
        );
    }
}

Try using a function for onMoveShouldSetResponder so it only swipes horizontally when gestureState.dx is far greater than gestureState.dy like so: 尝试使用onMoveShouldSetResponder的函数,因此当gestureState.dx远远大于gestureState.dy时它只会水平滑动,如下所示:

onMoveShouldSetResponder: (evt, gestureState) => {
    return Math.abs(gestureState.dx) > Math.abs(gestureState.dy * 3);
},

You could also have a function in onPanResponderMove tracks the direction of the swipe gesture and then reset in onPanResponderRelease so you don't have problems when a vertical swipe changes into a horizontal swipe like so: 你也可以在onPanResponderMove中有一个函数跟踪滑动手势的方向,然后在onPanResponderRelease中重置,这样当垂直滑动变为水平滑动时你没有问题,如下所示:

checkSwipeDirection(gestureState) {
    if( 
        (Math.abs(gestureState.dx) > Math.abs(gestureState.dy * 3) ) &&
        (Math.abs(gestureState.vx) > Math.abs(gestureState.vy * 3) )
    ) {
        this._swipeDirection = "horizontal";
    } else {
        this._swipeDirection = "vertical";
    }
}
canMove() {
    if(this._swipeDirection === "horizontal") {
        return true;
    } else {
        return false;
    }
}

Then use it like so: 然后像这样使用它:

onMoveShouldSetPanResponder: this.canMove,
onPanResponderMove: (evt, gestureState) => {
    if(!this._swipeDirection) this.checkSwipeDirection(gestureState);

// Your other code here
},
onPanResponderRelease: (evt, gestureState) => {
    this._swipeDirection = null;
}

I found an awesome article online by Satyajit Sahoo on Medium How I built React Native Tab View .It shows more in-depth how to implement your own Tab View. 我在Satyajit Sahoo上发现了一篇很棒的文章,介绍了如何构建React Native Tab View。它更深入地展示了如何实现自己的Tab View。 I recommend taking a look at the blog post as it was really helpful for me. 我建议看一下博客文章,因为它对我很有帮助。

Update: Check out the documentation here Gesture Responder Lifecycle if you want a parent component to prevent a child component from becoming the gesture responder or vice versa. 更新:如果您希望父组件阻止子组件成为手势响应者,请查看此处的文档Gesture Responder Lifecycle ,反之亦然。

Or you can try using my util react-native-scroll-locky and get it over with..! 或者您可以尝试使用我的util react-native-scroll-locky并将其与...结合使用!

import RNLocky from "react-native-scroll-locky";

class WhateverClass extends Component {
  constructor(props) {
    super(props);
    this.directionLockPanHandler = new RNLocky(
      RNLocky.Direction.HORIZONTAL, // or RNLocky.Direction.VERTICAL
    );
  }


  render() {
      return (
          <ScrollView
              {...this.directionLockPanHandler.getPanHandlers()}
          >
            <ScrollView>
                ...
            </ScrollView>
          </ScrollView>
      );
  }
}

Now the scroll view you applied this to, will only respond to the direction you want it to, and the two of them won't get confused again about which one should respond to the pan/gesture. 现在你应用它的滚动视图只会响应你想要它的方向,并且它们中的两个不会再混淆哪一个应该响应平移/手势。

In your view controller which is using your code block(scrollView,pan gesture) , 在使用代码块的视图控制器中(scrollView,pan gesture),

write this function: 写这个功能:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
   return true
}

Hope it helps.:) 希望能帮助到你。:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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