Reputation: 157
I´ve been struggling to get the masonry-like style into my app.
I tried applying the react-native-masonry
package. However you must pass an image url.
I am trying to accomplish the same style but to render text without necessarily rendering an image.
So far I´ve work my way around a FlatList
, but this is as far as I could get.
<FlatList
data={[
{ id: 1, username: "user1", title: "Title test", heigth: 150 },
{
id: 2,
username: "RH",
title: "Testing the length of a title with an image",
image: "http://localhost:5005/dummy.png",
heigth: 300
},
{ id: 3, username: "john", title: "Another not so long title" },
{
username: "CAF",
title:
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
heigth: 600
}
]}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
numColumns={2}
/>;
I am hardcoding different lengths to the cards but it seems that they stick to the largest height on the row.
And this is my card component (renderItem).
_renderItem = ({ item }) => (
<Card style={{ height: item.heigth }}>
<CardItem>
<Left>
<Text style={{ fontWeight: "bold", fontSize: 20 }}>
{item.title}
</Text>
</Left>
</CardItem>
<CardItem cardBody style={{ marginBottom: 5 }}>
{!isEmpty(item.image) && (
<Image
source={{ uri: item.image }}
style={{ width: 170, flex: 1, height: 100 }}
/>
)}
</CardItem>
<CardItem>
<Left />
<Body />
<Right>
<Text note>{item.username}</Text>
</Right>
</CardItem>
</Card>
);
Thanks
Upvotes: 2
Views: 6300
Reputation: 1
Using the technique presented by @Dror Bar, I've formulated the code to place items in two columns based on their height. Here is how you can do this:
let colLeft=0; let colRight=0; let colDiff=0;
const rows = [];
data.forEach(item => {
let cellData = {
marginTop: 0,
image1: { source: '', height: 0, marginTop: 0 },
image2: { source: '', height: 0, marginTop: 0 }
}
if(colLeft <= colRight) {
colDiff = colLeft - colRight;
cellData.marginTop = colDiff > 0 ? -colDiff : 0;
cellData.image1.source = item.source;
cellData.image1.height = item.height;
cellData.image1.marginTop = colDiff;
colLeft = colLeft + item.height;
console.log('Col Left: ', colLeft);
} else {
colDiff = colRight - colLeft;
cellData.marginTop = colDiff > 0 ? -colDiff : 0;
cellData.image2.source = item.source;
cellData.image2.height = item.height;
cellData.image2.marginTop = colDiff;
colRight = colRight + item.height;
console.log('Col Right: ', colRight);
}
rows.push(cellData);
});
return rows;
Here is the data sample:
const imageData = [
{
source: 'grey',
height: 180
},
{
source: 'blue',
height: 150
},
{
source: 'purple',
height: 70
},
{
source: 'blue',
height: 50
},
{
source: 'pink',
height: 140
},
{
source: 'blue',
height: 180
},
{
source: 'red',
height: 60
},
{
source: 'yellow',
height: 180
},
{
source: 'green',
height: 140
},
{
source: 'blue',
height: 100
},
];
You can use the flatList to render this data, just like as Dror Bar has done above.
Upvotes: 0
Reputation: 718
I found a way to achieve this in react native - no external libraries needed. The trick is to use negative margin. The implementation is slightly tricky, but not too difficult.
We need to apply the negative margin in the VirtualizedList prop CellRendererComponent in order to get it to work properly on Android.
The JSX:
<View style={styles.container}>
<FlatList
style={styles.flatlist}
data={data}
keyExtractor={(item, index) => index.toString()}
CellRendererComponent={({ children, item, ...props }) => {
return (
<View {...props} style={{ marginTop: item.marginTop }}>
{children}
</View>
)
}}
renderItem={({ item }) => {
const { source: source1, height: height1, marginTop: marginTop1 } = item.image1;
const { source: source2, height: height2, marginTop: marginTop2 } = item.image2;
return (
<View style={Style.viewRow}>
<Image source={source1} style={[styles.image, { height: height1, marginTop: marginTop1 }]} />
<Image source={source2} style={[styles.image, { height: height2, marginTop: marginTop2 }]} />
</View>
)
}}
/>
</View>
The data:
const source = { uri: 'https://placekitten.com/160/300' };
const data = [
{
marginTop: 0,
image1: { source, height: 300, marginTop: 0 },
image2: { source, height: 250, marginTop: 0 }
},
{
marginTop: -50,
image1: { source, height: 290, marginTop: 50 },
image2: { source, height: 300, marginTop: 0 }
},
{
marginTop: -40,
image1: { source, height: 250, marginTop: 40 },
image2: { source, height: 350, marginTop: 0 }
}
];
The styles:
const styles = StyleSheet.create({
container: {
flex: 1
},
flatList: {
width: '100%',
height: '100%'
},
viewRow: {
flexDirection: 'row'
},
image: {
width: '50%',
resizeMode: 'cover'
}
});
The only work that's left for you is to arrange the data of the images in the array. Make sure to have the height of the images, always place the taller image in the shorter side, and then calculate the difference... Enjoy.
Upvotes: 1
Reputation: 2665
Use this library, I was having the same requirement. It pretty much solves this problem. https://github.com/AppAndFlow/react-native-masonry-list
Upvotes: 1