我正在使用React-Native中的滑动来创建自己的选项卡导航器。它工作正常,但当我在我的一个标签内有一个ScrollView时,它似乎打破了。向左和向右滑动以更改选项卡可以正常工作,并且还可以在滚动视图中向下和向上滚动。当我单击拖动scrollView然后向侧面移动而不释放滑动时它会中断。然后选项卡系统只会重置为第一个选项卡。
我做了一个黑客攻击,当我滚动滚动视图时禁用从选项卡内部滑动。这可行,但感觉就像一个糟糕的解决方案,因为标签内容必须知道它在标签内。
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>
);
}
}
答案 0 :(得分:10)
尝试使用onMoveShouldSetResponder的函数,因此当gestureState.dx远远大于gestureState.dy时它只会水平滑动,如下所示:
onMoveShouldSetResponder: (evt, gestureState) => {
return Math.abs(gestureState.dx) > Math.abs(gestureState.dy * 3);
},
你也可以在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;
}
}
然后像这样使用它:
onMoveShouldSetPanResponder: this.canMove,
onPanResponderMove: (evt, gestureState) => {
if(!this._swipeDirection) this.checkSwipeDirection(gestureState);
// Your other code here
},
onPanResponderRelease: (evt, gestureState) => {
this._swipeDirection = null;
}
我在Satyajit Sahoo在Medium How I built React Native Tab View上发现了一篇很棒的文章。它更深入地展示了如何实现自己的Tab视图。我建议看一下博客文章,因为它对我很有帮助。
更新:如果您希望父组件阻止子组件成为手势响应者,请查看此处的文档Gesture Responder Lifecycle,反之亦然。
答案 1 :(得分:0)
或者您可以尝试使用我的实用程序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>
);
}
}
现在,将滚动条应用于您的滚动视图,它只会响应您想要的方向,并且两个人不会再混淆哪个应该响应平移/手势。
答案 2 :(得分:-4)
在使用代码块的视图控制器中(scrollView,平移手势),
写下这个函数:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
希望它有所帮助。:)