in extensions/virtuallistview/src/VirtualListView.tsx [682:902]
private _calcNewRenderedItemState(props: VirtualListViewProps<ItemInfo>): void {
if (this._layoutHeight === 0) {
// Wait until we get a height before bothering.
return;
}
if (props.itemList.length === 0) {
// Can't possibly be rendering anything.
return;
}
if (this._pendingMeasurements.size > 0) {
// Don't bother if we're still measuring things. Wait for the last batch to end.
return;
}
// What's the top/bottom line that we'll cull items that are wholly outside of?
const cullMargin = Math.max(this._layoutHeight * this._cullFraction, this._minCullAmount);
const topCullLine = this._lastScrollTop - cullMargin;
const bottomCullLine = this._lastScrollTop + this._layoutHeight + cullMargin;
// Do we need to cut anything out of the top because we've scrolled away from it?
while (this._itemsInRenderBlock > 0) {
const itemIndex = this._itemsAboveRenderBlock;
const item = props.itemList[itemIndex];
if (!this._isItemHeightKnown(item)) {
break;
}
const itemHeight = this._getHeightOfItem(item);
if (this._heightAboveRenderAdjustment + this._heightAboveRenderBlock + itemHeight >= topCullLine) {
// We're rendering up to the top render line, so don't need to nuke any more.
break;
}
this._itemsInRenderBlock--;
this._heightOfRenderBlock -= itemHeight;
this._itemsAboveRenderBlock++;
this._heightAboveRenderBlock += itemHeight;
this._recycleCell(item.key);
if (props.logInfo) {
props.logInfo('Culled Item From Top: ' + item.key);
}
}
// Do we need to cut anything out of the bottom because we've scrolled away from it?
while (this._itemsInRenderBlock > 0) {
const itemIndex = this._itemsAboveRenderBlock + this._itemsInRenderBlock - 1;
const item = props.itemList[itemIndex];
if (!this._isItemHeightKnown(item)) {
break;
}
const itemHeight = this._getHeightOfItem(item);
if (this._heightAboveRenderAdjustment + this._heightAboveRenderBlock + this._heightOfRenderBlock
- itemHeight <= bottomCullLine) {
break;
}
this._itemsInRenderBlock--;
this._heightOfRenderBlock -= itemHeight;
this._itemsBelowRenderBlock++;
this._heightBelowRenderBlock += itemHeight;
this._recycleCell(item.key);
if (props.logInfo) {
props.logInfo('Culled Item From Bottom: ' + item.key);
}
}
// Determine what the line is that we're rendering up to. If we haven't yet filled a screen,
// first get the screen full before over-rendering.
const overdrawAmount = this._calcOverdrawAmount();
let renderMargin = this._isInitialFillComplete ? overdrawAmount : 0;
let renderBlockTopLimit = this._lastScrollTop - renderMargin;
let renderBlockBottomLimit = this._lastScrollTop + this._layoutHeight + renderMargin;
if (this._itemsInRenderBlock === 0) {
let yPosition = this._heightAboveRenderAdjustment;
this._itemsAboveRenderBlock = 0;
// Find the first item that's in the render block and add it.
for (let i = 0; i < props.itemList.length; i++) {
const item = props.itemList[i];
const itemHeight = this._getHeightOfItem(item);
yPosition += itemHeight;
if (yPosition > renderBlockTopLimit) {
this._itemsAboveRenderBlock = i;
this._itemsInRenderBlock = 1;
this._allocateCell(item.key, item.template, i, !item.measureHeight, item.height,
yPosition - itemHeight, this._shouldShowItem(item, props));
if (!this._isItemHeightKnown(item)) {
this._pendingMeasurements.add(item.key);
}
break;
}
}
this._itemsBelowRenderBlock = props.itemList.length - this._itemsAboveRenderBlock - this._itemsInRenderBlock;
this._heightAboveRenderBlock = this._calcHeightOfItems(props, 0, this._itemsAboveRenderBlock - 1);
this._heightOfRenderBlock = this._calcHeightOfItems(props, this._itemsAboveRenderBlock,
this._itemsAboveRenderBlock + this._itemsInRenderBlock - 1);
this._heightBelowRenderBlock = this._calcHeightOfItems(props,
this._itemsAboveRenderBlock + this._itemsInRenderBlock, props.itemList.length - 1);
}
// What is the whole height of the scroll region? We need this both for calculating bottom
// offsets as well as for making the view render to the proper height since we're using
// position: absolute for all placements.
const itemBlockHeight = this._heightAboveRenderAdjustment + this._heightAboveRenderBlock +
this._heightOfRenderBlock + this._heightBelowRenderBlock;
const containerHeight = Math.max(itemBlockHeight, this._layoutHeight);
// Render the actual items now!
let yPosition = this._heightAboveRenderBlock + this._heightAboveRenderAdjustment;
let topOfRenderBlockY = yPosition;
// Start by checking heights/visibility of everything in the render block before we add to it.
for (let i = 0; i < this._itemsInRenderBlock; i++) {
const itemIndex = this._itemsAboveRenderBlock + i;
const item = props.itemList[itemIndex];
this._setCellTopAndVisibility(item.key, this._shouldShowItem(item, props),
yPosition, !!this.props.animateChanges);
const height = this._getHeightOfItem(item);
yPosition += height;
}
let bottomOfRenderBlockY = yPosition;
// See if the container height needs adjusting.
if (containerHeight !== this._containerHeight) {
if (props.logInfo) {
props.logInfo('Container Height Change: ' + this._containerHeight + ' to ' + containerHeight);
}
this._containerHeight = containerHeight;
this._containerHeightValue.setValue(containerHeight);
}
// Reuse an item-builder.
const buildItem = (itemIndex: number, above: boolean) => {
const item = props.itemList[itemIndex];
const isHeightKnown = this._isItemHeightKnown(item);
const itemHeight = this._getHeightOfItem(item);
assert(itemHeight > 0, 'list items should always have non-zero height');
this._itemsInRenderBlock++;
this._heightOfRenderBlock += itemHeight;
let yPlacement: number;
if (above) {
this._itemsAboveRenderBlock--;
this._heightAboveRenderBlock -= itemHeight;
topOfRenderBlockY -= itemHeight;
yPlacement = topOfRenderBlockY;
} else {
this._itemsBelowRenderBlock--;
this._heightBelowRenderBlock -= itemHeight;
yPlacement = bottomOfRenderBlockY;
bottomOfRenderBlockY += itemHeight;
}
if (!isHeightKnown) {
this._pendingMeasurements.add(item.key);
}
this._allocateCell(item.key, item.template, itemIndex, !item.measureHeight, item.height,
yPlacement, this._shouldShowItem(item, props));
if (props.logInfo) {
props.logInfo('New Item On ' + (above ? 'Top' : 'Bottom') + ': ' + item.key);
}
};
// Try to add items to the bottom of the current render block.
while (this._pendingMeasurements.size < _maxSimultaneousMeasures) {
// Stop if we go beyond the bottom render limit.
if (this._itemsBelowRenderBlock <= 0 ||
this._heightAboveRenderAdjustment + this._heightAboveRenderBlock +
this._heightOfRenderBlock >= renderBlockBottomLimit) {
break;
}
buildItem(this._itemsAboveRenderBlock + this._itemsInRenderBlock, false);
}
// Try to add an item to the top of the current render block.
while (this._pendingMeasurements.size < _maxSimultaneousMeasures) {
if (this._itemsAboveRenderBlock <= 0 ||
this._heightAboveRenderAdjustment + this._heightAboveRenderBlock <= renderBlockTopLimit) {
break;
}
buildItem(this._itemsAboveRenderBlock - 1, true);
}
// See if we've filled the screen and rendered it, and we're not waiting on any measurements.
if (!this._isInitialFillComplete && !this._isRenderDirty && this._pendingMeasurements.size === 0) {
// Time for overrender. Recalc render lines.
renderMargin = overdrawAmount;
renderBlockTopLimit = this._lastScrollTop - renderMargin;
renderBlockBottomLimit = this._lastScrollTop + this._layoutHeight + renderMargin;
this._popInvisibleIntoView(props);
// Render pass again!
this._componentDidRender();
}
if (props.logInfo) {
props.logInfo('CalcNewRenderedItemState: O:' + this._heightAboveRenderAdjustment +
' + A:' + this._heightAboveRenderBlock + ' + R:' + this._heightOfRenderBlock + ' + B:' +
this._heightBelowRenderBlock + ' = ' + itemBlockHeight + ', FilledViewable: ' + this._isInitialFillComplete);
}
}