Escobar5
Escobar5

Reputation: 4082

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?

enter image description here

Upvotes: 111

Views: 151892

Answers (16)

Ravi Dubey
Ravi Dubey

Reputation: 21

Below code works perfectly fine, width will be stretched as screen width and row with only 1 item will also have same width, you can remove scale from every unit used in code wrapped with scale, and width is from Dimensions from react-native :-

// code :-

renderItem function :-
const renderChapterCard = ( { item }: { item: string } ) => (
    <TouchableOpacity
      onPress={ () => { } }
      activeOpacity={ 0.4 }
      style={ styles.chapterCard }
    >
      <Image
        source={ { uri: 'https://example.com/demo1.png' } }
        resizeMode='contain'
        style={ styles.subjectImg }
      />

      <Title style={ styles.chapterTitleTxt }>{ item }</Title>

      <Text style={ [styles.testsCount, { color: chaptersCount.textColor, backgroundColor: chaptersCount.backgroundColor }] }>
        { 10 } Tests
      </Text>
    </TouchableOpacity>
  );

<FlatList
        data={ data }
        renderItem={ renderChapterCard }
        ListHeaderComponent={ <Title style={ styles.titleTxt }>Chapters</Title> }
        contentContainerStyle={ styles.scrollContainer }
        columnWrapperStyle={ styles.columnWrapperContainer }
        numColumns={ 2 }
      />


// styles :-

scrollContainer: {
    ...CommonStyles.scrollContainer,
    rowGap: scale( 10 ),
  },
  columnWrapperContainer: {
    flex: 1,
    columnGap: scale( 10 ),
  },
  titleTxt: {
    ...CommonStyles.titleTxt,
    marginTop: verticalScale( 8 ),
    fontSize: scale( 16 )
  },
  chapterCard: {
    ...CommonStyles.centeredContent,
    ...CommonStyles.cardContainer,
    ...CommonStyles.cardShadow,
    width: ( width - ( ( scale( 15 ) * 2 ) + ( scale( 10 ) ) ) ) / 2,  // subtracting all the margin and padding spaces
    gap: verticalScale( 8 ),
    paddingHorizontal: scale( 8 ),
    paddingVertical: verticalScale(15)
  },

Upvotes: 0

CrackerKSR
CrackerKSR

Reputation: 1926

I don't know if it is efficient or not but works like charm.

// I have 4 columns
let rem = column_count - images.length % column_count;
let blanks = Array(rem).fill( {} )
images.push(blanks);

<Flatlist
   data={images}
   ...
  />

// Note instead of using {}, you can use {key:value} to handle undesired result by checking these blank item

Here is my actual working code

...
 let blanks = Array.from({length:4-photos.length%4}).map(i=>({}))
  photos.push(blanks)

  return ( 
    <View style={styles.container}>
         <FlatList
          numColumns={4}
          data={photos}
...

Screenshot: screenshot of gallary grid view in React native

Upvotes: 0

Sem
Sem

Reputation: 1397

I had a similar issue, and instead of fixing it with CSS flexbox, I simply push a null element to the array if the array's length is odd number.

const data = [1,2,3];

if (data.length % 2 > 0) {
    data.push(null);
}

This is of course not a proper solution. Just wish to share an alternative.

Upvotes: 1

Ryan Turnbull
Ryan Turnbull

Reputation: 3944

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

Upvotes: 48

TOPKAT
TOPKAT

Reputation: 8688

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:

reactFlatlist

Upvotes: 6

Shahjahan
Shahjahan

Reputation: 142

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>
  )}}
/>

Upvotes: 2

Pratik Adhikari
Pratik Adhikari

Reputation: 517

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

Upvotes: -1

hegonen
hegonen

Reputation: 123

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:

enter image description here

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>

Upvotes: 4

Mateo Guzm&#225;n
Mateo Guzm&#225;n

Reputation: 1356

I tried some of the solutions above but I still had some problems with the margins on the last item (2 columns list).

enter image description here

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,
},

Upvotes: 3

Amit
Amit

Reputation: 2735

@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.

Upvotes: 5

Jagdish Suryawanshi
Jagdish Suryawanshi

Reputation: 429

just use flex:0.5 and width:'50%'

Upvotes: -1

Clapa Lucian
Clapa Lucian

Reputation: 600

You can use ListFooterComponent={this.renderFooter}

Upvotes: 0

jasonleonhard
jasonleonhard

Reputation: 13937

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"
        }
    });

Upvotes: 32

T&#249;ng Nguyễn
T&#249;ng Nguyễn

Reputation: 136

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>

Upvotes: 11

Emilius Mfuruki
Emilius Mfuruki

Reputation: 1197

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

Upvotes: 107

skyplor
skyplor

Reputation: 451

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>

Upvotes: 20

Related Questions