React Native: Correct scrolling in horizontal FlatList with Item Separator - react-native

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.

Related

How to make expo-av video to take needed inside a flatlist?

I am developing an instagram-like app where I needed to render images/videos. I am using flatlist to prevent memory lost, and using expo-av package to render the video.
Here is a screenshot of what I want to achieve:
So my goal here is to render videos with original ratio.
However, I am struggling to render the flatlist that contains the videos, it just doesn't render the component at all but I can still hear the video playing.
This is my FlatList:
<FlatList
style={{ width: "100%", height: "100%", backgroundColor: "yellow" }}
data={[0, 1, 2, 3, 4]}
keyExtractor={item => item}
renderItem={renderItem}
/>
And my renderItem callback:
const renderItem = ({ item }) => {
return <Post post={item} />;
}
Post item code:
export default ({ post }) => {
const videoRef = useRef(null);
const [status, setStatus] = useState({});
return (
<View style={{ width: "100%", height: "100%", backgroundColor: "blue" }}>
<Video
ref={videoRef}
source={{
uri: 'http://commondatastorage.googleapis.com/gtv-videosbucket/sample/VolkswagenGTIReview.mp4',
}}
style={{ width: "100%", height: "100%", backgroundColor: "blue" }}
resizeMode="contain"
autoplay
isLooping
shouldPlay={true}
onPlaybackStatusUpdate={status => setStatus(() => status)}
/>
</View>
);
}
Result (yellow background: flatlist area, post item should appear blue but not showing up):
The video would display if I give it a static width and height instead of values like 100%, but since I needed the renderItem to look original and take as much space as needed across all kinds of devices, so the only thing I could think of is a percentage.
If there is a way to know the aspect ratio or the width and height of the video, I can do dynamic calculations to achieve my goal, but I don't know if expo-av provide this information~
The renderItem would automatically take the max width inside a FlatList, but the height is default 0.
I figured that we can pass the video's natural aspect ratio to the style property so that it renders itself naturally with max size.
To get the video's natural aspect ratio, we have to define a function for the video's onReadyForDisplay property, it provides information of the video once the first frame is loaded.
To do that, we set the default ratio to the screen ratio:
import { Dimensions } from "react-native";
const defaultScreenRatio = Dimensions.get("window").width / Dimensions.get("window").height;
And then inside the component:
// Use the screenDefaultRatio to render the video before the video is loaded
const [videoRatio, setVideoRatio] = useState(screenDefaultRatio);
// Update the videoRatio right after we know the video natural size
const updateVideoRatioOnDisplay = (videoDetails) => {
const { width, height } = videoDetails.naturalSize;
const newVideoRatio = width / height;
setVideoRatio(newVideoRatio);
}
Code for Video item:
<View>
<Video
ref={videoRef}
source={{
uri: 'http://commondatastorage.googleapis.com/gtv-videosbucket/sample/VolkswagenGTIReview.mp4',
}}
style={{ aspectRatio: videoRatio, backgroundColor: "blue" }}
resizeMode="contain"
autoplay
isLooping
shouldPlay={true}
onPlaybackStatusUpdate={status => setStatus(() => status)}
// Update the video Ratio once done loading the first frame of the video
onReadyForDisplay={updateVideoRatioOnDisplay}
/>
</View>
So, I don't know how to make it dynamic to rach video, but you just can't have percentages for height and width

Overlapping items in React Native FlatList

I'm trying to make a list of items in FlatList overlap over each other like a stack of cards, but using a negative margin the item gets cut off, using "left: -20" does as well.
The image component is rather simple with round border:
export default class ProfilePicture extends React.Component {
render () {
let size = this.props.size || 50
return (
<Image
source={{ uri: this.props.picture }}
style={{
backgroundColor: 'rgba(12, 94, 20, 0.5);',
width: size,
height: size,
borderRadius: size / 2
}}
/>
)
}
}
And in the list is where I try to accomplish the overlap:
export default class RidersListCompact extends Component {
state = {
users: []
}
...
renderItem = ({ item: user, index }) => {
return <View style={styles.itemContainer}>
<ProfilePicture
picture={user.picture}
size={Layout.window.hp(6)}
/>
</View>
}
render () {
return (
<FlatList
renderItem={this.renderItem}
data={this.state.users}
keyExtractor={(user) => 'user_' + user.id}
horizontal
inverted
style={{ ...styles.container, ...this.props.style }}
/>
)
}
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row-reverse'
},
itemContainer: {
marginRight: -Layout.window.hp(2),
width: Layout.window.hp(6),
height: Layout.window.hp(6),
backgroundColor: 'rgba(0,0,0,0);'
}
})
I tried setting different zIndex on each item but haven't had much luck, is there a way to overlap images/components in FlatList?
Cheers!
Make use of Flex. seperate Items by putting then in flex direact row wise. use Props from flex. Flex has following props available
alignContent
alignItems
alignSelf
aspectRatio
borderBottomWidth
borderEndWidth
borderLeftWidth
borderRightWidth
borderStartWidth
borderTopWidth
borderWidth
bottom
direction
display
end
flex
flexBasis
flexDirection
flexGrow
flexShrink
flexWrap
height
justifyContent
left
margin
marginBottom
marginEnd
marginHorizontal
marginLeft
marginRight
marginStart
marginTop
marginVertical
maxHeight
maxWidth
minHeight
minWidth
overflow
padding
paddingBottom
paddingEnd
paddingHorizontal
paddingLeft
paddingRight
paddingStart
paddingTop
paddingVertical
position
right
start
top
width
zIndex
If you want to overlap images you should use position style in your styles. You need to set position to absolute and set left, right, top, bottom values.
More information

How to Scroll Right To Columns in React Native FlatList (2018)

I am seeking a way to scroll a viewport over a table like this, except that every cell is exactly the same size:
I am currently using FlatList's numColumns parameter to make a table and scroll the viewport over that table.
Here is a Snack example - RegularGridExample:
import React from 'react';
import { FlatList, Text, View } from 'react-native';
const numRows = 10,
numColumns = 10,
width = 100,
height = 100,
cells = [...Array(numRows * numColumns)].map((_, cellIndex) => {
const rowIndex = Math.floor(cellIndex / numRows),
colIndex = cellIndex % numColumns;
return {
key: `${colIndex},${rowIndex}`,
rowIndex,
colIndex,
styles: {
width,
height,
backgroundColor: 'green',
borderColor: 'black',
borderWidth: 1,
},
};
});
export default class RegularGridExample extends React.Component {
render() {
return (
<FlatList
data={cells}
renderItem={this.renderItem}
numColumns={numColumns}
horizontal={false}
columnWrapperStyle={{
borderColor: 'black',
width: numColumns * width,
}}
/>
);
}
renderItem = ({ item: { styles, rowIndex, colIndex } }) => {
return (
<View style={styles}>
<Text>r{rowIndex}</Text>
<Text>c{colIndex}</Text>
</View>
);
};
}
This example will correctly scroll to reveal the rows below the viewport, but it will not scroll to reveal the columns beyond the viewport. How can I enable scrolling the viewport to reveal a FlatList's columns?
Update 1
I do not think this can be easily solved with nested FlatLists, which is the first thing I tried before using the numColumns approach above. The use case here is shifting the viewport over a grid that's larger than the viewport, not just scrolling one row within the viewport.
Update 2
I'm seeking a virtualized solution. While the wireframe above uses text, the use case I'm actually interested in is browsing a tile server navigating over portions of a large 50MB+ image. It will be too slow to load all of them into a scroll view.
Unrelated Stack Overflow Posts
React Native ScrollView/FlatList not scrolling - this is about adding flex to the viewport to enable scrolling along the major axis of the FlatList, which is already working in the example above. My concern is scrolling along the crossAxis.
React native flatlist not scrolling - it is unclear what the expected and actual behavior is here
How can I sync two flatList scroll position in react native - here, the poster is seeking to simulate masonry layout; I'm not doing anything so fancy
This can't be done using the FlatList method, since numColumns is used which explicitly sets horizontal={false}, hence disabling the scrolling horizontal direction.
Here's a workaround by using nested ScrollViews
export default class RegularGridExample extends React.Component {
render() {
const generatedArray = [1,2,3,4,5,6]
return (
<ScrollView horizontal>
<ScrollView >
{generatedArray.map((data, index) => {
return <View style={{flexDirection: 'row'}} >
{generatedArray.map((data, index) => {
return <View style={{height: 100, width: 100, backgroundColor: 'red', borderWidth: 1, borderColor: 'black'}} />
})}
</View>
})}
</ScrollView>
</ScrollView>
);
}
}

Horizontal FlatList React Native with different height of items

I have a little problem with the FlatList Component in react native. In the FlatList are items with different height. Here is a little example of what i have:
example FlatL
The first problem is that all items are positioned at the top. I want to position all items at the bottom.
The second problem is that the height of the FlatList is always the height of the biggest item. So you can also scroll to another item in the white area of a small item...
here my code:
import React from "react";
import {
Text,
View,
Dimensions,
StyleSheet,
ListView,
TouchableOpacity,
Animated,
Image,
FlatList
} from "react-native";
import glamorous, { ThemeProvider } from "glamorous-native";
import theme from "../theme";
const { height, width } = Dimensions.get("window");
const cards = [
{
id: 1,
color: "red",
height: 400
},
{
id: 2,
color: "blue",
height: 300
},
{
id: 3,
color: "yellow",
height: 200
}
];
class Test extends React.Component {
render() {
return (
<View style={{ bottom: 0 }}>
<FlatList
ref={elm => (this.flatList = elm)}
showsHorizontalScrollIndicator={false}
data={cards}
pagingEnabled={true}
horizontal={true}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<View
style={{
height: item.height,
width: width,
backgroundColor: item.color
}}
/>
)}
/>
</View>
);
}
}
export default Test;
Has anyone a solution?
You can change the position of each item by setting the contentContainerStyle property of the FlatList itself. But the height of the FlatList will always have to be the height of the largest component inside it.
I solved this with onViewableItemsChanged prop
viewabilityConfig={{itemVisiblePercentThreshold: 50}}
onViewableItemsChanged={this.onViewableItemsChanged}
onViewableItemsChanged = ({ viewableItems }) => {
let index = viewableItems[0].index;
this.setState({ indexOfImages: index, heightOfImages: SCREEN_WIDTH * (viewableItems[0].item.height / viewableItems[0].item.width) });
}
We have width and height of the items in its data so I'm taking width and height of the visible item on the screen and do some calculations and I'm using this.state.heightOfImages in flatlist height like this
height: this.state.heightOfImages == undefined ? SCREEN_WIDTH * (images[0].height / images[0].width) : this.state.heightOfImages
You can just wrap the content of each card in another <View> element with styles={{ minHeight: 200, maxHeight: 300 }} and it will work fine!

React Native FlatList with columns, Last item width

I'm using a FlatList to show a list of items in two columns
<FlatList style={{margin:5}}
data={this.state.items}
numColumns={2}
keyExtractor={(item, index) => item.id }
renderItem={(item) => <Card image={item.item.gallery_image_url} text={item.item.name}/> }
/>
The card component is just a view with some styles:
<View style={{ flex: 1, margin: 5, backgroundColor: '#ddd', height: 130}} ></View>
It is working fine, but if the number of items is odd, the last row only contains one item and that item stretches to the full width of the screen.
How can I set the item to the same width of the others?
for your case use flex: 1/2
therefore: Your item should have flex of 1/(number of columns) if you have 3 columns your item should have flex:1/3
Theres a few things you can try here.
A) Setting a pre-defined width for the card ( Maybe equal to the height you've set? ). Then you can use alignItems in order to have the card positioned in the middle or on the left - Unsure as to which you wanted here.
B) If there are an even number of cards, you could add an empty View at the end in order to fill this space. I find this method pretty clunky but useful when trying to leave space for future elements.
C) Simply use alignItems: 'space-between, i like to use this to center items, but you would have to define the width, or use something like flex:0.5
I suggest researching more into flexbox to help you with this, as it is hard to tell the context of this situation. I'm assuming the above methods will help, but if not, here are some links for you to look at -
A Complete Guide to Flexbox (CSS Tricks)
Layout with Flexbox (React Native)
Video Tutorial (Youtube) Link Broken
Hope this helps. If you need any further clarification - just ask
This is the cleanest way to style a FlatList with columns and spaced evenly:
<FlatList style={{margin:5}}
numColumns={2} // set number of columns
columnWrapperStyle={style.row} // space them out evenly
data={this.state.items}
keyExtractor={(item, index) => item.id }
renderItem={(item) => <Card image={item.item.gallery_image_url} text={item.item.name}/> }
/>
const style = StyleSheet.create({
row: {
flex: 1,
justifyContent: "space-around"
}
});
You can try to get the current width of the device via Dimensions, do some math based on the number of columns you want to render, minus off the margins and set that as the minWidth and maxWidth.
For example:
const {height, width} = Dimensions.get('window');
const itemWidth = (width - 15) / 2;
<View style={{ flex: 1, margin: 5, backgroundColor: '#ddd', minWidth: {this.itemWidth}, maxWidth: {this.itemWidth}, height: 130}} ></View>
The reason for it is your Card have style flex: 1, so it will try to expand all the space remain.
You can fix it by add maxWidth: '50%' to your Card style
<View style={{ flex: 1, margin: 5, backgroundColor: '#ddd', height: 130, maxWidth: '50%'}} ></View>
#Emilius Mfuruki suggestion is good, but if you have text with varying length it doesn't work perfectly. Then use this width inside your item view:
const {height, width} = Dimensions.get('window');
const itemWidth = (width - (MarginFromTheSide * 2 + MarginInBetween * (n-1))) / n;
In FlatList use:
columnWrapperStyle={{
flex: 1,
justifyContent: 'space-evenly',
}}
Works perfectly.
The simplest solution is do the math.
Imagine we have 2 View for each Row and we want to give 10 margin to every side it will look something like that:
As you see in the image above each View have 2 margins in horizontal. (inside of red rectangle)
So we have to subtract the product of margin, number of column and 2 from the width.
import { Dimensions } from 'react-native';
const {width} = Dimensions.get("window")
const column = 2
const margin = 10
const SIZE = (width - (margin * column * 2)) / column
<View style={{ margin: 10, width: SIZE }} ></View>
I tried some of the solutions above but I still had some problems with the margins on the last item (2 columns list).
My solution was simply wrapping the item into a parent container, leaving the original container with flex: 1 and the parent container of the item with flex: 0.5 so it would take the margin correctly.
itemContainer: {
flex: 0.5,
},
itemSubContainer: {
flex: 1,
marginHorizontal: margin,
},
A simple way with flex
<FlatList style={{margin:5}}
data={this.state.items}
numColumns={2}
keyExtractor={(item, index) => item.id }
renderItem={({item, index}) => {
const lastItem = index === this.state.items.length - 1;
return (
<View style={{flex: lastItem ? 1 / 2 : 1 }}>
<Card image={item.gallery_image_url} text={item.name}/>
</View>
)}}
/>
You can use ListFooterComponent={this.renderFooter}
None of the above answers have worked perfectly for me so I post my own answer:
works with padding and margins
the last element will always have the correct size
<FlatList
data={data}
numColumns={2}
renderItem={({item, index}) => {
const lastItem = index === data.length - 1;
return (
<View style={{flex: 1, padding: 8, maxWidth: lastItem ? '50%' : '100%' }}>
...
</View>
)}}
/>
Note: change maxWidth according to number of columns
Result:
just use flex:0.5 and width:'50%'
Create an array with odd number of images in it, like:
const images = [
require('./../...jpg'),
require('./../...jpg'),
require('./../...jpg'),
require('./../...jpg'),
require('./../...jpg'),
];
And then, use the code given below,
const App = () => {
const _renderItem = ({ item, index }) => (
<Image
source={item}
style={{
width: '50%',
height: 200,
}}
resizeMode="cover"
/>
);
return (
<View style={{flex: 1, marginHorizontal: 10,}}>
<FlatList
columnWrapperStyle={{ justifyContent: 'space-between' }}
keyExtractor={(_, index)=> index.toString()}
data={images}
numColumns={2}
renderItem={_renderItem}
/>
</View>
)
};
export default App;
Working Example