在 React Native 中获取 ScrollView 的当前滚动位置
Get current scroll position of ScrollView in React Native
是否可以获取当前滚动位置,或者 React Native 中 <ScrollView>
组件的当前页面?
所以像这样:
<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>
我相信contentOffset
会给你一个包含左上角滚动偏移量的对象:
http://facebook.github.io/react-native/docs/scrollview.html#contentoffset
试试。
<ScrollView onScroll={this.handleScroll} />
然后:
handleScroll: function(event: Object) {
console.log(event.nativeEvent.contentOffset.y);
},
在另一种情况下,假设您还想实现分页
指标,你会想通过这样做更进一步:
<ScrollView
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x:
scrollX } } }], {listener: (event) => handleScroll(event)})}
scrollEventThrottle={16}
>
...some content
</ScrollView>
其中 scrollX 是一个可用于分页的动画值,您的 handleScroll 函数可以采用以下形式:
const handleScroll = (event) => {
const positionX = event.nativeEvent.contentOffset.x;
const positionY = event.nativeEvent.contentOffset.y;
};
Brad Oyler 的回答是正确的。但是您只会收到一个事件。如果你需要不断更新滚动位置,你应该设置 scrollEventThrottle
属性,像这样:
<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 只有一个。
至于页面,我正在开发一个高阶组件,它基本上使用上述方法来完成此操作。当您深入了解初始布局和内容更改等细微之处时,实际上只需要一点时间。我不会声称已经做到了 'correctly',但在某种意义上,我认为正确的答案是使用能够谨慎且始终如一地执行此操作的组件。
参见:react-native-paged-scroll-view。希望得到反馈,即使是我做错了!
如果您希望简单地获取当前位置(例如,当某个按钮被按下时)而不是在用户滚动时跟踪它,那么调用 onScroll
侦听器将导致性能问题。目前,简单获取当前滚动位置的最有效方法是使用 react-native-invoke 包。这个确切的事情有一个例子,但是这个包做了很多其他的事情。
要按照原始问题的要求在滚动结束后获得 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>
免责声明:以下内容主要是我自己在 React Native 0.50 中的实验结果。 ScrollView
documentation 目前缺少下面涵盖的很多信息;例如 onScrollEndDrag
完全没有记录。由于这里的一切都依赖于未记录的行为,很遗憾,我不能保证这些信息在一年甚至一个月后仍然正确。
此外,下面的所有内容都假定为纯垂直滚动视图,我们感兴趣的是其 y 偏移量;如果需要,转换为 x 偏移量,希望对 reader.
来说是一个简单的练习
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),你还需要处理滚动视图中 content 的变化,那真是一团糟。
最简单的情况是如果你只需要处理Android;只需使用 onScroll
:
<ScrollView
onScroll={event => {
this.yOffset = event.nativeEvent.contentOffset.y
}}
>
要额外支持 iOS,如果 你很乐意在每一帧触发 onScroll
处理程序并接受它的性能影响,并且 如果你不需要处理frame的变化,那就稍微复杂一点:
<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}
>
但是如果我们想要处理框架变化(例如,因为我们允许设备旋转,改变滚动视图框架的可用高度)and/or 内容变化,那么我们必须另外实现两者 onContentSizeChange
and onLayout
跟踪滚动视图的框架及其内容的高度,从而不断计算 最大 可能的偏移量并推断偏移量何时因框架而自动减少或内容大小更改:
<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% 确定它在您同时 更改滚动视图的框架和内容的大小的情况下总是能正常工作。但这是我能想到的最好的,直到 this feature gets added within the framework itself,我认为这是任何人都能做到的最好的。
这就是最终从视图中实时获取当前滚动位置的方式。我所做的只是使用滚动事件侦听器和 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} />
)
}
}
以上答案告诉我们如何使用不同的 API、onScroll
、onMomentumScrollEnd
等来获取位置;如果你想知道页面索引,你可以使用偏移值来计算它。
<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与可见区域的关系如下:
使用onScroll进入死循环。可以改用 onMomentumScrollEnd 或 onScrollEndDrag
<ScrollView style={{ height: hp('70%'), width: '100%' }} showsVerticalScrollIndicator={false}
ref={myRef}
// for getting scrollView offset ->
onScroll={(event => setOffset(event.nativeEvent.contentOffset.y))}
// for giving scrollView offset ->
onContentSizeChange={() =>
myRef.current.scrollTo({
x: 0,
y: Offset
})
}
// pagingEnabled={true}
// scrollEventThrottle={16}
>
<滚动视图
showsVerticalScrollIndicator={false}
ref={myRef}
onScroll={(event => setOffset(event.nativeEvent.contentOffset.y))}
onContentSizeChange={() =>
myRef.current.scrollTo({
x: 0,
y: Offset
})
}
是否可以获取当前滚动位置,或者 React Native 中 <ScrollView>
组件的当前页面?
所以像这样:
<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>
我相信contentOffset
会给你一个包含左上角滚动偏移量的对象:
http://facebook.github.io/react-native/docs/scrollview.html#contentoffset
试试。
<ScrollView onScroll={this.handleScroll} />
然后:
handleScroll: function(event: Object) {
console.log(event.nativeEvent.contentOffset.y);
},
在另一种情况下,假设您还想实现分页 指标,你会想通过这样做更进一步:
<ScrollView
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x:
scrollX } } }], {listener: (event) => handleScroll(event)})}
scrollEventThrottle={16}
>
...some content
</ScrollView>
其中 scrollX 是一个可用于分页的动画值,您的 handleScroll 函数可以采用以下形式:
const handleScroll = (event) => {
const positionX = event.nativeEvent.contentOffset.x;
const positionY = event.nativeEvent.contentOffset.y;
};
Brad Oyler 的回答是正确的。但是您只会收到一个事件。如果你需要不断更新滚动位置,你应该设置 scrollEventThrottle
属性,像这样:
<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 只有一个。
至于页面,我正在开发一个高阶组件,它基本上使用上述方法来完成此操作。当您深入了解初始布局和内容更改等细微之处时,实际上只需要一点时间。我不会声称已经做到了 'correctly',但在某种意义上,我认为正确的答案是使用能够谨慎且始终如一地执行此操作的组件。
参见:react-native-paged-scroll-view。希望得到反馈,即使是我做错了!
如果您希望简单地获取当前位置(例如,当某个按钮被按下时)而不是在用户滚动时跟踪它,那么调用 onScroll
侦听器将导致性能问题。目前,简单获取当前滚动位置的最有效方法是使用 react-native-invoke 包。这个确切的事情有一个例子,但是这个包做了很多其他的事情。
要按照原始问题的要求在滚动结束后获得 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>
免责声明:以下内容主要是我自己在 React Native 0.50 中的实验结果。 ScrollView
documentation 目前缺少下面涵盖的很多信息;例如 onScrollEndDrag
完全没有记录。由于这里的一切都依赖于未记录的行为,很遗憾,我不能保证这些信息在一年甚至一个月后仍然正确。
此外,下面的所有内容都假定为纯垂直滚动视图,我们感兴趣的是其 y 偏移量;如果需要,转换为 x 偏移量,希望对 reader.
来说是一个简单的练习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),你还需要处理滚动视图中 content 的变化,那真是一团糟。
最简单的情况是如果你只需要处理Android;只需使用 onScroll
:
<ScrollView
onScroll={event => {
this.yOffset = event.nativeEvent.contentOffset.y
}}
>
要额外支持 iOS,如果 你很乐意在每一帧触发 onScroll
处理程序并接受它的性能影响,并且 如果你不需要处理frame的变化,那就稍微复杂一点:
<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}
>
但是如果我们想要处理框架变化(例如,因为我们允许设备旋转,改变滚动视图框架的可用高度)and/or 内容变化,那么我们必须另外实现两者 onContentSizeChange
and onLayout
跟踪滚动视图的框架及其内容的高度,从而不断计算 最大 可能的偏移量并推断偏移量何时因框架而自动减少或内容大小更改:
<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% 确定它在您同时 更改滚动视图的框架和内容的大小的情况下总是能正常工作。但这是我能想到的最好的,直到 this feature gets added within the framework itself,我认为这是任何人都能做到的最好的。
这就是最终从视图中实时获取当前滚动位置的方式。我所做的只是使用滚动事件侦听器和 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} />
)
}
}
以上答案告诉我们如何使用不同的 API、onScroll
、onMomentumScrollEnd
等来获取位置;如果你想知道页面索引,你可以使用偏移值来计算它。
<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与可见区域的关系如下:
使用onScroll进入死循环。可以改用 onMomentumScrollEnd 或 onScrollEndDrag
<ScrollView style={{ height: hp('70%'), width: '100%' }} showsVerticalScrollIndicator={false}
ref={myRef}
// for getting scrollView offset ->
onScroll={(event => setOffset(event.nativeEvent.contentOffset.y))}
// for giving scrollView offset ->
onContentSizeChange={() =>
myRef.current.scrollTo({
x: 0,
y: Offset
})
}
// pagingEnabled={true}
// scrollEventThrottle={16}
>
<滚动视图
showsVerticalScrollIndicator={false}
ref={myRef}
onScroll={(event => setOffset(event.nativeEvent.contentOffset.y))}
onContentSizeChange={() =>
myRef.current.scrollTo({
x: 0,
y: Offset
})
}