react-native ScrollView触摸与滚动事件

本文详细解析了ScrollView在触摸与滚动过程中的事件处理,包括onStartShouldSetResponderCapture、onStartShouldSetResponder、onTouchStart、onTouchEnd等,以及滚动过程中的onScrollBeginDrag、onScroll、onResponderRelease等事件,阐述了它们在滚动交互中的作用和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
最后是一帧滚动的结束,惯性滚动结束,屏幕静止。

未完待续(事件抢夺)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值