Get current scroll position of ScrollView in React Native
Try.
<ScrollView onScroll={this.handleScroll} />
And then:
handleScroll: function(event: Object) {
console.log(event.nativeEvent.contentOffset.y);
},
In another context, let's say you also want to implement a pagination
indicator, you'll want to go further by doing this:
<ScrollView
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x:
scrollX } } }], {listener: (event) => handleScroll(event)})}
scrollEventThrottle={16}
>
...some content
</ScrollView>
where scrollX would be an animated value you can use for you pagination and your handleScroll function can take the form:
const handleScroll = (event) => {
const positionX = event.nativeEvent.contentOffset.x;
const positionY = event.nativeEvent.contentOffset.y;
};
How to keep scroll position after state update in React Native ScrollView
create a ref variable
const ScrollViewRef = useRef();
then in ScrollView write like this
<ScrollView
ref={ScrollViewRef}
// onLayout will make sure it scroll to Bottom initially
onLayout={() => ScrollViewRef.current.scrollToEnd()}
// We don't need `onContentSizeChanged`
// this onScroll fetches data when scroll reaches top
// then it scrolls to last position as you asked
onScroll={({ nativeEvent }) => {
if (ifCloseToTop(nativeEvent)) {
handleLoadMore()
ScrollViewRef.current.scrollTo({
y: nativeEvent.layoutMeasurement.height,
x: 0,
animated: false,
});
}
}}
scrollEventThrottle={400}>
<View style={styles.head}>
// messages
</View>
</ScrollView>
Don't forget to import useRef
at the top
import {useRef} from "react-native";
Working Example here
react-native : change view corresponding to scroll position
I guess there's no direct way in RN if you want to animated a change of view, however, in your case I can think of a little trick using the mix of opacity
, position: absolute
and interpolate()
, here is a minimal example which you can directly copy and paste to test it:
import React, { Component } from 'react';
import { StyleSheet, Animated, View, ScrollView } from 'react-native';
class AnimationExample extends Component {
constructor(props) {
super(props)
this.state = {
showBlueView: false,
animatedOpacityValue: new Animated.Value(0),
}
}
handleScroll = (event) => {
const { animatedOpacityValue, showBlueView } = this.state;
const scrollPosition = event.nativeEvent.contentOffset.y;
if (scrollPosition > 100 && !showBlueView) {
Animated.timing(animatedOpacityValue, {
toValue: 1,
}).start(() => this.setState({ showBlueView: true }))
}
if (scrollPosition < 100 && showBlueView) {
Animated.timing(animatedOpacityValue, {
toValue: 0,
}).start(() => this.setState({ showBlueView: false }))
}
}
render() {
const { animatedOpacityValue } = this.state;
return (
<ScrollView
style={styles.scrollView}
onScroll={this.handleScroll}
scrollEventThrottle={16}
>
<View style={styles.green} />
<View style={styles.animatedViewsPositioner}>
<Animated.View
style={{
...styles.red,
opacity: animatedOpacityValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
}),
}}
/>
<Animated.View
style={{
...styles.blue,
opacity: animatedOpacityValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
}),
}}
/>
</View>
</ScrollView>
)
}
}
const styles = StyleSheet.create({
scrollView: {
flex: 1,
},
green: {
height: 600,
width: '100%',
backgroundColor: 'green',
},
red: {
height: 300,
width: '100%',
backgroundColor: 'red',
},
blue: {
position: 'absolute',
height: 300,
width: '100%',
backgroundColor: 'blue',
},
animatedViewsPositioner: {
position: 'relative',
},
})
In the example above, I first access the scroll position by applying a handleScroll
function to the scrollView
. Make sure you have scrollEventThrottle
set to 16 to ensure the function is triggered every second, but beware of possible performance issue caused by that (if you care, you might take a look at this for more info).
To achieve a view change triggered when user scroll to certain position (which is actually not, but it looks like that), I use a view
to wrap both red and blue views, the red one is default with opacity: 1
, while the blue one with default opacity: 0
, sitting on top of the red one.
I hide the red view and show the blue one by animating their opacity
using interpolate()
. With the help of that, both opacity values are controlled by one animatedValue animatedOpacityValue
put in the state. I added a state showBlueView
to optimise the performance by avoid constantly setting states triggered by onScroll.
Here's an update to add touchableOpacities
on both views, simply achieve by hiding the blue view when it's unused.
First, add a log function:
log = (stringToPrint) => () => {
console.log(stringToPrint)
}
Next, change the scrollView
like this by adding two touchableOpacity
<ScrollView
style={styles.scrollView}
onScroll={this.handleScroll}
scrollEventThrottle={16}
>
<View style={styles.green} />
<View style={styles.animatedViewsPositioner}>
<Animated.View
style={{
...styles.red,
opacity: animatedOpacityValue.interpolate({
inputRange: [0, 1],
outputRange: [1, 0],
}),
}}
>
<TouchableOpacity
style={{ backgroundColor: 'black', width: 80, height: 30 }}
onPress={this.log('click on red')}
/>
</Animated.View>
{showBlueView && (
<Animated.View
style={{
...styles.blue,
opacity: animatedOpacityValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
}),
}}
>
<TouchableOpacity
style={{ backgroundColor: 'black', width: 80, height: 30 }}
onPress={this.log('click on blue')}
/>
</Animated.View>
)}
</View>
</ScrollView>
Note that I added showBlueView &&
to hide the blue view when its opacity is 0, so that it will not block any click event applied to the red view (even though the blue view is hidden, it is actually on top of the red view with opacity: 0
).
react native scrollview scroll position
I would suggest using a FlatList
combined with a KeyboardAvoidingView
. The header must be placed differently and I've cleaned up the code a little bit. Notice that my useEffect
is only triggered once in order to fill the test data. I use TypeScript as well, so you might remove typing.
The main "trick" for the KeyboardAvoidingView
is the property behavior
which differs between iOS and Android.
Consider this codesnippet.
behavior={Platform.OS === "ios" ? "padding" : "height"}
And here is a full working version.
import React, { useEffect, useRef, useState } from "react"
import { FlatList, KeyboardAvoidingView, Text, TextInput, View } from "react-native"
type Message = {
id?: number
message?: string
date?: Date
}
export const Test = () => {
const [messages, setMessages] = useState<Message[]>()
const flatListRef = useRef<FlatList>(null)
// only for test
useEffect(() => {
setMessages([
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
{ id: 5 },
{ id: 6 },
{ id: 7 },
{ id: 8 },
{ id: 9 },
{ id: 10 },
{ id: 11 },
{ id: 12 },
])
}, [])
return (
<View style={{ flex: 1, backgroundColor: "red", paddingTop: 30 }}>
<View style={{ padding: 15, justifyContent: "center" }}>
<Text>HEADER</Text>
</View>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={{ flex: 1, backgroundColor: "#111111" }}>
<FlatList
data={messages}
ref={flatListRef}
onContentSizeChange={() => flatListRef.current.scrollToEnd({ animated: true })}
onLayout={() => flatListRef.current.scrollToEnd({ animated: true })}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text style={{ color: "white", flex: 1, paddingVertical: 20 }}>{item.id}</Text>}
/>
<View style={{ backgroundColor: "black", flex: 1, marginBottom: 40 }}>
<TextInput
style={{
backgroundColor: "#2E2E2E",
width: "100%",
borderRadius: 18,
height: 36,
paddingLeft: 10,
paddingRight: 10,
color: "#FFFFFF",
}}
/>
</View>
</KeyboardAvoidingView>
</View>
)
}
The above results in the correct behavior. However, I've only tested this on iOS. Android might behave differently.
Using FlatList
is not a necessity but usually advised.
Here is a screeshot.
Edit: For automatically scrolling the flatlist content if the keyboard is opened, add the following props to the flatlist.
ref={flatListRef}
onContentSizeChange={() => flatListRef.current.scrollToEnd({animated: true })}
onLayout={() => flatListRef.current.scrollToEnd({ animated: true })}
The flatListRef
is a ref which can be set as follows.
const flatListRef = useRef<FlatList>(null)
I have updated the full working example above.
react native PanResponder to get current scrollView position
Assuming that by touch release you mean the moment when one touch stops existing, you can do that with ScrollVIew's onTouchEnd prop.
I would do that by using onScroll to save the offset somewhere and onTouchEnd to do something with that offset.
<ScrollView
onScroll={(event) => {
this.offsetY = event.nativeEvent.contentOffset.y;
}}
onTouchEnd={(event) => {
console.log('offsetY:', this.offsetY);
console.log('touch info:', event.nativeEvent);
}}
>
{content of the scroll view}
</ScrollView>
If by touch release you meant that there should be zero touches, you can check that with the event that is passed to onTouchEnd.
Related Topics
React Native App Works on Debug Mode, But Not Works Release Mode on Ios
How to Force Nslocalizedstring to Use a Specific Language
How to Return Value from Alamofire
Convert Utf-8 Encoded Nsdata to Nsstring
Find Out If Character in String Is Emoji
-Didselectrowatindexpath: Not Being Called
Why It Shows "Ld: Framework Not Found Bolts"
Disabling Auto-Play in Full Screen on Ios
How to Pass Prepareforsegue: an Object
Ios Upload Image and Text Using Http Post
Objective-C Arc: Strong VS Retain and Weak VS Assign