diff --git a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts index 3d6d77830013..b1f8694e710a 100644 --- a/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/auto-size-virtual-scroll.ts @@ -39,11 +39,10 @@ export class ItemSizeAverager { * @param size The measured size of the given range in pixels. */ addSample(range: ListRange, size: number) { - const weight = range.end - range.start; - const newTotalWeight = this._totalWeight + weight; + const newTotalWeight = this._totalWeight + range.end - range.start; if (newTotalWeight) { const newAverageItemSize = - (size * weight + this._averageItemSize * this._totalWeight) / newTotalWeight; + (size + this._averageItemSize * this._totalWeight) / newTotalWeight; if (newAverageItemSize) { this._averageItemSize = newAverageItemSize; this._totalWeight = newTotalWeight; @@ -87,7 +86,6 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { */ attach(viewport: CdkVirtualScrollViewport) { this._viewport = viewport; - this._updateTotalContentSize(); this._renderContentForOffset(this._viewport.measureScrollOffset()); } @@ -96,21 +94,27 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { this._viewport = null; } - /** Called when the viewport is scrolled. */ + /** Implemented as part of VirtualScrollStrategy. */ onContentScrolled() { if (this._viewport) { this._renderContentForOffset(this._viewport.measureScrollOffset()); } } - /** Called when the length of the data changes. */ + /** Implemented as part of VirtualScrollStrategy. */ onDataLengthChanged() { if (this._viewport) { - this._updateTotalContentSize(); this._renderContentForOffset(this._viewport.measureScrollOffset()); } } + /** Implemented as part of VirtualScrollStrategy. */ + onContentRendered() { + if (this._viewport) { + this._checkRenderedContentSize(); + } + } + /** * Update the buffer parameters. * @param minBufferPx The minimum amount of buffer rendered beyond the viewport (in pixels). @@ -122,6 +126,17 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { this._addBufferPx = addBufferPx; } + /** + * Checks the size of the currently rendered content and uses it to update the estimated item size + * and estimated total content size. + */ + private _checkRenderedContentSize() { + const viewport = this._viewport!; + const renderedContentSize = viewport.measureRenderedContentSize(); + this._averager.addSample(viewport.getRenderedRange(), renderedContentSize); + this._updateTotalContentSize(renderedContentSize); + } + /** * Render the content that we estimate should be shown for the given scroll offset. * Note: must not be called if `this._viewport` is null @@ -179,9 +194,13 @@ export class AutoSizeVirtualScrollStrategy implements VirtualScrollStrategy { } /** Update the viewport's total content size. */ - private _updateTotalContentSize() { + private _updateTotalContentSize(renderedContentSize: number) { const viewport = this._viewport!; - viewport.setTotalContentSize(viewport.getDataLength() * this._averager.getAverageItemSize()); + const renderedRange = viewport.getRenderedRange(); + const totalSize = renderedContentSize + + (viewport.getDataLength() - (renderedRange.end - renderedRange.start)) * + this._averager.getAverageItemSize(); + viewport.setTotalContentSize(totalSize); } } diff --git a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts b/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts index 46d523b34484..9a7bfa2b38f1 100644 --- a/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts +++ b/src/cdk-experimental/scrolling/fixed-size-virtual-scroll.ts @@ -70,6 +70,9 @@ export class FixedSizeVirtualScrollStrategy implements VirtualScrollStrategy { this._updateRenderedRange(); } + /** Called when the range of items rendered in the DOM has changed. */ + onContentRendered() { /* no-op */ } + /** Update the viewport's total content size. */ private _updateTotalContentSize() { if (!this._viewport) { diff --git a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts b/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts index 4fbf35b419af..1bd56fbb99de 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-strategy.ts @@ -31,4 +31,7 @@ export interface VirtualScrollStrategy { /** Called when the length of the data changes. */ onDataLengthChanged(); + + /** Called when the range of items rendered in the DOM has changed. */ + onContentRendered(); } diff --git a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts index ce5eb56aadc6..fdabd276ec88 100644 --- a/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk-experimental/scrolling/virtual-scroll-viewport.ts @@ -23,6 +23,7 @@ import { import {Observable} from 'rxjs/Observable'; import {fromEvent} from 'rxjs/observable/fromEvent'; import {sampleTime} from 'rxjs/operators/sampleTime'; +import {take} from 'rxjs/operators/take'; import {takeUntil} from 'rxjs/operators/takeUntil'; import {animationFrame} from 'rxjs/scheduler/animationFrame'; import {Subject} from 'rxjs/Subject'; @@ -99,6 +100,11 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy { return this._viewportSize; } + /** Get the current rendered range of items. */ + getRenderedRange(): ListRange { + return this._renderedRange; + } + // TODO(mmalebra): Consider calling `detectChanges()` directly rather than the methods below. /** @@ -122,6 +128,9 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy { this._ngZone.run(() => { this._renderedRangeSubject.next(this._renderedRange = range); this._changeDetectorRef.markForCheck(); + this._ngZone.runOutsideAngular(() => this._ngZone.onStable.pipe(take(1)).subscribe(() => { + this._scrollStrategy.onContentRendered(); + })); }); } } @@ -169,6 +178,12 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy { this.elementRef.nativeElement.scrollLeft : this.elementRef.nativeElement.scrollTop; } + /** Measure the combined size of all of the rendered items. */ + measureRenderedContentSize() { + const contentEl = this._contentWrapper.nativeElement; + return this.orientation === 'horizontal' ? contentEl.offsetWidth : contentEl.offsetHeight; + } + ngOnInit() { Promise.resolve().then(() => { this._viewportSize = this.orientation === 'horizontal' ?