React Native - 如何实现横向List的RefreshControl效果?

新手上路,请多包涵

我正在用React Native做一个横向的翻页组件,每次左右滑动横向切换一整屏。当滑动到最左端后(也就是滑动组件的开头位置时),再向右滑动的话,需要刷新整个列表,同时需要显示交互效果。

目前我整个组件主体部分是用VirtualizedList实现的,整屏滑动的功能已经没有问题,但是右滑刷新的功能没有现成的效果/控件可以实现。我自己在整个list组件上加了PanResponder去捕获滑动事件,当列表滑到最左侧(scroll offset X接近0时),如果再监听到右滑事件(以dx > Math.abs(dy)为准判断右滑),则显示出一个隐藏在VirtualizedList头部的一个ActivityIndicator以表示在刷新。

我设想的刷新效果是这样的:

图片描述

但是PanResponder在捕获滑动事件的时候,onPanResponderGrant会被触发,但是在grant之后。onPanResponderMove却经常触发不了。而且即使触发了on move,也很快就会中断(体现出来的效果就是整个列表在跟着我的滑动向右移动了一小断距离后突然就停了,不再跟着我的滑动走了,可能是因为显示安卓原生的动画而被拦截了?)

图片描述

而除了onPanResponderGrantonPanResponderMove之后,像onPanResponderRelease onPanResponderTerminate甚至连onPanResponderTerminationRequest都没有被触发(这应该意味着并没有父控件强制要求拦截事件)。

有没有人知道其中的错误出在什么地方?或者有没有可以推荐的RefreshControl可以适用于横向的ScrollView/ListView的?


试验用的代码如下:

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}) => true;//(this._scrollPos||0) < 50 && Math.abs(dx) > Math.abs(dy);
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: (evt, gestureState) => {
        return shouldRespondPan(gestureState)
      },
      onStartShouldSetPanResponderCapture: (evt, gestureState) => {
        return shouldRespondPan(gestureState)
      },
      onMoveShouldSetPanResponder: (evt, gestureState) => {
        return shouldRespondPan(gestureState)
      },
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
        return shouldRespondPan(gestureState)
      },
      onPanResponderGrant: (evt, gestureState) => { // 会触发
        console.warn('----pull-Grant: ' + JSON.stringify(gestureState));
      },
      onPanResponderReject: (e, gestureState) => { // 不触发
        console.warn('----pull-Reject: ' + JSON.stringify(gestureState));
      },
      onPanResponderStart: (e, gestureState) => { // 不触发
        console.warn('----pull-Start: ' + JSON.stringify(gestureState));
      },
      onPanResponderEnd: (e, gestureState) => { // 会触发
        console.warn('----pull-End: ' + JSON.stringify(Object.keys(e)));
        this.resetRefreshStatus();
      },
      onPanResponderMove: (evt, gestureState) => { // 会触发,但只触发一次就直接触发on end了
        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.resetRefreshStatus();
      },
      onPanResponderTerminate: () => { // 不触发
        console.warn('----pull-Terminate: ' + JSON.stringify(gestureState));
        this.resetRefreshStatus();
      },
      onShouldBlockNativeResponder: (e, gestureState) => { // 会触发,但返回true和false结果一样
        console.warn('----pull-BlockNative');
        return false;
      },
    });

    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', backgroundColor: 'white'}}>
            <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;
阅读 4.4k
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题