PanResponder 中的 ScrollView
Posted
技术标签:
【中文标题】PanResponder 中的 ScrollView【英文标题】:ScrollView inside an PanResponder 【发布时间】:2017-06-07 08:54:42 【问题描述】:我正在使用 React-Native 中的滑动来实现我自己的选项卡导航器。它工作正常,但是当我的一个选项卡中有一个 ScrollView 时,它似乎坏了。左右滑动以更改选项卡工作正常,并且在滚动视图中向下和向上滚动。当我单击拖动滚动视图然后横向移动而不释放滑动时,它会中断。然后标签系统只是重置为第一个标签。
我做了一个 hack,当滚动视图滚动时,我禁止从选项卡内部滑动。这可行,但感觉是一个糟糕的解决方案,因为标签内容必须知道它在标签内。
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>
);
【问题讨论】:
你使用了 shouldRecognizeSimultaneouslyWith 功能吗? 嘿,我不知道。生病检查一下。任何示例如何将它与我的解决方案一起使用? 【参考方案1】:尝试使用 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;
我在 Medium How I built React Native Tab View 上发现了 Satyajit Sahoo 的一篇很棒的在线文章。它更深入地展示了如何实现自己的标签视图。我建议看一下这篇博文,因为它对我很有帮助。
更新:如果您希望父组件防止子组件成为手势响应器,反之亦然,请查看此处的文档 Gesture Responder Lifecycle。
【讨论】:
dy 值的 *3 是怎么回事?这是为什么?谢谢 不适合我。它完全绕过了 panResponder。【参考方案2】:或者您可以尝试使用我的实用工具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>
);
现在你应用这个的滚动视图,只会响应你想要的方向,他们两个不会再混淆哪个应该响应平移/手势。
【讨论】:
【参考方案3】:在使用代码块的视图控制器中(滚动视图,平移手势),
编写这个函数:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
return true
希望对你有帮助。:)
【讨论】:
很抱歉,我正在使用带有 React-Native 的 javascript。你把这行代码放在哪里了?以上是关于PanResponder 中的 ScrollView的主要内容,如果未能解决你的问题,请参考以下文章
仅当我移动手指时才允许Panresponder React native
React Native - panResponder 动画在 iOS 上不起作用
如何在 React Native 中制作微调器?我想通过 PanResponder 旋转框