brh55 / react-native-masonry

:raised_hands: A pure JS react-native component to render a masonry~ish layout for images with support for dynamic columns, progressive image loading, device rotation, on-press handlers, and headers/captions.
MIT License
1.32k stars 157 forks source link

Images in the masonry grid are oddly rendered #89

Closed davidvuong closed 6 years ago

davidvuong commented 6 years ago

I've included some examples of what I mean below:

screenshot 2018-06-27 22 50 46 screenshot 2018-06-27 22 51 09 screenshot 2018-06-27 22 51 34 screenshot 2018-06-27 22 51 56

As you can see, sometimes certain columns grow faster than others leaving large patchy areas. This is the code snippet I'm using for the above examples:

import * as React from 'react';
import _ from 'lodash';
import {
  Container,
  Content,
  View,
  Text,
} from 'native-base';
import Masonry from 'react-native-masonry';

export default class PhotoGalleryComponent extends React.Component {
  state = { bricks: [] };

  BATCH_SIZE = 20;
  PHOTO_IDS = [...];
  index = 0;

  constructor(props) {
    super(props);

    const timer = setInterval(() => {
      if (this.PHOTO_IDS.length === 0) {
        clearInterval(timer);
      } else {
        const photoIds = this.PHOTO_IDS.splice(0, this.BATCH_SIZE);
        const photoUrls = _.map(photoIds, (photoId) => {
          this.index += 1;
          return {
            data: { i: this.i },
            uri: `https://example.com/photos/${photoId}?width=220`,
            renderHeader: (data) => {
              return (
                <View style={{
                  position: 'absolute',
                  top: 0,
                  zIndex: 100000,
                }}>
                  <Text style={{ fontSize: 12 }}>{data.i}</Text>
                </View>
              );
            },
          };
        });
        this.setState((prevState) => {
          return { bricks: [...prevState.bricks, ...photoUrls] };
        });
      }
    }, 3000);
  }

  render() {
    return (
      <Container>
        <Header title="Photo Gallery" />
        <Content>
          <Masonry
            columns={4}
            bricks={this.state.bricks}
          />
        </Content>
        <Footer navigation={this.props.navigation} />
      </Container>
    );
  }
}

React Native: 0.55.4 React Native Masonry: 0.5.0-alpha.1

Any idea what might be wrong? I'm hoping there's a temporary workaround. Thanks!

brh55 commented 6 years ago

By default it's placing each image the moment they are resolved based on the next position, since you are on alpha v0.5.0 you can the priority property and set it to 'balance' and it will use heights and attempt to balance the masonry with it's current state.

davidvuong commented 6 years ago

@brh55 Thanks. Using a balanced priority seemed to have helped. I'm still seeing patchy areas but they're not as bad.

davidvuong commented 6 years ago

@brh55 Just an update: I've changed from 4 to 3 columns and set priority to balance and I don't think this is working correctly. Initially, all 3 columns have photos but as I add more, overtime the first column grows but other two remain unchanged.

In case this is useful at all: I'm now testing with about 1.3k images. This starts to happen when I insert images with differing heights. The first 24 images have the same height and width but the 25th image has a smaller height (although, same width). At this point masonry only updates the first column.

I've included a screenshot as an example:

image

davidvuong commented 6 years ago

I wanted to see if this was still a problem when the image heights are all the same. Unfortunately it is still an issue.

image

<Masonry
  columns={3}
  bricks={this.state.images}
  sorted
  priority="balance"
  spacing={1.5}
  imageContainerStyle={{
    height: 180,
  }}
/>
brh55 commented 6 years ago

If you have all image heights the same, you should just use a standard flatlist, there should be plenty of components available that will accomplished a grid effect.

The issue with masonry is it balances the grid using the component state. When each image is asynchronously resolved, it places the image within a column and then updates the state, but react setStates is also asynchronous and will sometimes batch the state changes into one (for performance reasons). Hence why placements may be off as it's using an older state. Redux would probably solve this issue, but I'm not too keen on rewriting this as I'm not using the component myself for any production apps any longer.