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
最後是一幀滾動的結束,慣性滾動結束,屏幕靜止。