From d3ff189cd69d07acda56c0401ee66639e26c6032 Mon Sep 17 00:00:00 2001 From: Jay Meistrich Date: Sat, 15 Feb 2025 17:26:17 +0700 Subject: [PATCH 1/4] Fix scrollForNextCalculateItemsInView was incorrect if the number of items in view decreased because of item sizes changing --- src/LegendList.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/LegendList.tsx b/src/LegendList.tsx index 647222e8..34e80488 100644 --- a/src/LegendList.tsx +++ b/src/LegendList.tsx @@ -477,6 +477,8 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef(props: LegendListProps & { ref?: ForwardedRef= 0 && nextBottom >= 0 ? { From 34f7232c2824d448cc5d4dc96fce47d7d6f10ed6 Mon Sep 17 00:00:00 2001 From: Jay Meistrich Date: Mon, 27 Jan 2025 11:02:50 +0800 Subject: [PATCH 2/4] feat: add overrideItemLayout prop, support fixed element sizes and col spans --- example/app/(tabs)/cards.tsx | 6 ++-- example/app/cards-columns/index.tsx | 12 ++++++- src/Container.tsx | 4 +-- src/LegendList.tsx | 56 +++++++++++++++++++++++------ src/state.tsx | 1 + src/types.ts | 9 +++++ 6 files changed, 72 insertions(+), 16 deletions(-) diff --git a/example/app/(tabs)/cards.tsx b/example/app/(tabs)/cards.tsx index 555c81cc..3cf0e3fd 100644 --- a/example/app/(tabs)/cards.tsx +++ b/example/app/(tabs)/cards.tsx @@ -1,7 +1,7 @@ import { type Item, renderItem } from "@/app/cards-renderItem"; import { DO_SCROLL_TEST, DRAW_DISTANCE, ESTIMATED_ITEM_LENGTH } from "@/constants/constants"; import { useScrollTest } from "@/constants/useScrollTest"; -import { LegendList, type LegendListRef } from "@legendapp/list"; +import { LegendList, type LegendListProps, type LegendListRef } from "@legendapp/list"; import { useRef, useState } from "react"; import { LogBox, Platform, StyleSheet, Text, View } from "react-native"; @@ -9,9 +9,10 @@ LogBox.ignoreLogs(["Open debugger"]); interface CardsProps { numColumns?: number; + overrideItemLayout?: LegendListProps["overrideItemLayout"]; } -export default function Cards({ numColumns = 1 }: CardsProps) { +export default function Cards({ numColumns = 1, ...props }: CardsProps) { const listRef = useRef(null); const [data, setData] = useState( @@ -59,6 +60,7 @@ export default function Cards({ numColumns = 1 }: CardsProps) { Empty } + {...props} // viewabilityConfigCallbackPairs={[ // { // viewabilityConfig: { id: "viewability", viewAreaCoveragePercentThreshold: 50 }, diff --git a/example/app/cards-columns/index.tsx b/example/app/cards-columns/index.tsx index a29428c9..7fcb9ef1 100644 --- a/example/app/cards-columns/index.tsx +++ b/example/app/cards-columns/index.tsx @@ -4,7 +4,17 @@ import { LogBox, Platform, StyleSheet } from "react-native"; LogBox.ignoreLogs(["Open debugger"]); export default function CardsColumns() { - return ; + return ( + { + if (index === 3) { + layout.span = 2; + // layout.size = 150; + } + }} + /> + ); } const styles = StyleSheet.create({ diff --git a/src/Container.tsx b/src/Container.tsx index b66236d7..0a5342a7 100644 --- a/src/Container.tsx +++ b/src/Container.tsx @@ -28,11 +28,11 @@ export const Container = ({ const maintainVisibleContentPosition = use$("maintainVisibleContentPosition"); const position = use$(`containerPosition${id}`) || ANCHORED_POSITION_OUT_OF_VIEW; const column = use$(`containerColumn${id}`) || 0; + const colSpan = use$(`containerColSpan${id}`) || 1; const numColumns = use$("numColumns"); const otherAxisPos: DimensionValue | undefined = numColumns > 1 ? `${((column - 1) / numColumns) * 100}%` : 0; - const otherAxisSize: DimensionValue | undefined = numColumns > 1 ? `${(1 / numColumns) * 100}%` : undefined; - + const otherAxisSize: DimensionValue | undefined = numColumns > 1 ? `${(colSpan / numColumns) * 100}%` : undefined; const style: StyleProp = horizontal ? { flexDirection: ItemSeparatorComponent ? "row" : undefined, diff --git a/src/LegendList.tsx b/src/LegendList.tsx index 34e80488..0b827350 100644 --- a/src/LegendList.tsx +++ b/src/LegendList.tsx @@ -75,6 +75,7 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef(props: LegendListProps & { ref?: ForwardedRef { - const sizeKnown = refState.current!.sizes.get(key)!; + const state = refState.current!; + const sizeKnown = state.sizes.get(key)!; if (sizeKnown !== undefined) { return sizeKnown; } - const size = + let size = (getEstimatedItemSize ? getEstimatedItemSize(index, data) : estimatedItemSize) ?? DEFAULT_ITEM_SIZE; + + if (overrideItemLayout) { + const layout = { span: 1, size }; + overrideItemLayout(layout, data, index, peek$(ctx, "numColumns"), extraData); + if (layout.size !== size) { + size = layout.size; + state.fixedSizes.set(key, size); + } + if (layout.span > 1) { + state.colSpans.set(key, layout.span); + } + } // TODO: I don't think I like this setting sizes when it's not really known, how to do // that better and support viewability checking sizes - refState.current!.sizes.set(key, size); + state.sizes.set(key, size); return size; }; const calculateInitialOffset = (index = initialScrollIndex) => { @@ -183,6 +197,8 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef getId(i))); if (maintainVisibleContentPosition) { @@ -432,10 +448,15 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef(props: LegendListProps & { ref?: ForwardedRef 1 && column + colSpan > numColumns + 1) { + i--; + startNewRow = true; + didStartNewRow = true; + } else if (positions.get(id) !== top) { positions.set(id, top); } @@ -452,10 +477,10 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef scroll) { - startNoBuffer = i; + startNoBuffer = i - column + 1; } if (startBuffered === null && top + size > scroll - scrollBufferTop) { - startBuffered = i; + startBuffered = i - column + 1; startBufferedId = id; } if (startNoBuffer !== null) { @@ -469,12 +494,16 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef numColumns) { + column += colSpan; + if (startNewRow || column > numColumns) { top += maxSizeInRow; column = 1; maxSizeInRow = 0; } + + if (!startNewRow) { + didStartNewRow = false; + } } const { startBuffered: prevStartBuffered, endBuffered: prevEndBuffered } = state; @@ -631,6 +660,8 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef(ctx, `containerPosition${i}`); + const colSpan = state.colSpans.get(id) ?? 1; + const prevColSpan = peek$(ctx, `containerColSpan${i}`); const prevColumn = peek$(ctx, `containerColumn${i}`); const prevData = peek$(ctx, `containerItemData${i}`); @@ -640,7 +671,9 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef= 0 && column !== prevColumn) { set$(ctx, `containerColumn${i}`, column); } - + if (colSpan !== prevColSpan) { + set$(ctx, `containerColSpan${i}`, colSpan); + } if (prevData !== item) { set$(ctx, `containerItemData${i}`, data[itemIndex]); } @@ -1002,7 +1035,8 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef(ctx, "numColumns"); @@ -1012,7 +1046,7 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef 0.5) { + if (!fixedSizes.has(itemKey) && (!prevSize || Math.abs(prevSize - size) > 0.5)) { let diff: number; if (numColumns > 1) { diff --git a/src/state.tsx b/src/state.tsx index 31860b61..c2e8615f 100644 --- a/src/state.tsx +++ b/src/state.tsx @@ -18,6 +18,7 @@ export type ListenerType = | `containerPosition${number}` | `containerColumn${number}` | "containersDidLayout" + | `containerColSpan${number}` | "extraData" | "numColumns" | "lastItemKey" diff --git a/src/types.ts b/src/types.ts index 50e8cdb0..7d5ed12f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -54,6 +54,13 @@ export type LegendListPropsBase< */ renderScrollComponent?: (props: ScrollViewProps) => React.ReactElement; extraData?: any; + overrideItemLayout?: ( + layout: { span?: number; size?: number }, + item: ItemT, + index: number, + maxColumns: number, + extraData?: any + ) => void; }; export type AnchoredPosition = { @@ -110,6 +117,8 @@ export interface InternalState { scrollForNextCalculateItemsInView: { top: number; bottom: number } | undefined; enableScrollForNextCalculateItemsInView: boolean; minIndexSizeChanged: number | undefined; + colSpans: Map; + fixedSizes: Map; } export interface ViewableRange { From fa2fb3d74c13056bde98bac8e866d25dbcae7749 Mon Sep 17 00:00:00 2001 From: Jay Meistrich Date: Tue, 4 Feb 2025 11:41:56 -0800 Subject: [PATCH 3/4] Change usage of getRowHeight in updateItemSize to use getItemSize --- src/LegendList.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/LegendList.tsx b/src/LegendList.tsx index 0b827350..f7faac70 100644 --- a/src/LegendList.tsx +++ b/src/LegendList.tsx @@ -1043,14 +1043,12 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef 0.5)) { let diff: number; if (numColumns > 1) { - const prevMaxSizeInRow = getRowHeight(row); sizes.set(itemKey, size); const column = columns.get(itemKey); @@ -1062,7 +1060,7 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef Date: Tue, 4 Feb 2025 16:05:23 -0800 Subject: [PATCH 4/4] Fix using wrong container position when resetting --- src/LegendList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LegendList.tsx b/src/LegendList.tsx index f7faac70..0ade96d3 100644 --- a/src/LegendList.tsx +++ b/src/LegendList.tsx @@ -523,7 +523,7 @@ const LegendListInner: (props: LegendListProps & { ref?: ForwardedRef