React Native ScrollView scrollTo spring animation - react-native

I am using React Native's ScrollView's scrollTo method to animate the scroll view. How can I make this animation spring loaded, as in Animated.spring (without the use of a 3rd party library)?

Track the scroll position
<ScrollView
ref={(ref) => { this.scrollView = ref; }}
onScroll={(event) => {
this.scrollY = event.nativeEvent.contentOffset.y;
}}
scrollEventThrottle={16}
>
And then
scrollTo(y) {
if (!this.scrollY) this.scrollY = 0;
const animatedValue = new Animated.Value(this.scrollY);
const id = animatedValue.addListener(({ value }) => {
this.scrollView.scrollTo({ x: 0, y: value, animated: false });
});
Animated.spring(animatedValue, { toValue: y }).start(() => { animatedValue.removeListener(id); /* finished callback */ });
}

Related

Why is SectionList ref.current always undefined?

I am trying to use the scrollToOffset and scrollToIndex that SectionList provides. However, the SectionList ref I am using is always undefined when using ref.current.
In my class component I have the following:
private sectionListRef: any;
constructor(props: SampleScreenProps) {
super(props);
this.sectionListRef = null;
}
componentDidMount = (): void => {
setTimeout(() => this.scrollToSection(), 2000);
};
scrollToSection = (): void => {
// do some calculations to get the itemIndex and sectionIndex to scroll to
this.sectionListRef.scrollToLocation({
animated: false,
itemIndex,
sectionIndex,
viewPosition: 0.5
}); // this fails if the list is huge and the indices are far from the initial render location, and so moves on to onScrollToIndexFailed
};
onScrollToIndexFailed = (error) => {
// this.sectionListRef.current is always undefined in this function
this.sectionListRef.current?.scrollToOffset({
offset: error.averageItemLength * error.index,
animated: true
});
setTimeout(() => {
if (this.state.dataSet.length !== 0 && this.sectionListRef !== null) {
this.sectionListRef.current?.scrollToIndex({ index: error.index, animated: true });
}
}, 10);
};
finally, here's the SectionList itself:
<SectionList
inverted
ref={(ref) => {
this.sectionListRef = ref;
}}
sections={mappedSections}
renderItem={this.renderItem}
renderSectionFooter={this.renderSectionFooter}
keyExtractor={(item, index) => index.toString()}
keyboardShouldPersistTaps="never"
keyboardDismissMode="on-drag"
bounces={false}
initialNumToRender={20}
onScrollToIndexFailed={this.onScrollToIndexFailed}
/>
What is the cause of this?
Edit: I tried doing this.sectionListRef = React.createRef() in the constructor instead, but same result.
I understand scrollToOffset is part of SectionList, but the other two scroll functions are part of the underlying virtualized list, but it should still work the same as far as I know.

How to scroll FlatList to some index immediately after scrolling?

How to scroll FlatList component to some index/children after dragging the FlatList?
For Example:
As we can see in Youtube/TikTok stories, when we drag the screen the next video appears immidiatly after it. So, I am implementing it with FlatList, if we drag the item below then FlatList should move to the above item/index. So, what I am doing is that storing the currently displayed index and on onScrollEndDrag prop I am checking the position of Y, and accordingly run scrollToIndex function, but it's not working.
Reason
Because while scrolling after drag, FlatList ignores the scrollToIndex function.
Is their anyone to help me out of it???
import React from 'react';
import { View, Text, StyleSheet, AppState, FlatList, Animated, Dimensions } from 'react-native';
import fetchDataFromDirectory from '../data/fetchDataFromWhatsApp';
import PlayerVideo from '../components/VideoPlayer';
import Image from '../components/Image';
const { width, height } = Dimensions.get('window');
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
class VideoScreen extends React.Component {
state = {
pdfInfo: [], //[{id, name, path},...]
appState: '',
viewableIndex: 0
}
fetchData = async () => {
const data = await fetchDataFromDirectory('videos');
this.setState({ pdfInfo: data.pdfInfo });
}
componentDidUpdate(prevProps, prevState) {
if (this.state.pdfInfo.length > this.dataLength) { //We are seeing if we need to scroll to top or not
this.dataLength = this.state.pdfInfo.length;
try {
this.list.scrollToIndex({ animated: true, index: 0, viewPosition: 0 })
} catch (err) {
}
}
}
handleAppStateChange = (nextAppState) => {
//the app from background to front
if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
this.fetchData();
}
//save the appState
this.setState({ appState: nextAppState });
}
componentDidMount() {
this.videoHeight = height;
this.dataLength = 0;
this.fetchData();
AppState.addEventListener('change', this.handleAppStateChange);
}
onViewableItemsChanged = ({ viewableItems, changed }) => {
// console.log("Visible items are", viewableItems);
// console.log("Changed in this iteration", changed);
try {
this.setState({ viewableIndex: viewableItems[0]['index'] })
} catch (err) {
}
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange)
}
render() {
return <AnimatedFlatList
onLayout={(e) => {
const { height } = e.nativeEvent.layout;
this.videoHeight = height;
}}
// onResponderRelease={e => {console.log(e.nativeEvent.pageY)}}
// onResponderRelease={(e) => console.log(e.nativeEvent.)}
// onScrollBeginDrag
// snapToAlignment={'top'}
// onMoveShouldSetResponderCapture={(e) => {e.nativeEvent.}}
// decelerationRate={'fast'}
decelerationRate={'fast'}
scrollEventThrottle={16}
// onScroll={(e) => console.log('+++++++++++++++++',Object.keys(e), e.nativeEvent)}
onScrollEndDrag={(e) => {
// this.list.setNativeProps({ scrollEnabled: false })
console.log(e.nativeEvent)
if (e.nativeEvent.velocity.y > 0.1) {
console.log('go to above')
this.list.scrollToIndex({animated: true, index: this.state.viewableIndex - 1, viewPosition: 0})
} else if (e.nativeEvent.velocity.y < -0.9){
console.log('go to below')
this.list.scrollToIndex({animated: true, index: this.state.viewableIndex + 1, viewPosition: 0})
}
// if (e.nativeEvent.velocity.y < 0.1000 && e.nativeEvent.velocity.y >= 0) {
// this.list.scrollToIndex({animated: true, index: this.state.viewableIndex, viewPosition: 0})
// }
// else if (e.nativeEvent.velocity.y < -0.1000 && e.nativeEvent.velocity.y < 0) {
// this.list.scrollToIndex({animated: true, index: this.state.viewableIndex, viewPosition: 0})
// }
// this.list.setNativeProps({ scrollEnabled: true })
console.log('h1')
}}
viewabilityConfig={{
// itemVisiblePercentThreshold: 90,
viewAreaCoveragePercentThreshold: 60
}}
// extraData={this.state.viewableIndex}
onViewableItemsChanged={this.onViewableItemsChanged}
// scr
contentContainerStyle={styles.screen}
data={this.state.pdfInfo}
keyExtractor={item => item.id}
ref={ref => this.list = ref}
renderItem={({ item, index }) => {
// console.log(index)
return <PlayerVideo
source={item.path}
refList={this.list}
height={this.videoHeight}
index={index}
isViewable={this.state.viewableIndex == index ? true : false} />
}}
</View>
}}
/>
}
}
export default VideoScreen;
const styles = StyleSheet.create({
screen: {
backgroundColor: '#111212',
// flex: 1
}
})
First add a ref flatListRef to the flatlist:
<Flatlist
ref={(ref) => this.flatListRef = ref}
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
onScrollBeginDrag={onBeginScroll}/>
/>
Then define the onScrollBeginDrag function as below to scroll to a specific index:
onBeginScroll = () => {
this.flatListRef._listRef._scrollRef.scrollToIndex({ animating: true, index: [YOUR_INDEX_HERE] });
}
yes you should access the element _listRef then _scrollRef then call the scrollToIndex
react-native 0.64.1
react 17.0.2

React-Native Animated does not work near setState

let animatedHeight = new Animated.Value(50);
const animate = () => {
animatedHeight.setValue(50);
Animated.timing(animatedHeight, {
toValue: 0,
duration: 200,
useNativeDriver: true
}).start();
};
const handleSubmit = async (values:ILoginProps) => {
setLoading(true);
axios.post('http://127.0.0.1:3333/api/v1/auth/login', values)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
animate();
setLoading(false);
})
}
<Animated.View style={[style.errorContainer, {transform: [{translateY: animatedHeight}]}]}>
<Text style={style.errorText}>Credentials not found!</Text>
</Animated.View>
I have this code, first I set a loading state and then execute a animate function which runs the Animated function to create the animation effect.
When i have setLoading(true) before animate(), the animation doesn't happen.
I really have no idea why this happens and no idea how to solve this
Use useRef hook to wrap your animated value. React will keep tracking of its value after re-rendering, in other case you might lose it.
let animatedHeight = React.useRef(new Animated.Value(50)).current;

FlatList scrollToIndex method does not scroll to correct index

Actual Behaviour :
I'm working on a podcasts playing application in react-native and implementing track player in it. When i play a podcast in the player screen, tap on next icon twice and then navigate to the topic screen of the podcast being played in the player screen currently, then i'm required to scroll to the podcast item being played from the FlatList in the topic screen and show it as highlighted.
For this i have used scrollToIndex method of FlatList in conjunction with getItemLayout prop of FlatList and passed the index of the current podcast item being played to the method. But the method is not working as expected and is scrolling to wrong position. I have also tried many workarounds with no use.
Expected Behaviour :
I'm supposed to scroll to the particular podcast item being played from the topic screen in the player screen and show it as highlighted. Can someone help me please. Thanks in advance.
What i have tried :
<FlatList
showsVerticalScrollIndicator={false}
style={{ marginLeft: 20, marginRight: 20 }}
ref={(ref) => {
this.flatListRef = ref;
}}
initialScrollIndex={insights.length > 50 ? 50 : 40}
initialNumToRender={2}
getItemLayout={this.getItemLayout}
data={insights}
extraData={this.state}
stickyHeaderIndices={[0]}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
ListHeaderComponent={playAll ? null : this._renderHeader}
ItemSeparatorComponent={ListSeparator}
contentContainerStyle={{...Styles.listBottomSpace}}
legacyImplementation={false}
windowSize={20}
maxToRenderPerBatch = {1}
removeClippedSubviews={false}
onScrollToIndexFailed={(error) => {
this.flatListRef.scrollToOffset({ offset: error.averageItemLength *
error.index, animated: false });
setTimeout(() => {
if (insights.length !== 0 && this.flatListRef !== null) {
this.flatListRef.scrollToIndex({ index: error.index, animated: false
});
}
}, 100);
}}
/>
getItemHeight = (event) => {
if (!this.itemHeight) {
const { height } = event.nativeEvent.layout;
this.itemHeight = height;
}
};
getItemLayout = (data, index) => {
let height = this.itemHeight ? this.itemHeight : 100;
return { length: height, offset: height * index, index };
};
scrollToItem = (insights) => {
const { currentTrack } = this.props;
let activeInsight = currentTrack ? lodash.findIndex(insights, (insight) => {
return (insight.insight_id === Number(currentTrack.id) && insight.insight_title ===
currentTrack.title)
}) : -1;
this.flatListRef &&
activeInsight > -1 &&
this.flatListRef.scrollToIndex({
animated: false,
index: activeInsight,
viewPosition: 0.5,
})
};
Environment :
"react": "16.8.3",
"react-native": "0.59.9",
"MacOS": "10.15.3"
Have a look at this usage of scrollToIndex:
https://snack.expo.io/#s2gt05/flatlist-scrolltoindex-practical-example.-
I had the same issue only on iOS. Sometimes the scrollToIndex function wont scroll to the specific index.
Somehow the solution for me was to send the index as string not as int. in Adrian-Gapriel Peslar solution you will see that he is using "" + index too.

How do we set speed to scrollTo() in React Native?

I'm scroll the page on click of a button using:
this.scrollTo({y: height, x: 0, animated: true})
The scroll works fine, however I'd like to slow down the scroll animation.
How do we do that?
This is a pretty neat solution that uses the scrollview's content height to scroll an entire view (on mount). However, the same trick can be used (add a listener to an animated value) to create a scroll function that can be triggered by some event at any given moment (to any given value).
import { useEffect, useRef, useState } from 'react'
import { Animated, Easing, ScrollView } from 'react-native'
const SlowAutoScroller = ({ children }) => {
const scrollRef = useRef()
const scrollAnimation = useRef(new Animated.Value(0))
const [contentHeight, setContentHeight] = useState(0)
useEffect(() => {
scrollAnimation.current.addListener((animation) => {
scrollRef.current &&
scrollRef.current.scrollTo({
y: animation.value,
animated: false,
})
})
if (contentHeight) {
Animated.timing(scrollAnimation.current, {
toValue: contentHeight,
duration: contentHeight * 100,
useNativeDriver: true,
easing: Easing.linear,
}).start()
}
return () => scrollAnimation.current.removeAllListeners()
}, [contentHeight])
return (
<Animated.ScrollView
ref={scrollRef}
onContentSizeChange={(width, height) => {
setContentHeight(height)
}}
onScrollBeginDrag={() => scrollAnimation.current.stopAnimation()}
>
{children}
</Animated.ScrollView>
)
}
On android you can use the smoothScrollTo option