react-native ScrollView觸摸與滾動事件

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

未完待續(事件搶奪)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章