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


115

是否可以获取当前滚动位置或<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>

相关:GitHub问题,建议添加获取此信息的便捷方法。
Mark Amery

Answers:


207

尝试。

<ScrollView onScroll={this.handleScroll} />

然后:

handleScroll: function(event: Object) {
 console.log(event.nativeEvent.contentOffset.y);
},

5
滚动值更改时,这不会总是触发。例如,如果调整了scrollview的大小,现在它可以立即在屏幕上显示整个内容,那么您似乎并不总是会收到一个onScroll事件告诉您。
Thomas Parslow

19
或者,event.nativeEvent.contentOffset.x如果您有水平的ScrollView;)谢谢!
Tieme'9

2
这也可以在Android中使用。
Janaka Pushpakumara

4
scrollEventThrottle令人沮丧的是,除非您设置为16或更低(以获取每个单帧事件),否则无法保证它会报告最后一帧的偏移量-这意味着要确保滚动完成后您拥有正确的偏移量,您需要设置scrollEventThrottle={16}并接受其对性能的影响。
Mark Amery

@bradoyler:是否可以知道的最大值event.nativeEvent.contentOffset.y
艾萨克(Isaac)

55

免责声明:以下内容主要是我自己在React Native 0.50中进行实验的结果。该ScrollView文档当前缺少许多下面介绍的信息。例如onScrollEndDrag完全没有文件记录。由于此处的所有内容都依赖于未记录的行为,因此,我不能保证此信息将在一年甚至一个月后保持正确。

同样,下面的所有内容都假定为纯垂直滚动视图,我们感兴趣的是y偏移量;如果需要的话,希望将其转换为x偏移量对于读者来说是一个容易的练习。


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

onScroll

在Android上

在用户滚动时触发每个帧,在用户释放滚动视图后滑动视图时在每个帧上触发,在滚动视图静止时在最后一个帧上触发,并且在滚动视图的偏移因其帧而改变时触发变化(例如由于从横向旋转到纵向)。

在iOS上

在用户拖动时或滚动视图滑动时触发,以决定的某个频率,scrollEventThrottle每帧最多至一次scrollEventThrottle={16}如果用户在有足够的动量可以滑动时释放滚动视图,则onScroll在滑动后静止时,处理程序也会触发。但是,如果用户在静止状态下拖动然后释放滚动视图,onScroll则除非已设置为触发滚动的每一帧,否则不能保证为最终位置触发。scrollEventThrottleonScroll

scrollEventThrottle={16}通过设置较大的数字可以降低设置的性能成本。但是,这onScroll将不会触发所有帧。

onMomentumScrollEnd

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

onScrollEndDrag

当用户停止拖动滚动视图时触发,无论滚动视图是保持静止还是开始滑动。


考虑到这些行为上的差异,跟踪偏移量的最佳方法取决于您的具体情况。在最复杂的情​​况下(您需要支持Android和iOS,包括处理ScrollView由于旋转而导致的框架更改,并且您不希望将Android的性能从设置scrollEventThrottle为16降到最低),并且需要处理也更改了滚动视图中的内容,那真是一团糟。

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

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
>

为了额外支持iOS,如果您乐于在onScroll每个框架上触发处理程序并接受其性能影响,并且如果您不需要处理框架更改,那么只会稍微复杂一点:

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  scrollEventThrottle={16}
>

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

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  onScrollEndDrag={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  scrollEventThrottle={160}
>

但是,如果我们要处理框架更改(例如,因为我们允许设备旋转,更改滚动视图框架的可用高度)和/或内容更改,则我们必须另外实现两者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}
>

是的,这很恐怖。我也不是100%肯定会在您同时更改滚动视图的框架大小和内容的情况下始终正常工作。但这是我能想到的最好的方法,并且直到在框架本身中添加此功能之前,我认为这是任何人都能做到的最好方法。


19

布拉德·奥伊勒(Brad Oyler)的答案是正确的。但是您只会收到一个事件。如果需要不断更新滚动位置,则应设置scrollEventThrottleprop,如下所示:

<ScrollView onScroll={this.handleScroll} scrollEventThrottle={16} >
  <Text>
    Be like water my friend 
  </Text>
</ScrollView>

和事件处理程序:

handleScroll: function(event: Object) {
  console.log(event.nativeEvent.contentOffset.y);
},

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


12
这是不正确的。首先,scrollEvenThrottle仅适用于iOS,不适用于android。其次,它仅调节您接收onScroll调用的频率。默认值为每帧一次,而不是一个事件。1给您更多更新,而16给您更少。默认为0。这个答案是完全错误的。facebook.github.io/react-native/docs/…–
Teodors

1
我在React Native支持Android之前就回答了这个问题,所以是的,答案仅适用于iOS。默认值为零,这导致每次滚动视图时仅发送一次滚动事件。
cutemachine's

1
函数(事件:对象){}的es6表示法是什么?
cbartondock

1
只是function(event) { }
cutemachine '17

1
@Teodors虽然此答案不能涵盖Android,但您的其余批评则完全困惑。scrollEventThrottle={16}没有给你“少”的更新; 1-16范围内的任何数字都可以为您提供最大的更新速率。当用户开始滚动时,默认值为0(在iOS上)为您提供一个事件,然后没有后续更新(这是毫无用处的,并且可能是错误);默认值不是 “每帧一次”。除了您的评论中的这两个错误外,您的其他主张与答案中所说的没有矛盾(尽管您似乎认为它们确实如此)。
马克·阿默里

7

如果您只是想获取当前位置(例如,按下某个按钮时),而不是在用户滚动时跟踪它,那么调用onScroll侦听器将导致性能问题。当前,最简单地获取当前滚动位置的最有效方法是使用react-native-invoke程序包。确实有一个示例,但是该程序包还执行其他许多操作。

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


2
您是否知道现在是否存在一种更清洁的方式(要求低一点的hacky)来请求偏移量,还是您知道这仍然是最好的方式?(我想知道为什么这个答案没有被认可,因为它是唯一看起来有意义的
答案

1
包裹不再存在,它移到某个地方了吗?Github上正显示出404
Noitidart

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>

-1; onMomentumScrollEnd仅当用户放开滚动视图时,如果滚动视图具有一定的动量,才触发。如果在滚动视图不移动的情况下释放它们,那么您将永远不会获得任何动量事件。
Mark Amery

1
我测试了onMomentumScrollEnd,无法触发它是不可能的,我尝试了不同的滚动方式,尝试在滚动到最终位置时释放了触摸,并且它也被触发了。因此,据我测试,此答案是正确的。
fermmm

6

以上答案告诉了如何使用不同的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
   }
 };

在此处输入图片说明

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

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


如果要获取当前项目的“索引”,这是正确的答案。您采用event.nativeEvent.contentOffset.x / deviceWidth。谢谢你的帮助!
SaraInésCalderón19年

1

这样最终可以从视图中实时获取当前滚动位置。我正在做的是使用滚动事件监听器和scrollEventThrottle。然后,我将其作为处理我制作的滚动功能和更新道具的事件传递。

 export default class Anim extends React.Component {
          constructor(props) {
             super(props);
             this.state = {
               yPos: 0,
             };
           }

        handleScroll(event){
            this.setState({
              yPos : event.nativeEvent.contentOffset.y,
            })
        }

        render() {
            return (
          <ScrollView onScroll={this.handleScroll.bind(this)} scrollEventThrottle={16} />
    )
    }
    }

考虑到性能,在onScroll上更新状态是否很好?
rishi19年

1

使用onScroll进入无限循环。可以使用onMomentumScrollEnd或onScrollEndDrag代替


0

至于页面,我正在研究一个更高阶的组件,该组件基本上使用上述方法来做到这一点。当您深入了解诸如初始布局和内容更改之类的细微之处时,实际上只需要一点时间。我不会声称自己做得“正确”,但是从某种意义上说,我会考虑使用正确且谨慎地执行此操作的组件的正确答案。

请参阅:react-native-paged-scroll-view。希望反馈,即使是我做错了!


1
这太棒了。谢谢你这样做。我立刻发现这非常有用。
马蒂·科尔特斯

很高兴!非常感谢您的反馈!

1
抱歉-1,因为该项目公开承认它没有维护,并且该组件具有“几个丑陋的错误”
Mark Amery

感谢您的反馈@MarkAmery。:)寻找开放源代码的时间并不容易。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.