@shopify/flash-list
Version:
FlashList is a more performant FlatList replacement
221 lines • 8.04 kB
JavaScript
import { RVLayoutManager, } from "./LayoutManager";
/**
* GridLayoutManager implementation that arranges items in a grid pattern.
* Items are placed in rows and columns, with support for items spanning multiple columns.
*/
export class RVGridLayoutManagerImpl extends RVLayoutManager {
constructor(params, previousLayoutManager) {
super(params, previousLayoutManager);
/** If there's a span change for grid layout, we need to recompute all the widths */
this.fullRelayoutRequired = false;
this.boundedSize = params.windowSize.width;
}
/**
* Updates layout parameters and triggers recomputation if necessary.
* @param params New layout parameters
*/
updateLayoutParams(params) {
const prevNumColumns = this.maxColumns;
super.updateLayoutParams(params);
if (this.boundedSize !== params.windowSize.width ||
prevNumColumns !== params.maxColumns) {
this.boundedSize = params.windowSize.width;
if (this.layouts.length > 0) {
// update all widths
this.updateAllWidths();
this.recomputeLayouts(0, this.layouts.length - 1);
this.requiresRepaint = true;
}
}
}
/**
* Processes layout information for items, updating their dimensions.
* @param layoutInfo Array of layout information for items
* @param itemCount Total number of items in the list
*/
processLayoutInfo(layoutInfo, itemCount) {
for (const info of layoutInfo) {
const { index, dimensions } = info;
const layout = this.layouts[index];
layout.height = dimensions.height;
layout.isHeightMeasured = true;
layout.isWidthMeasured = true;
}
// TODO: Can be optimized
if (this.fullRelayoutRequired) {
this.updateAllWidths();
this.fullRelayoutRequired = false;
return 0;
}
}
/**
* Estimates layout dimensions for an item at the given index.
* @param index Index of the item to estimate layout for
*/
estimateLayout(index) {
const layout = this.layouts[index];
layout.width = this.getWidth(index);
layout.height = this.getEstimatedHeight(index);
layout.isWidthMeasured = true;
layout.enforcedWidth = true;
}
/**
* Handles span change for an item.
* @param index Index of the item
*/
handleSpanChange(index) {
this.fullRelayoutRequired = true;
}
/**
* Returns the total size of the layout area.
* @returns RVDimension containing width and height of the layout
*/
getLayoutSize() {
if (this.layouts.length === 0)
return { width: 0, height: 0 };
const totalHeight = this.computeTotalHeightTillRow(this.layouts.length - 1);
return {
width: this.boundedSize,
height: totalHeight,
};
}
/**
* Recomputes layouts for items in the given range.
* @param startIndex Starting index of items to recompute
* @param endIndex Ending index of items to recompute
*/
recomputeLayouts(startIndex, endIndex) {
const newStartIndex = this.locateFirstIndexInRow(Math.max(0, startIndex - 1));
const startVal = this.getLayout(newStartIndex);
let startX = startVal.x;
let startY = startVal.y;
for (let i = newStartIndex; i <= endIndex; i++) {
const layout = this.getLayout(i);
if (!this.checkBounds(startX, layout.width)) {
const tallestItem = this.processAndReturnTallestItemInRow(i - 1);
startY = tallestItem.y + tallestItem.height;
startX = 0;
}
layout.x = startX;
layout.y = startY;
startX += layout.width;
}
if (endIndex === this.layouts.length - 1) {
this.processAndReturnTallestItemInRow(endIndex);
}
}
/**
* Calculates the width of an item based on its span.
* @param index Index of the item
* @returns Width of the item
*/
getWidth(index) {
return (this.boundedSize / this.maxColumns) * this.getSpan(index);
}
/**
* Processes items in a row and returns the tallest item.
* Also handles height normalization for items in the same row.
* Tallest item per row helps in forcing tallest items height on neighbouring items.
* @param endIndex Index of the last item in the row
* @returns The tallest item in the row
*/
processAndReturnTallestItemInRow(endIndex) {
var _a, _b;
const startIndex = this.locateFirstIndexInRow(endIndex);
let tallestItem;
let maxHeight = 0;
let i = startIndex;
let isMeasured = false;
while (i <= endIndex) {
const layout = this.layouts[i];
isMeasured = isMeasured || Boolean(layout.isHeightMeasured);
maxHeight = Math.max(maxHeight, layout.height);
if (layout.height > ((_a = layout.minHeight) !== null && _a !== void 0 ? _a : 0) &&
layout.height > ((_b = tallestItem === null || tallestItem === void 0 ? void 0 : tallestItem.height) !== null && _b !== void 0 ? _b : 0)) {
tallestItem = layout;
}
i++;
if (i >= this.layouts.length) {
break;
}
}
if (!tallestItem && maxHeight > 0) {
maxHeight = Number.MAX_SAFE_INTEGER;
}
tallestItem = tallestItem !== null && tallestItem !== void 0 ? tallestItem : this.layouts[startIndex];
if (!isMeasured) {
return tallestItem;
}
if (tallestItem) {
let targetHeight = tallestItem.height;
if (maxHeight - tallestItem.height > 1) {
targetHeight = 0;
this.requiresRepaint = true;
}
i = startIndex;
while (i <= endIndex) {
this.layouts[i].minHeight = targetHeight;
if (targetHeight > 0) {
this.layouts[i].height = targetHeight;
}
i++;
if (i >= this.layouts.length) {
break;
}
}
tallestItem.minHeight = 0;
}
return tallestItem;
}
/**
* Computes the total height of the layout.
* @param endIndex Index of the last item in the row
* @returns Total height of the layout
*/
computeTotalHeightTillRow(endIndex) {
const startIndex = this.locateFirstIndexInRow(endIndex);
const y = this.layouts[startIndex].y;
let maxHeight = 0;
let i = startIndex;
while (i <= endIndex) {
maxHeight = Math.max(maxHeight, this.layouts[i].height);
i++;
if (i >= this.layouts.length) {
break;
}
}
return y + maxHeight;
}
updateAllWidths() {
for (let i = 0; i < this.layouts.length; i++) {
this.layouts[i].width = this.getWidth(i);
}
}
/**
* Checks if an item can fit within the bounded width.
* @param itemX Starting X position of the item
* @param width Width of the item
* @returns True if the item fits within bounds
*/
checkBounds(itemX, width) {
return itemX + width <= this.boundedSize + 0.9;
}
/**
* Locates the index of the first item in the current row.
* @param itemIndex Index to start searching from
* @returns Index of the first item in the row
*/
locateFirstIndexInRow(itemIndex) {
if (itemIndex === 0) {
return 0;
}
let i = itemIndex;
for (; i >= 0; i--) {
if (this.layouts[i].x === 0) {
break;
}
}
return Math.max(i, 0);
}
}
//# sourceMappingURL=GridLayoutManager.js.map