I work on a horizontal list with the component FlatList in the tvOS environment. The problem occurs on a small list of 3 elements, I set the initialScrollIndex equal to the second or last element, the good item is selected. However when I try to go back on a previous item the selection occurs but there is no scroll.
<FlatList
getItemLayout={(data, index) => ({
length: 300,
offset: 300 * index,
index,
})}
initialScrollIndex={this.props.initialScrollIndex}
keyExtractor={this._keyExtractor}
horizontal={this.props.horizontal}
scrollEnabled={true}
extraData={this.state}
ref={list => (this.myScrollView = list)}
data={this.finalData}
removeClippedSubviews={false}
renderItem={this.props.renderRow}
/>
Have you tried changing the properties of the returning object within getItemLayout from length: 300 to width: 300. I would think this should be width rather than length because you are rendering a horizontal FlatList.
getItemLayout={(data, index) => ({
width: 300, //- Here
offset: 300 * index,
index,
}
)}
Related
I have FlatList element:
<FlatList
ref={listRef}
data={data}
onLayout={_onLayoutDimensionsChanged}
keyExtractor={_keyExtractor}
renderItem={_renderItem}
initialScrollIndex={initialScrollIndex}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={{ itemVisiblePercentThreshold: 50 }}
ItemSeparatorComponent={ItemSeparator}
snapToInterval={totalItemWidth}
decelerationRate={0.0}
getItemLayout={(data, index) => ({
length: totalItemWidth,
offset: totalItemWidth * index,
index
})}
initialNumToRender={2}
showsHorizontalScrollIndicator={false}
removeClippedSubviews
bounces
horizontal
/>
Where totalItemWidth is:
totalItemWidth = itemsWidth + SEPARATOR_SIZE;
I would like to achieve that swipe gesture will move one item at maximum but even with deceleration rate set to decelerationRate={0.0} my list is swiping even 2 items if I swipe with my fingers little bit faster than normal swipe's speed is.
Is there any way to prevent double-swipe there? I'm implementing Image Gallery so it is not very user-friendly when a user can swipe two photos at once.
Thank you so much!
Use disableIntervalMomentum={ true } in your FlatList. This will only allow the user to scroll one page at a time horizontally:
<AnimatedFlatList
disableIntervalMomentum={true} // use this
contentInset={{right: wp('20%')}}
showsHorizontalScrollIndicator={false}
ItemSeparatorComponent={separatorComponent}
decelerationRate={0.0}
snapToInterval={ITEM_SIZE}
horizontal={true}
scrollEventThrottle={1}
onScroll={onScrollEvent}
data={filteredData.filteredData}
renderItem={renderItem}
keyExtractor={keyExtractor}
/>
I am trying to use onViewableItemsChanged event to detect the items of my FlatList that are currently displayed on screen.
In my ViewabilityConfig (Code is provided below), I set the itemVisiblePercentThreshold parameter to 100 which I assumed will require my item to be fully displayed to be considered viewable. However that is not the case for me.
As you can see in the following screenshot:
Screenshot of my App
It is obvious that the top most item is not completely on screen (Which should make the visible items consist only of 3 items). But when I print the length of the array in my onViewableItemsChanged event handler, it returns 4 (and when I inspect the values, including the top most item).
Log Result of Viewable Items Array Length
Is this the problem of FlatList onViewableItemsChanged event? Or did I implemented it incorrectly?
I tried to find solution from the documentation and React-native github but there is no further explanation about how this event works.
Some related snippets of my code are as follow:
FlatList Definition
<FlatList
viewabilityConfig={this.clippingListViewabilityConfig}
inverted={true}
horizontal={false}
data = {this.props.clippingResultArray}
ref={(ref) => this.clippingResultFlatList = ref}
style={{
// flexGrow:0,
// backgroundColor: 'green',
// width:'100%',
// width: Dimensions.get('window').width,
}}
contentContainerStyle={{
// justifyContent:'flex-end',
// flexGrow:0,
// flexDirection:'row',
// alignItems:'flex-end',
}}
renderItem={this.renderClippingListItemRight}
keyExtractor={(item, index) => index.toString()}
onViewableItemsChanged={this.onClippingListViewableChanged}
// removeClippedSubviews={true}
{...this._clippingListItemPanResponder.panHandlers}
/>
onViewableItemsChanged Listener
onClippingListViewableChanged = (info) => {
console.log("***************************NUMBER OF CURRENT VIEWABLE ITEMS:",info.viewableItems.length);
console.log("Item list:",info.viewableItems);
this.setState({
...this.state,
viewableItems: info.viewableItems,
});
};
Viewable Configuration
this.clippingListViewabilityConfig = {
waitForInteraction: false,
itemVisiblePercentThreshold: 100,
minimumViewTime: 500, //In milliseconds
};
I want to implement a scrollToIndex function for my flatlist. However, it prompts and out of range error. I am wondering is it related to getItemLayout inside flatlist if it is single column only.
getItemLayout = (data, index) => (
{ length: win.width*0.335, offset: (win.width*0.335)*index, index }
)
render(){
return(
<FlatList
numColumns={3}
ref={(ref) => { this.dataList = ref; }}
renderItem={({ item, index }) => this._renderItem(item, index)}
data={this.state.data}
extraData={this.state}
getItemLayout={this.getItemLayout}//required for scrollToIndex
style={{ flex: 1, paddingBottom: win.height * 0.02 }}
keyExtractor={(item, index) => index}
/>
)
}
React Native FlatList getItemLayout for multiple columns
The idea is the FlatList just needs to know for this index, how tall is it (length), how do I jump to it (offset) and pass back the current index. Not sure why they need the index as that doesn't change.
const FIXED_ITEM_HEIGHT = 100
const NUM_COLUMNS = 3
getItemLayout = (data, index) => ({
length: FIXED_ITEM_HEIGHT,
offset: FIXED_ITEM_HEIGHT * Math.floor(index / NUM_COLUMNS),
index
})
HOWEVER... This feature doesn't work as it's supposed to. It fires on scroll but the index doesn't change. There are many known issues at the time of this writing so don't use it in production.
https://github.com/facebook/react-native/issues/20467
So currently Facebook gets an unlike on this feature.
When you set numColumns above 1 ScrollView will call getItemLayout once per row instead of per item. So it's better to think of the index as "rowIndex" rather than item index.
I discovered this when my ScrollView got jumpy after loading around 17 rows of content or 51 items, and it would at certain scroll points jump 1 row up/down.
ReactNative: v0.52.0
Platform: iOS
My FlatList code:
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
legacyImplementation={false}
data={this.props.photos}
renderItem={item => this.renderPhoto(item)}
keyExtractor={photo => photo.id}
ItemSeparatorComponent={this.itemSeparatorComponent}
/>
Item separator code:
itemSeparatorComponent = () => {
return <View style = {
{
height: '100%',
width: 5,
backgroundColor: 'red',
}
}
/>
}
And finally FlatList item component:
renderPhoto = ({ item, index }) => {
return (
<View style = {{ width: SCREEN_WIDTH, height: 'auto' }}>
<FastImage
style = { styles.photo }
resizeMode = { FastImage.resizeMode.contain }
source = {{ uri: item.source.uri }}
/>
</View>
)
}
But when scrolling, the FlatList makes an offset to the separator but not to the left edge of item:
And with each new element the FlatList adds the width of the all previous separators to offset:
How to make the FlatList component consider the width of the separator component in horizontal scrolling and make proper offset?
I had the same use-case. For anyone looking for a solution, here it is.
Step 1) Don't use ItemSeparatorComponent prop. Instead, render it inline in your renderItem component.
Step 2) (Key-point). Specify the width and height in the style prop of the FlatList. The width, in your case, should be SCREEN_WIDTH + 5.
Then Flatlist will automatically move the entire screen (photo + separator) away when pagination is enabled. So now your code should be like so:-
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
legacyImplementation={false}
data={this.props.photos}
renderItem={item => this.renderPhoto(item)}
keyExtractor={photo => photo.id}
style={{width: SCREEN_WIDTH + 5, height:'100%'}}
/>
Render photo code:-
renderPhoto = ({ item, index }) => {
return (
<View style = {{ width: SCREEN_WIDTH + 5, height: 'auto',
flexDirection:'row'}}>
<FastImage
style = { styles.photo }
resizeMode = { FastImage.resizeMode.contain }
source = {{ uri: item.source.uri }}
/>
{this. itemSeparatorComponent()}
</View>
)}
Item separator code:
itemSeparatorComponent = () => {
return <View style = {
{
height: '100%',
width: 5,
backgroundColor: 'red',
}
}
/>
}
If you still can't figure it out, then look at this component:
https://github.com/zachgibson/react-native-parallax-swiper
Try to go into the implementation, you will see that this guy has provided width and height to the Animated.ScrollView.
https://github.com/zachgibson/react-native-parallax-swiper/blob/master/src/ParallaxSwiper.js
Line number: 93 - 97
The top-level view you're returning in the renderPhoto function has a width of SCREEN_WIDTH, yet the ItemSeparatorComponent, which renders in between each item, is taking up a width of 5 as per your style definition. Consequently, for each additional item you scroll to, that initial offset will become 5 more pixels on the left.
To fix this, you can either remove the ItemSeparatorComponent completely, (as you already have pagingEnabled set to true), or set the width of the top-level view returned in renderPhoto equal to SCREEN_WIDTH - 2.5. That way you'll see half of the item separator on the right edge of one photo and the other half on the left edge of the next photo.
Actually, one other possible solution could be to remove the item separator, set the renderPhoto View's width to SCREEN_WIDTH + 5, and then include these additional properties inside the style: {paddingRight: 5, borderRightWidth: 5, borderRightColor: 'red'}. That way the red separator won't be visible until scrolling left and right, because of the pagingEnabled property.
How to adjust horizontal FlatList with separators in order to skip separators when pagination is enabled. I want to see separators only when swiping between items. I tried to set it in getItemLayout but it doesn't work properly. I used getItemLayout = (_, index) => ({ length: window.width, offset: (window.width + separatorWidth) * index, index }) Behaviour looks like that
This confused me as well.
There are a few snap related properties inherited/extended from <ScrollView> that are useful here.
Checkout: snapToInterval and snapToOffsets.
If you're using a <FlatList> or <ScrollView> to act as a horizontal full-width carousel, and want to enforce snapping so that a single "page" within the list is always within view (i.e. users can't stop partially between views), these snap props are what you need.
Note: you need to disable pagingEnabled in order for these props to be respected.
Simplified example code:
render() {
const totalItemWidth = window.width + separatorWidth;
return (
<FlatList
{ /* ... other props — data, renderItem, style, etc ... */}
horizontal
showsHorizontalScrollIndicator={false}
snapToInterval={totalItemWidth}
decelerationRate="fast"
bounces={false}
getItemLayout={(data, index) => ({
length: totalItemWidth,
offset: totalItemWidth * index,
index,
})}
ItemSeparatorComponent={SomeSeparatorComponent}
/>
)
}
How it behaves on iOS verse Android:
Android is a bit clunkier and I'm still refining the decelerationRate and overall feel... but it's close, IMO.