npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-native-waterfall-list-view

v1.0.3

Published

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

Downloads

117

Readme

react-native-waterfall-list-view

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

效果展示

两列瀑布流

RPReplay_Final1726644535

三列瀑布流

RPReplay_Final1726645556

使用说明

  • 组件基于 flatlist 实现   几乎支持 flatlist 所有属性 个别属性不支持 例如 horizontal 目前仅支持垂直方向
  • 基于 ts + hooks 实现 有较好的类型提示
  • 支持不定高 item 内部通过布局自动计算 所以 getItemLayout 设置无效
  • 关于 ref 的支持 默认取到的是 WaterFallList 的 ref   内部包括自定义的属性和 flatlist 实例方法.    如果想获取内部 flatlist 的 ref 对象 可以通过 WaterFallList 内部转发的的 flatList 属性
const waterfallRef = useRef<IWaterFallList>(null);

<WaterFallList ref={waterfallRef} />;

waterfallRef.current?.flatList?.scrollToOffset;

接口类型

export type ItemData = {
  offsetTop: number; // !! 元素距离容器顶部的距离
  itemH: number; // 元素高度
  itemData: any; // 元素数据
  columnIndex: number; // 当前元素所在的列
  index: number; // 原始列表的位置索引
};

export type RowData = {
  rowIndex: number; // 第几行
  rowData: ItemData[]; // 每行的列数据
  rowH: number; // 行高度
  rowOffsetTop: number; // !! 每行距离容器顶部的距离
};

export type IRenderItemProps = {
  item: ItemData;
  index: number;
  row: RowData;
};

export interface IWaterFallListProps
  extends Omit<
    FlatListProps<RowData>,
    "renderItem" | "ItemSeparatorComponent"
  > {
  renderItem: ({
    item,
    index,
    row,
  }: IRenderItemProps) => React.ReactElement | null;
  ItemSeparatorComponent?: () => JSX.Element;
  children?: React.ReactNode; // 添加children类型
  rowStyle?: ViewStyle;
}

export interface IWaterFallList {
  refreshList: (offset?: number, animated?: boolean) => void;
  flatList: FlatList | null;
}

示例

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. 决策当前元素应该放在第几列 每行的高度是多少

例如当前一行是这个形式:

第三个元素应该加在下一行的最短一列:

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

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