在React Native中获取ScrollView的当前滚动位置

San*_*ang 97 ios reactjs react-native

是否可以<ScrollView>在React Native中获取当前滚动位置或组件的当前页面?

所以类似于:

<ScrollView
  horizontal={true}
  pagingEnabled={true}
  onScrollAnimationEnd={() => { 
      // get this scrollview's current page or x/y scroll position
  }}>
  this.state.data.map(function(e, i) { 
      <ImageCt key={i}></ImageCt> 
  })
</ScrollView>
Run Code Online (Sandbox Code Playgroud)

bra*_*ler 185

尝试.

<ScrollView onScroll={this.handleScroll} />
Run Code Online (Sandbox Code Playgroud)

然后:

handleScroll: function(event: Object) {
 console.log(event.nativeEvent.contentOffset.y);
},
Run Code Online (Sandbox Code Playgroud)

  • 或者`event.nativeEvent.contentOffset.x`,如果你有一个水平的ScrollView;)谢谢! (17认同)
  • 滚动值更改时,这并不总是会触发.例如,如果调整了scrollview,导致它现在能够立即在屏幕上显示整个事物,那么您似乎并不总是会看到onScroll事件. (3认同)
  • 这也可以在Android中使用。 (2认同)
  • 令人沮丧的是,除非你将[`scrollEventThrottle`](https://facebook.github.io/react-native/docs/scrollview.html#scrolleventthrottle)设置为16或更低(为了获得每一帧的事件),否则就没有保证这将报告*final*帧的偏移量 - 这意味着为了确保在滚动完成后你有正确的偏移量,你需要设置`scrollEventThrottle = {16}`并接受它的性能影响. (2认同)

Mar*_*ery 42

免责声明:以下内容主要是我在React Native 0.50中进行实验的结果.该ScrollView文件目前缺少下面提到的许多信息; 例如onScrollEndDrag,完全没有记录.由于这里的一切都依赖于无证件的行为,遗憾的是,我不能保证这些信息在一年甚至一个月之后仍然是正确的.

此外,下面的所有内容都假设一个纯粹的垂直滚动视图,我们感兴趣的y偏移; 如果需要,转换为x偏移对于读者来说有望是一个简单的练习.


ScrollView采取各种事件处理程序event,让您通过当前的滚动位置event.nativeEvent.contentOffset.y.其中一些处理程序在Android和iOS之间的行为略有不同,详情如下.

onScroll

在Android上

在用户滚动时触发每一帧,在用户释放滚动视图时滑动每一帧,在滚动视图停止时在最后一帧上触发,以及每当滚动视图的偏移因其帧而改变时改变(例如由于从风景到肖像的旋转).

在iOS上

在用户拖动时或在滚动视图滑动时触发,以某个频率确定,scrollEventThrottle每帧最多确定一次scrollEventThrottle={16}.如果用户在有足够的动力滑动时释放滚动视图,则在滑行onScroll后休息时也会触发处理程序.然而,如果用户拖动,然后释放滚动视图,而它是固定的,onScroll保证除非火为最终位置scrollEventThrottle已被设置,使得onScroll火灾滚动的每一帧.

设置的性能成本scrollEventThrottle={16}可以通过将其设置为更大的数量来降低.但是,这意味着onScroll每个帧都不会触发.

onMomentumScrollEnd

滑动后滚动视图停止时触发.如果用户在静止时释放滚动视图使其不会滑动,则根本不会触发.

onScrollEndDrag

当用户停止拖动滚动视图时触发 - 无论滚动视图是静止还是开始滑动.


鉴于行为的这些差异,跟踪偏移的最佳方式取决于您的确切情况.在最复杂的情​​况下(你需要支持Android和iOS,包括处理ScrollView由于旋转引起的框架变化,并且你不想接受Android上设置scrollEventThrottle为16 的性能损失),你需要处理滚动视图中的内容也发生了变化,那是一个非常糟糕的混乱.

最简单的情况是你只需要处理Android; 只需使用onScroll:

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
>
Run Code Online (Sandbox Code Playgroud)

为了额外支持iOS,如果你很乐意onScroll每帧都触发处理程序并接受它的性能影响,并且如果你不需要处理帧更改,那么它只是稍微复杂一点:

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  scrollEventThrottle={16}
>
Run Code Online (Sandbox Code Playgroud)

为了降低iOS上的性能开销,同时仍然保证我们记录滚动视图所处的任何位置,我们可以增加scrollEventThrottle并另外提供一个onScrollEndDrag处理程序:

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  onScrollEndDrag={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  scrollEventThrottle={160}
>
Run Code Online (Sandbox Code Playgroud)

但是如果我们想要处理帧更改(例如,因为我们允许设备旋转,更改滚动视图框架的可用高度)和/或内容更改,那么我们必须另外实现两者onContentSizeChangeonLayout跟踪两者的高度滚动视图的框架及其内容,从而不断计算最大可能偏移量,并推断由于框架或内容大小更改而自动减少偏移量:

<ScrollView
  onLayout={event => {
    this.frameHeight = event.nativeEvent.layout.height;
    const maxOffset = this.contentHeight - this.frameHeight;
    if (maxOffset < this.yOffset) {
      this.yOffset = maxOffset;
    }
  }}
  onContentSizeChange={(contentWidth, contentHeight) => {
    this.contentHeight = contentHeight;
    const maxOffset = this.contentHeight - this.frameHeight;
    if (maxOffset < this.yOffset) {
      this.yOffset = maxOffset;
    }
  }}
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y;
  }}
  onScrollEndDrag={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y;
  }}
  scrollEventThrottle={160}
>
Run Code Online (Sandbox Code Playgroud)

是的,这太可怕了.我也不是100%肯定在你同时改变滚动视图的框架和内容的大小的情况下它总能正常工作.但这是我能想到的最好的,直到这个功能在框架内添加,我认为这是任何人都可以做到的最好的.

  • 我花了几个小时查看各种反应本机票证和类似的 SO 票证,这是我找到的最完整的解决方案。IMO 确实令人失望的是,相应的反应本机票证(上面链接)被关闭并锁定,因为这里的用例和缺陷非常明显。 (2认同)

cut*_*ine 19

Brad Oyler的回答是正确的.但是你只会收到一个活动.如果你需要不断更新滚动位置,你应该设置scrollEventThrottle道具,如下所示:

<ScrollView onScroll={this.handleScroll} scrollEventThrottle={16} >
  <Text>
    Be like water my friend …
  </Text>
</ScrollView>
Run Code Online (Sandbox Code Playgroud)

和事件处理程序:

handleScroll: function(event: Object) {
  console.log(event.nativeEvent.contentOffset.y);
},
Run Code Online (Sandbox Code Playgroud)

请注意,您可能会遇到性能问题.相应地调整油门.16为您提供最多的更新.0只有一个.

  • 这是不正确的.首先,scrollEvenThrottle仅适用于iOS而不适用于Android.其次,它仅规定您接收onScroll呼叫的频率.默认值是每帧一次,而不是一个事件.1为您提供更多更新,16给您更少.0是默认值.这个答案是完全错误的.https://facebook.github.io/react-native/docs/scrollview.html#scrolleventthrottle (11认同)
  • 我在 React Native 支持 Android 之前回答了这个问题,所以是的,答案仅适用于 iOS。默认值为零,这导致每次滚动视图时滚动事件仅发送一次。 (2认同)
  • @Teodors 虽然这个答案不包括 Android,但你的其余批评完全令人困惑。`scrollEventThrottle={16}` 不会*为您提供“更少”的更新;1-16 范围内的任何数字都会为您提供最大可能的更新率。默认值 0(在 iOS 上)会在用户开始滚动时为您提供一个事件,然后不会进行后续更新(这非常无用,并且可能是一个错误);默认值是*不是*“每帧一次”。除了您评论中的这两个错误之外,您的其他断言与答案所说的任何内容都不矛盾(尽管您似乎认为它们确实如此)。 (2认同)

RY_*_*eng 12

以上答案告诉了如何使用不同的API来获取位置,onScrollonMomentumScrollEnd等; 如果你想知道页面索引,你可以使用偏移值来计算它。

 <ScrollView 
    pagingEnabled={true}
    onMomentumScrollEnd={this._onMomentumScrollEnd}>
    {pages}
 </ScrollView> 

  _onMomentumScrollEnd = ({ nativeEvent }: any) => {
   // the current offset, {x: number, y: number} 
   const position = nativeEvent.contentOffset; 
   // page index 
   const index = Math.round(nativeEvent.contentOffset.x / PAGE_WIDTH);

   if (index !== this.state.currentIndex) {
     // onPageDidChanged
   }
 };
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

在iOS中,ScrollView与可见区域的关系如下: 图片来自https://www.objc.io/issues/3-views/scroll-view/

参考:https : //www.objc.io/issues/3-views/scroll-view/


Teo*_*ors 6

如果您希望简单地获取当前位置(例如,当按下某个按钮时)而不是在用户滚动时跟踪它,那么调用onScroll侦听器将导致性能问题.目前,简单获取当前滚动位置的最高效方法是使用react-native-invoke包.这个确切的事情有一个例子,但是包还有很多其他的东西.

在这里阅读它. https://medium.com/@talkol/invoke-any-native-api-directly-from-pure-javascript-in-react-native-1fb6afcdf57d#.68ls1sopd

  • 您是否知道现在是否存在一种更干净的方式(要求低一点的hacky)来请求偏移量,或者这仍然是您所知的最佳方法?(我想知道为什么这个答案没有被采纳,因为它是唯一看起来有意义的答案) (2认同)
  • 包裹已经不在了,是不是搬到什么地方了?Github 显示 404。 (2认同)

小智 6

要在滚动结束后按照原始问题的要求获取 x/y,最简单的方法可能是:

<ScrollView
   horizontal={true}
   pagingEnabled={true}
   onMomentumScrollEnd={(event) => { 
      // scroll animation ended
      console.log(e.nativeEvent.contentOffset.x);
      console.log(e.nativeEvent.contentOffset.y);
   }}>
   ...content
</ScrollView>
Run Code Online (Sandbox Code Playgroud)