ScrollView是我们常用的组件之一,因此搞清楚它的触摸与滚动事件十分重要!
1.在ScrollView里面轻触一下
(1)onStartShouldSetResponderCapture
这个属性接收一个回调函数,函数原型是 function(evt): bool,在触摸事件开始(touchDown)的时候,RN 容器组件会回调此函数,询问组件是否要劫持事件响应者设置,自己接收事件处理,如果返回 true,表示需要劫持;
/**
* There are times when the scroll view wants to become the responder
* (meaning respond to the next immediate `touchStart/touchEnd`), in a way
* that *doesn't* give priority to nested views (hence the capture phase):
*
* - Currently animating.
* - Tapping anywhere that is not the focused input, while the keyboard is
* up (which should dismiss the keyboard).
*
* Invoke this from an `onStartShouldSetResponderCapture` event.
*/
scrollResponderHandleStartShouldSetResponderCapture: function(e: Event): boolean {
// First see if we want to eat taps while the keyboard is up
var currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
if (!this.props.keyboardShouldPersistTaps &&
currentlyFocusedTextInput != null &&
e.target !== currentlyFocusedTextInput) {
return true;
}
return this.scrollResponderIsAnimating();
},
也就是说,我们在触碰ScrollView的时候,这个方法是第一个调用的,目的是判断是否进行劫持这个触摸事件(true是拦截,不让子视图去处理触摸事件;false是放开,交给子视图处理本次触摸事件)。目前,这个方法里面只对两种情况进行了处理,一是,屏幕内是否有TextInput正处于focused状态,如果是,则拦截,交给ScrollView去处理(例如:我们在ScrollView里面使用了TextInput,而此时正处于focused状态,我们点击ScrollView的其他区域则响应的应该是滑动事件);二是,判断现在动画是否正在进行(true是正在进行,false是没有正在进行的动画)。
注意:如果将这个函数的返回值,一直保持的是true,那么所有的触摸事件将不会下发给子视图,也就是说只可以响应ScrollView的触摸与滚动事件。
(2)onStartShouldSetResponder
这个属性接收一个回调函数,函数原型是 function(evt): bool,在触摸事件开始(touchDown)的时候,RN 会回调此函数,询问组件是否需要成为事件响应者,接收事件处理,如果返回 true,表示需要成为响应者;
假如组件通过上面的方法返回了 true,表示发出了申请要成为事件响应者请求,想要接收后续的事件输入。因为同一时刻,只能有一个事件处理响应者,RN 还需要协调所有组件的事件处理请求,所以不是每个组件申请都能成功,RN 通过如下两个回调来通知告诉组件它的申请结果。也就是说,只是去询问你想不想成为事件响应者,具体能不能成功,要看下面函数。
/**
* Merely touch starting is not sufficient for a scroll view to become the
* responder. Being the "responder" means that the very next touch move/end
* event will result in an action/movement.
*
* Invoke this from an `onStartShouldSetResponder` event.
*
* `onStartShouldSetResponder` is used when the next move/end will trigger
* some UI movement/action, but when you want to yield priority to views
* nested inside of the view.
*
* There may be some cases where scroll views actually should return `true`
* from `onStartShouldSetResponder`: Any time we are detecting a standard tap
* that gives priority to nested views.
*
* - If a single tap on the scroll view triggers an action such as
* recentering a map style view yet wants to give priority to interaction
* views inside (such as dropped pins or labels), then we would return true
* from this method when there is a single touch.
*
* - Similar to the previous case, if a two finger "tap" should trigger a
* zoom, we would check the `touches` count, and if `>= 2`, we would return
* true.
*
*/
scrollResponderHandleStartShouldSetResponder: function(): boolean {
return false;
},
这个方法全部返回false,在这个地方基本没起到作用,因为轻触一下屏幕还不足以让它成为事件响应者(毕竟主要功能是滚动),看注释的意思是后期扩展使用!
(3)onTouchStart
按下屏幕时触发,即使现在屏幕还在滚动或者有其他手指又触发屏幕。
(4)onTouchEnd
手指离开屏幕触摸结束时触发,即使现在屏幕还在滚动,跟onTouchStart相反。
2.滚动ScrollView
手指抬起之前
前面散步已经说过了,就不过多介绍了。
(1)onTouchMove
移动手指时触发,表示触摸手指移动的事件,这个回调可能非常频繁,所以这个回调函数的内容需要尽量简单;可以观察一下,这个过程中一共触发两轮onTouchMove方法,第一轮,指的是手指发生了小范围的移动,但是不足以触发屏幕滚动;第二轮,是真正的视图滚动。总之,只要手指发生偏移量,这个方法就会被回调。
(2)onScrollBeginDrag
拖拽开始,子视图开始移动,只是开始时回去调用。
(3)onScrollShouldSetResponder
跟前面onStartShouldSetResponder相类似,询问组件是否需要成为滚动事件响应者,接收事件处理,如果返回 true,表示需要成为响应者;
/**
* Invoke this from an `onScroll` event.
*/
scrollResponderHandleScrollShouldSetResponder: function(): boolean {
return this.state.isTouching;
},
isTouching指的是当前ScrollView区域是否还有触摸点。
(4)onResponderGrant
表示申请成功,组件成为了事件处理响应者,这时组件就开始接收后序的滚动事件输入。一般情况下,这时开始,组件进入了激活状态,并进行一些事件处理或者手势识别的初始化。
(5)onScroll(_handleScroll)
也许在这些方法中,我们最关心,也是最容易使用到的就是在这个方法了。像平时我们需要ScrollView的滑动来操作某些动画或者其他情况的,依靠的就是这个方法。
_handleScroll: function(e: Object) {
console.log('***********_handleScroll');
if (__DEV__) {
if (this.props.onScroll && !this.props.scrollEventThrottle && Platform.OS === 'ios') {
console.log( // eslint-disable-line no-console-disallow
'You specified `onScroll` on a <ScrollView> but not ' +
'`scrollEventThrottle`. You will only receive one event. ' +
'Using `16` you get all the events but be aware that it may ' +
'cause frame drops, use a bigger number if you don\'t need as ' +
'much precision.'
);
}
}
if (Platform.OS === 'android') {
if (this.props.keyboardDismissMode === 'on-drag') {
dismissKeyboard();
}
}
this.scrollResponderHandleScroll(e);
},
注意:注释部分人家也说了,如果你没有设置scrollEventThrottle这个属性,那么onScroll这个方法只是回调一次,如果设置的16(js:60帧每秒,每帧大概16ms),那么会引起丢帧的问题,总之选一个合适的值。
手指抬起之后(红框里面)
(1)onResponderRelease
手指释放后,视图成为响应者,释放滚动事件。
(2)onScrollEndDrag
滑动结束拖拽时触发,并不一定是停止滚动。
(3)onMomentumScrollBegin
接着就是一帧滚动的开始onMomentumScrollBegin,它的起始位置和onScrollEndDrag的结束位置重合。(惯性滚动)
(4)onScrollShouldSetResponder
因为这个时候,所有手指全部抬起来了,所以返回值一直就是false,则ScrollView不想成为滚动事件响应者,更不存在下面的那些流程了。
(5)onScroll(_handleScroll)
滚动并没有停止,坐标一直在变化,所以还会回调。
注:设置scrollEventThrottle这个属性,onScrollShouldSetResponder和onScroll频繁回调。
(6)onMomentumScrollEnd
最后是一帧滚动的结束,惯性滚动结束,屏幕静止。