React Native - 如何在水平滚动列表中模拟RefreshControl

时间:2017-07-19 14:49:26

标签: react-native horizontalscrollview react-native-scrollview

问题

我正在开发一个水平滚动列表,它总是在每次滑动时滚动整个屏幕(即一个"页面")。当它位于第一页并且用户再次向右拉时,我需要刷新整个列表

对于垂直ScrollView,如果用户在视图位于其最顶端位置时向下拉,则会出现标准RefreshControl。但对于横向版本,没有标准的解决方案。

我的实验

想法

我尝试添加PanResponder来捕捉触摸和移动事件。请注意,我在我的应用程序中使用了VirtualizedList。基本逻辑是:

  • 我创建了一个隐藏的View(其宽度默认设置为0),其中包含ActivityIndicator,并将此视图作为ListHeaderComponent插入VirtualizedList
  • 我会听取VirtualizedList的{​​{1}}事件并继续保存最新的滚动位置
  • 当滚动位置接近0时,用户向右拉(事件捕获的手势通过onScroll),然后使用ActivityIndi​​cator设置隐藏视图的宽度以模拟RefreshControl行为

结果

但是,PanResponder无法正确捕获所需的事件。大多数情况下,当我在PanResponder的开头(在Android设备上)向右拉时,只会显示标准的命中边缘动画(显示渐变蓝色图层)。

通过添加一些日志,我发现偶尔会触发VirtualizedList,但onPanResponderGrantonPanResponderMoveonPanResponderReleaseonPanResponderTerminate都不会被触发

触发onPanResponderTerminationRequest时,隐藏的onPanResponderGrant偶尔会出现在个别地方。 "拉到右边"效果只会很快显示,并且总是立即停止,只显示隐藏视图的一小部分。

我猜底层ActivityIndicator会窃取该事件并阻止我的代码响应。但即使我将ScrollView设置为始终退回onPanResponderTerminationRequest仍然无法提供帮助。即使我直接用false替换VirtualizedList也会产生相同的结果。

当ScrollView滚动到顶部时,有没有人知道如何正确捕捉触摸和移动事件?或者是否有另一种可行的方法来实现ScrollView的横向版本?

实验代码

RefreshControl

临时解决方法(不太完美)

  • 直接在import React from 'react' import { Text, View, ActivityIndicator, ScrollView, VirtualizedList, Dimensions, PanResponder, Animated, Easing } from 'react-native' let DATA = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; class Test extends React.PureComponent { constructor(props) { super(props); this.state = { refreshIndicatorWidth: new Animated.Value(0), }; this.onScroll = evt => this._scrollPos = evt.nativeEvent.contentOffset.x; this.onPullToRefresh = dist => Animated.timing(this.state.refreshIndicatorWidth, { toValue: dist, duration: 0, easing: Easing.linear, }).start(); this.resetRefreshStatus = () => { Animated.timing(this.state.refreshIndicatorWidth, { toValue: 0, }).start(); }; const shouldRespondPan = ({dx, dy}) => (this._scrollPos||0) < 50 && dx > 10 && Math.abs(dx) > Math.abs(dy); this._panResponder = PanResponder.create({ onMoveShouldSetPanResponder: (evt, gestureState) => { return shouldRespondPan(gestureState) }, onMoveShouldSetPanResponderCapture: (evt, gestureState) => { return shouldRespondPan(gestureState) }, onPanResponderGrant: (evt, gestureState) => { console.warn('----pull-Grant: ' + JSON.stringify(gestureState)); }, onPanResponderMove: (evt, gestureState) => { console.warn('----pull-Move: ' + JSON.stringify(gestureState)); this.onPullToRefresh(gestureState.dx) }, onPanResponderTerminationRequest: () => { console.warn('----pull-TerminationRequest: ' + JSON.stringify(gestureState)); return false }, onPanResponderRelease: (evt, gestureState) => { console.warn('----pull-Release: ' + JSON.stringify(gestureState)); this.props.refreshMoodList(); }, onPanResponderTerminate: () => { console.warn('----pull-Terminate: ' + JSON.stringify(gestureState)); this.resetRefreshStatus(); }, }); this.renderRow = this.renderRow.bind(this); } componentDidUpdate(prevProps, prevState, prevContext) { console.warn('----updated: ' + JSON.stringify(this.props)); this.resetRefreshStatus(); } renderRow({item, index}) { return ( <View style={{flex: 1, alignItems: 'center', justifyContent: 'center', width: Dimensions.get('window').width, height: '100%'}}> <Text>{item}</Text> </View> ); } render() { /******* Test with a VirtualizedList *******/ return ( <VirtualizedList {...this._panResponder.panHandlers} data={DATA} ListEmptyComponent={<View/>} getItem={getItem} getItemCount={getItemCount} keyExtractor={keyExtractor} renderItem={this.renderRow} horizontal={true} pagingEnabled={true} showsHorizontalScrollIndicator={false} ListHeaderComponent={( <Animated.View style={{flex: 1, height: '100%', width: this.state.refreshIndicatorWidth, justifyContent: 'center', alignItems: 'center'}}> <ActivityIndicator size="large"/> </Animated.View> )} onScroll={this.onScroll} /> ); /******* Test with a ScrollView *******/ return ( <View style={{height:Dimensions.get('window').height}}> <ScrollView {...this._panResponder.panHandlers} horizontal={true} > <View style={{width: Dimensions.get('window').width * 2, height: 100, backgroundColor: 'green'}}> <Text>123</Text> </View> </ScrollView> </View> ); } } const getItem = (data, index) => data[index]; const getItemCount = data => data.length; const keyExtractor = (item, index) => 'R' + index; export default Test; 中触发刷新事件(以及动画):这样,我仍然无法跟踪触摸移动。因此滚动窗格无法跟随触摸点。

  • onPanResponderGrant中的操作移至onPanResponderMove并附加条件(例如添加我自己的&#34; grant&#34;条件判断):这种方式,我可以跟踪触摸动作,但由于我还没有得到真正的&#34;授予&#34;,我无法捕获onMoveShouldSetPanResponder / *Release / *Terminate事件(即,我不知道触摸事件何时结束,因此实际上从未知道何时触发刷新事件!)

完美解决方案

等待你的想法......

1 个答案:

答案 0 :(得分:0)

您可以使用contentOffset来确定用户是否在水平列表中“拉到刷新”。以下内容适用于FlatListScrollView

onScroll={(e) => {
  const xOffset = e.nativeEvent.contentOffset.x;

  // logic goes here to handle a negative value

}}
scrollEventThrottle={16}

当xOffset达到某个值时,你可以做一些事情或设置一个标志。希望这能让你开始!