Skip to content

wuyunqiang/react-native-waterfall-list-view

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-native-waterfall-list-view

基于官方 flatlist 实现的 多列 不定高 瀑布流组件 不依赖任何第三方

效果展示

两列瀑布流

RPReplay_Final1726644535

三列瀑布流

RPReplay_Final1726645556

使用说明

  • 组件基于 flatlist 实现   几乎支持 flatlist 所有属性 个别属性不支持 例如 horizontal 目前仅支持垂直方向
  • 基于 ts + hooks 实现 有较好的类型提示
  • 支持不定高 item 内部通过布局自动计算 所以 getItemLayout 设置无效
  • 关于 ref 的支持 默认取到的是 WaterFallList 的 ref   内部包括自定义的属性和 flatlistRef.    如果想获取内部 flatlist 的 ref 对象 可以通过 WaterFallList 内部转发的的 flatListRef 对象
import WaterFallList from "react-native-waterfall-list-view"
import React, { memo, useEffect, useRef, useState } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import Item from "./item";

const colors = [
  "#5A479A",
  "#001694",
  "#32296B",
  "#D1D1D1",
  "#641024",
  "#FE3666",
  "#292647",
  "#B0E38F",
  "#6195FC",
  "#444444",
  "#FFD283",
  "#52210D",
  "#FFE8ED",
  "#3C325F",
  "#19191E",
];

let index = 0;

const getList = (length = 15) => {
  return Array.from({ length }, () => {
    index++;
    return {
      h: Math.floor(Math.random() * 80) + 100,
      bg: colors[Math.floor(Math.random() * colors.length)],
      index,
      key: index,
      name: index,
    };
  });
};

const App = () => {
  const [list, changeList] = useState([]);
  const waterfallRef = useRef<IWaterFallList>();

  const refresh = () => {
    index = 0;
    waterfallRef.current?.refreshList();
    changeList(getList(20));

    console.log(
      "test scrollToOffset",
      waterfallRef.current?.flatListRef.current?.scrollToOffset
    );
  };

  const onEndReached = () => {
    const nList = [...list, ...getList(20)];
    console.log("test onEndReached", nList);
    changeList(nList);
  };

  const onScroll = () => {
    console.log("test onScroll");
  };

  useEffect(() => {
    changeList(getList(20));
  }, []);

  return (
    <View
      style={{
        height: "100%",
        width: "100%",
        backgroundColor: "#FAB5B5",
        paddingHorizontal: 5,
      }}
    >
      <TouchableOpacity onPress={refresh}>
        <View
          style={{
            width: "100%",
            backgroundColor: "blue",
            height: 50,
            marginTop: 20,
            justifyContent: "center",
            alignItems: "center",
          }}
        >
          <Text style={{ color: "white" }}>刷新列表</Text>
        </View>
      </TouchableOpacity>
      <WaterFallList
        ref={waterfallRef}
        ItemSeparatorComponent={() => {
          return <View style={{ width: "100%", height: 10 }}></View>;
        }}
        initialNumToRender={10}
        windowSize={10}
        ListHeaderComponent={() => {
          return (
            <View
              style={{
                width: "100%",
                backgroundColor: "orange",
                height: 300,
                marginBottom: 20,
              }}
            ></View>
          );
        }}
        onScroll={onScroll}
        renderItem={({ item }) => <Item data={item}></Item>}
        data={list}
        contentContainerStyle={{ flexGrow: 1 }}
        onEndReachedThreshold={0.5}
        onEndReached={onEndReached}
        numColumns={3}
        showsVerticalScrollIndicator={false}
        ListEmptyComponent={() => {
          return (
            <View
              style={{
                width: "100%",
                height: "100%",
                justifyContent: "center",
                alignItems: "center",
                backgroundColor: "pink",
              }}
            >
              <Text style={{ fontSize: 30, color: "red" }}>empty</Text>
            </View>
          );
        }}
        ListFooterComponent={() => {
          return (
            <View
              style={{
                width: "100%",
                height: 50,
                justifyContent: "center",
                alignItems: "center",
                backgroundColor: "pink",
              }}
            >
              <Text style={{ fontSize: 30, color: "red" }}>正在加载中</Text>
            </View>
          );
        }}
      />
    </View>
  );
};
export default memo<typeof App>(App);

注意事项

  1. item 最大高度与最小高度差值不宜过大 建议:  最小高度>=最大高度的30%
  2. item 渲染完成后 不建议动态改变item的高度 会引起布局抖动
  3. 内部封装了 refreshList 函数 在刷新列表前 建议先调用此函数(刷新调用即可 列表项增加不需要调用)
  4. 仅支持垂直瀑布流

原理

  1. 首先更改数据源 将单列表数组转换为 N 维数组确定每行的具体 item 数
  2. 首先渲染一次当前列表 获取到每个元素的真实高度信息(获取定位信息的方式可以参考这篇文章),并通过_itemHeightsRef 记录下来。
  3. 当高度信息收集完成 触发强制刷新 再次渲染一次列表
  4. 决策当前元素应该放在第几列 每行的高度是多少

例如当前一行是这个形式:
截屏2024-09-18 19 48 46

第三个元素应该加在下一行的最短一列:
截屏2024-09-18 19 48 41

同理第四个元素也加在最短的一列 第二列
然后计算第二行的行高:
因为 flatlist 的一行是以最长的元素高度为准 所以我们需要计算出最长的元素高度是多少 并且还要减去上一行的高度这个偏移量。
所以高度如下
截屏2024-09-18 17 59 27

然后不断循环列表 直到结束。

About

基于flatlist实现的 多列 不定高 瀑布流组件

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published