我正在用React Native做一个横向的翻页组件,每次左右滑动横向切换一整屏。当滑动到最左端后(也就是滑动组件的开头位置时),再向右滑动的话,需要刷新整个列表,同时需要显示交互效果。
目前我整个组件主体部分是用VirtualizedList
实现的,整屏滑动的功能已经没有问题,但是右滑刷新的功能没有现成的效果/控件可以实现。我自己在整个list组件上加了PanResponder
去捕获滑动事件,当列表滑到最左侧(scroll offset X接近0时),如果再监听到右滑事件(以dx > Math.abs(dy)
为准判断右滑),则显示出一个隐藏在VirtualizedList
头部的一个ActivityIndicator
以表示在刷新。
我设想的刷新效果是这样的:
但是PanResponder
在捕获滑动事件的时候,onPanResponderGrant
会被触发,但是在grant之后。onPanResponderMove
却经常触发不了。而且即使触发了on move,也很快就会中断(体现出来的效果就是整个列表在跟着我的滑动向右移动了一小断距离后突然就停了,不再跟着我的滑动走了,可能是因为显示安卓原生的动画而被拦截了?)
而除了onPanResponderGrant
和onPanResponderMove
之后,像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;