Implementing a paginated slider in react native - react-native

Looking for ideas on how to implement a Netflix.com like slider in react native.
If I use a ScrollView, user can scroll all the way to right with one single momentum scroll. But Netflix slider scrolls only 6 (itemsPerRow) items at a time. So, ScrollView may not be ideal. I will use general View instead of ScrollView.
<View style={styles.slider}>
<SliderControl pressHandler={loadPrevItems}>
<Text>{'<'}</Text>
</SliderControl>
<Animated.View
style={
{
transform: [{
translateX: interpolatedAnimation,
}],
}
}
>
{renderContent()}
</Animated.View>
<SliderControl pressHandler={loadNextItems}>
<Text>{'>'}</Text>
</SliderControl>
</View>
The slider controls are tied to onPress event handlers. The handlers adjusts the animation value as needed. When the slider is re-rendered, it is animated (slide to left/right) and shows next/prev items. I figured that they are using transform translateX animation for this. This is easily achievable with Animated API
const animation = useRef(new Animated.Value(0)).current;
const interpolatedAnimation = useMemo(() => {
return animation.interpolate({
inputRange: [0, 100],
outputRange: [0, sliderWidth],
});
}, [animation, sliderWidth]);
useEffect(() => {
Animated.timing(animation, {
toValue: moveDirection === 'right ? -100 : 100,
duration: 750,
useNativeDriver: true,
}).start();
});
Along with the Next/Prev button, Netflix slider also supports the trackpad scroll events. If we scroll slightly, the slider doesn't move at all. But we do a momentum scroll, it slides exactly 6 (itemsPerRow) items.
A general View doesn't support scroll event handlers to listen to trackpad scroll action. Not sure how I can achieve this part
ScrollView - lot of unwanted scrolling
View - No support for scroll event listener
Any ideas?

Related

React Native Testing Library: scroll FlatList to bottom

I have a react-native FlatList component with onEndReached event used to load more items.
I am testing the component with react-native-testing-library. The test is as follows:
Component mounted.
Requests 10 items to mockedBackend.
Expect FlatList to render those 10 items.
fireEvent onEndReached to ask for next page with some more items.
Expect FlatList to render new items.
Test was failing because FlatList was not rendering new items even thought FlatList's data property was being updated (I used act and waitFor as needed).
I managed to make the test pass when I fired onScroll event to bottom after some tries with hardcoded values.
Is there a way to make a helper to scroll to bottom taking into account proper FlatList size?
Helper to improve:
export const scrollListToBottom = (list: ReactTestInstance) => {
// FIXME: improve to get real list size
// After some tries these hardcoded values allowed to pass the test
act(() => {
fireEvent.scroll(list, {
nativeEvent: {
contentSize: {height: 500, width: 100},
contentOffset: {y: 400, x: 0},
layoutMeasurement: {height: 100, width: 100},
}
})
})
// FIXME: if onScroll works perfectly is this even needed?
act(() => {
fireEvent(list, 'onEndReached')
})
}
Thank you.

Bidirectional infinite scrolling with FlatList?

Has anyone had experience doing bidirectional infinite scrolling with FlatList (or any other component)?
My use case is I have a calendar month view that I want to be able to swipe left and right to get the next and previous month.
FlatList with onEndReached and onEndReachedThreshold works great for one direction, but there is no similar thing for the start. Any suggestions?
You can use the onScroll props on the Flatlist for that: https://reactnative.dev/docs/scrollview#onscroll
onScroll={({
nativeEvent: {
contentInset: { bottom, left, right, top },
contentOffset: { x, y },
contentSize: { height: contentHeight, width: contentWidth },
layoutMeasurement: { height, width },
zoomScale,
},
}) => {
if ( y <= your_start_threshold) {
// Do what you need to do on start reached
}
}}
Only problem is that React native doesn't support bidirectional FlatList on Android. This means that when you add items to the start of your list, it will jump to the top instead of keeping the current items in view (https://github.com/facebook/react-native/issues/25239).
(iOs does have a maintainVisibleContentPosition props)

Title animations on screen transition in custom Header component?

I have my own Header component that has a search field and some other features unique to my app. I simply define it on the navigationOptions for each screen.
SomeScreen.navigationOptions = (props) => ({
header: <Header {... props} />,
});
What I want to do though is animate the title inside the component itself as it moves from screen to screen? In general how can I accomplish this with a custom header? I've looked at the react-navigation-stacks implementation of Header but I can't make much sense of it.
Depending on your version of stack navigator, you'll need to check the props you receive. First of all, pass a function returning an element to the header option so that you receive the props:
SomeScreen.navigationOptions = {
header: props => <Header {...props} />
};
If you're on react-navigation-stack#2.x (alpha), you get a prop called scene. It's an object containing a progress property which has 3 properties which are reanimated nodes.
current: represents the progress of the screen that the header, it ranges from 0 to 1, where 0 means the screen is fully hidden, and 1 means the screen is fully shown.
next: similar to current, but for the next screen in the stack. can be undefined when there's no next screen
previous: similar to current, but for the previous screen in the stack. can be undefined when there's no previous screen
You can interpolate on these values to animate your view. For example, say you want to animate the opacity to start from 0.5 and to 1 as screen comes in:
import Animated from "react-native-reanimated";
// ...
<Animated.Text
style={{
opacity: Animated.interpolate(scene.progress.current, {
inputRange: [0, 1],
outputRange: [0.5, 1]
})
}}
/>;
If you're on react-navigation-stack#1.x, you get a prop called position. It's an animated node which represents the current position of the stack (ranges from 0 to current index). You can interpolate on it to animate your header. It can be a bit tricky, so you'll have to play around.
For example, say you want to animate the opacity to start from 0.5 and to 1 as screen comes in:
NOTE: I haven't tested this one so the code might be wrong. So you'll have to play around with it.
import { Animated } from "react-native";
// ...
<Animated.Text
style={{
opacity: position.interpolate({
inputRange: [index - 1, index],
outputRange: [0.5, 1]
})
}}
/>;

React Native Animated API - combine translate and rotation

Encountered this issue recently when I work with react native animated API.
As the image shows, a card component is positioned at top left corner, its flip animation state is controlled by the rotateY value, moving animation is controlled by translateX and translateY values.
It seems the rotation pivot point always been set to the card's original position. After the card has been moved (changing the translateX and translateY value), the card flip rotation animates reference its original position.
It there a way to adjust the rotation pivot point? Alternatively, is there a way to animate component's position instead of translation? Thanks.
Got it working finally. Turns out you can animate the component position change without using the translate property, by adding a listener to the animated value and updating the component state accordingly:
in the constructor, setup card component initial position and cardPos animated value.
in the componentDidMount function, attach listeners to the animated values. when animated values change, update the component state.
in the render function set the component root value style to position:"absolute" and actual position sync to the values in component's state.
constructor(props){
super(props);
// set card initial position as component state
this.state = {
cardPosX: this.props.position.x,
cardPosY: this.props.position.y
};
this.flipAnimatedValue = new Animated.Value(
this.props.isFacingUp ? 180 : 0
);
this.flipAnimatedInterpolate = this.flipAnimatedValue.interpolate({
inputRange: [0, 90, 180],
outputRange: ["0deg", "90deg", "0deg"]
});
// create animated value for card Position X and Y
this.cardPosXAnimatedValue = new Animated.Value(this.props.position.x);
this.cardPosYAnimatedValue = new Animated.Value(this.props.position.y);
}
componentDidMount() {
// addListener for cardPos Animated Value
// when animated values change, update the component state
this.cardPosXAnimatedValue.addListener(({ value }) => {
this.setState({ cardPosX: value });
});
this.cardPosYAnimatedValue.addListener(({ value }) => {
this.setState({ cardPosY: value });
});
}
render(){
return (
<View
style={{
width: this.cardWidth,
height: this.cardHeight,
position: "absolute",
top: this.state.cardPosY, //card position sync with animated value
left: this.state.cardPosX
}}
>
... //child components
</View>
);
}

ScrollView onScroll event

react-native: 0.50.3
VIDEO: https://youtu.be/Piz30mH4o1s
I want to hide or show navbar on scroll. When user scrolls down, I change the height of navbar and 'paddingTop' position of content. After that 'onScroll' event triggering several times, even if user didn't scroll.
How can I deal with this?
I will show u a similar exemple ı hope it helps:
I assume that you have header component and mainPage components. In mainPage you can pass scrollY value to the header component with onScroll method like this:
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
y:
this.refs.header === undefined
? 0
: this.refs.header.state.scrollY
}
}
}
])}
And than in header component, you can interpolate your your scrollY value to change headerHeight or header padding style like this
const headerHeight = this.state.scrollY.interpolate({
inputRange: [0, HEADER_SCROLL_DISTANCE],
outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT],
extrapolate: "clamp"
});
Than u have to change style your animated.view like this:
<Animated.View style={[styles.someStyle, {height: headerHeight}]}></Animated.View>
Feel free to ask any question to me.