react-native-refresh-loadmore-recyclerlistview
Version:
The listview that you need and deserve. It was built for performance, uses cell recycling to achieve smooth scrolling.
181 lines (155 loc) • 6.11 kB
text/typescript
/***
* Computes the positions and dimensions of items that will be rendered by the list. The output from this is utilized by viewability tracker to compute the
* lists of visible/hidden item.
* Note: In future, this will also become an external dependency which means you can write your own layout manager. That will enable everyone to layout their
* views just the way they want. Current implementation is a StaggeredList
*/
import LayoutProvider, { Dimension } from "../dependencies/LayoutProvider";
import CustomError from "../exceptions/CustomError";
export default class LayoutManager {
private _layoutProvider: LayoutProvider;
private _window: Dimension;
private _totalHeight: number;
private _totalWidth: number;
private _layouts: Rect[];
private _isHorizontal: boolean;
constructor(layoutProvider: LayoutProvider, dimensions: Dimension, isHorizontal: boolean = false, cachedLayouts?: Rect[]) {
this._layoutProvider = layoutProvider;
this._window = dimensions;
this._totalHeight = 0;
this._totalWidth = 0;
this._layouts = cachedLayouts ? cachedLayouts : [];
this._isHorizontal = isHorizontal;
}
public getLayoutDimension(): Dimension {
return { height: this._totalHeight, width: this._totalWidth };
}
public getLayouts(): Rect[] {
return this._layouts;
}
public getOffsetForIndex(index: number): Point {
if (this._layouts.length > index) {
return { x: this._layouts[index].x, y: this._layouts[index].y };
}
throw new CustomError({
message: "No layout available for index: " + index,
type: "LayoutUnavailableException",
});
}
public overrideLayout(index: number, dim: Dimension): void {
const layout = this._layouts[index];
if (layout) {
layout.isOverridden = true;
layout.width = dim.width;
layout.height = dim.height;
}
}
public setMaxBounds(itemDim: Dimension): void {
if (this._isHorizontal) {
itemDim.height = Math.min(this._window.height, itemDim.height);
} else {
itemDim.width = Math.min(this._window.width, itemDim.width);
}
}
//TODO:Talha laziliy calculate in future revisions
public reLayoutFromIndex(startIndex: number, itemCount: number): void {
let startIndexTMp = this._locateFirstNeighbourIndex(startIndex);
let startX = 0,
startY = 0,
maxBound = 0;
const startVal = this._layouts[startIndexTMp];
if (startVal) {
startX = startVal.x;
startY = startVal.y;
this._pointDimensionsToRect(startVal);
}
const oldItemCount = this._layouts.length,
itemDim = { height: 0, width: 0 };
let itemRect = null,
oldLayout = null;
for (let i = startIndexTMp; i < itemCount; i++) {
oldLayout = this._layouts[i];
if (oldLayout && oldLayout.isOverridden) {
itemDim.height = oldLayout.height;
itemDim.width = oldLayout.width;
} else {
this._layoutProvider.setLayoutForType(this._layoutProvider.getLayoutTypeForIndex(i), itemDim, i);
}
this.setMaxBounds(itemDim);
while (!this._checkBounds(startX, startY, itemDim, this._isHorizontal)) {
if (this._isHorizontal) {
startX += maxBound;
startY = 0;
this._totalWidth += maxBound;
} else {
startX = 0;
startY += maxBound;
this._totalHeight += maxBound;
}
maxBound = 0;
}
maxBound = this._isHorizontal ? Math.max(maxBound, itemDim.width) : Math.max(maxBound, itemDim.height);
//TODO: Talha creating array upfront will speed this up
if (i > oldItemCount - 1) {
this._layouts.push({ x: startX, y: startY, height: itemDim.height, width: itemDim.width });
} else {
itemRect = this._layouts[i];
itemRect.x = startX;
itemRect.y = startY;
itemRect.width = itemDim.width;
itemRect.height = itemDim.height;
}
if (this._isHorizontal) {
startY += itemDim.height;
} else {
startX += itemDim.width;
}
}
if (oldItemCount > itemCount) {
this._layouts.splice(itemCount, oldItemCount - itemCount);
}
this._setFinalDimensions(maxBound);
}
private _pointDimensionsToRect(itemRect: Rect): void {
if (this._isHorizontal) {
this._totalWidth = itemRect.x;
} else {
this._totalHeight = itemRect.y;
}
}
private _setFinalDimensions(maxBound: number): void {
if (this._isHorizontal) {
this._totalHeight = this._window.height;
this._totalWidth += maxBound;
} else {
this._totalWidth = this._window.width;
this._totalHeight += maxBound;
}
}
private _locateFirstNeighbourIndex(startIndex: number): number {
if (startIndex === 0) {
return 0;
}
let i = startIndex - 1;
for (; i >= 0; i--) {
if (this._isHorizontal) {
if (this._layouts[i].y === 0) {
break;
}
} else if (this._layouts[i].x === 0) {
break;
}
}
return i;
}
private _checkBounds(itemX: number, itemY: number, itemDim: Dimension, isHorizontal: boolean): boolean {
return isHorizontal ? (itemY + itemDim.height <= this._window.height) : (itemX + itemDim.width <= this._window.width);
}
}
export interface Rect extends Dimension, Point {
isOverridden?: boolean;
}
export interface Point {
x: number;
y: number;
}