反应像Instagram这样的本机网格

时间:2020-09-07 08:55:09

标签: css react-native gridview

我们如何实现像this这样的网格。我查看了许多反应本机库,但仍在寻找它。我尝试了自定义网格视图,但是在没有更多数据时创建了一个问题。 请让我知道最佳方法。

1 个答案:

答案 0 :(得分:1)

通过创建自定义组件,我设法为自己的项目做到了。

注意:在使用以下组件之前,请记住:

  1. 我使用ScrollView代替了FlatList,因为在我的情况下这是不可行的。可能效率不高。
  2. 我已经使用了外部库lodash,请确保安装该库。
  3. 这仅适用于3列图像,但您可以根据需要进行更改。
  4. 我有一个名为MyImage的自定义子组件,该子组件可以根据需要处理加载和错误。您可以跳过并使用您的图片标签或其他任何标签。

说明:

我创建了包含三个图像的单元格。共有3种类型的细胞。

  1. 右侧是大图。
  2. 左侧是大图。
  3. 没有大图像的普通网格。

我创建块,或者我们可以使用lodash构成数组数组,该数组用作每一行的数据。每个小块或数组将有3个对象。

groupEveryNthRow = 3意味着我将从0开始的每3行上都有一个带有大图像的单元格。

bigImageSide代表应该在哪一侧显示大图。我一直在从左到右变化。您可以根据自己的情况选择。

所有其他代码都是不言自明的。如果您什么都不懂,请告诉我。

代码

InstaGrid.js

import React from 'react';
import {
  View,
  StyleSheet,
  Dimensions,
  ScrollView,
  ActivityIndicator,
} from 'react-native';
var {width} = Dimensions.get('window');
import * as _ from 'lodash';
import MyImage from './MyImage';

const InstaGrid = ({
  data,
  columns,
  onEndReachedThreshold,
  onEndReached,
  loading = false,
  onItemClick,
}) => {
  const groupEveryNthRow = 3;
  const {mainContainer, groupedGridContainer} = styles;
  var currentRow = 0;
  const rowsArray = _.chunk(data, columns);
  var bigImageSide = 'right';

  const renderGroupedItem = (row) => {
    const smallImage1 = row[0];
    const smallImage2 = row[1];
    const largeImage = row[2];

    if (bigImageSide === 'right') {
      bigImageSide = 'left';
      return (
        <View style={{flexDirection: 'row'}}>
          <View style={groupedGridContainer}>
            <View style={styles.gridStyle}>
              <MyImage
                style={styles.imageThumbnail}
                sourceObj={smallImage1}
                onPress={() => {
                  onItemClick(smallImage1);
                }}
              />
            </View>
            <View style={styles.gridStyle}>
              <MyImage
                style={styles.imageThumbnail}
                sourceObj={smallImage2}
                onPress={() => {
                  onItemClick(smallImage2);
                }}
              />
            </View>
          </View>
          <View style={styles.gridStyle}>
            <MyImage
              style={styles.imageThumbnailLarge}
              sourceObj={largeImage}
              onPress={() => {
                onItemClick(largeImage);
              }}
            />
          </View>
        </View>
      );
    } else {
      bigImageSide = 'right';
      return (
        <View style={{flexDirection: 'row'}}>
          <View style={styles.gridStyle}>
            <MyImage
              style={styles.imageThumbnailLarge}
              sourceObj={largeImage}
              onPress={() => {
                onItemClick(largeImage);
              }}
            />
          </View>
          <View style={groupedGridContainer}>
            <View style={styles.gridStyle}>
              <MyImage
                style={styles.imageThumbnail}
                sourceObj={smallImage1}
                onPress={() => {
                  onItemClick(smallImage1);
                }}
              />
            </View>
            <View style={styles.gridStyle}>
              <MyImage
                style={styles.imageThumbnail}
                sourceObj={smallImage2}
                onPress={() => {
                  onItemClick(smallImage2);
                }}
              />
            </View>
          </View>
        </View>
      );
    }
  };

  const renderSingleItem = (item) => {
    return (
      <View style={styles.gridStyle}>
        <MyImage
          style={styles.imageThumbnail}
          sourceObj={item}
          onPress={() => {
            onItemClick(item);
          }}
        />
      </View>
    );
  };

  const renderCell = (row) => {
    if (row.length >= columns && currentRow % groupEveryNthRow === 0) {
      currentRow++;
      return <View>{renderGroupedItem(row)}</View>;
    }
    currentRow++;
    return (
      <View style={{flexDirection: 'row'}}>
        {row.map((item) => {
          return renderSingleItem(item);
        })}
      </View>
    );
  };

  const isCloseToBottom = ({layoutMeasurement, contentOffset, contentSize}) => {
    const paddingToBottom = 20;
    return (
      layoutMeasurement.height + contentOffset.y >=
      contentSize.height - paddingToBottom
    );
  };

  const renderFooter = () => {
    return (
      <View style={{marginBottom: 16}}>
        <ActivityIndicator animating size="large" />
      </View>
    );
  };

  return (
    <ScrollView
      scrollEventThrottle={onEndReachedThreshold}
      onScroll={({nativeEvent}) => {
        if (isCloseToBottom(nativeEvent)) {
          onEndReached();
        }
      }}>
      <View style={mainContainer}>
        {rowsArray.map((row) => {
          return renderCell(row);
        })}
      </View>
      {loading && renderFooter()}
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  mainContainer: {
    width: '100%',
  },
  groupedGridContainer: {
    flexDirection: 'column',
    flexWrap: 'wrap',
  },
  imageThumbnail: {
    height: width / 3 - 12,
    width: width / 3 - 12,
    resizeMode: 'stretch',
    alignSelf: 'flex-start',
    justifyContent: 'flex-start',
  },
  imageThumbnailLarge: {
    height: width * 0.6 + 12,
    width: width * 0.6 + 12,
    marginLeft: 4,
    resizeMode: 'stretch',
    alignSelf: 'flex-start',
    justifyContent: 'flex-start',
  },
  gridStyle: {
    margin: 4,
  },
});

export default InstaGrid;

MyImage.js

import React, {useState} from 'react';
import {
  TouchableOpacity,
  Image,
  StyleSheet,
  ActivityIndicator,
} from 'react-native';

const MyImage = ({style, sourceObj, onPress}) => {
  const [imageError, setImageError] = useState(false);
  const [loading, setLoading] = useState(true);

  return (
    <TouchableOpacity onPress={onPress}>
      {imageError || !sourceObj.card_images ? (
        <Image
          source={require('../images/userImage.jpg')}
          style={style}
          onLoadEnd={() => setLoading(false)}
        />
      ) : (
        <Image
          style={style}
          source={{uri: sourceObj.card_images.front_image}}
          onError={(e) => {
            setLoading(false);
            setImageError(true);
          }}
          onLoadEnd={() => setLoading(false)}
        />
      )}
      {loading && (
        <ActivityIndicator
          style={styles.activityIndicator}
          animating={loading}
        />
      )}
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  activityIndicator: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
  },
});

export default MyImage;

用法:只是

<InstaGrid
        data={details}
        columns={3}
        loading={loading}
        onItemClick={(item) => {
          console.log('Got the Item:' + JSON.stringify(item));
        }}
        onEndReachedThreshold={400}
        onEndReached={() => (offset !== -1 ? fetchData() : null)}
      />