mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 13:01:01 +08:00
fix(virtual-list): works with infinite-scroll
fixes #9350 fixes #9722 fixes #9247 fixes #10778
This commit is contained in:
@ -216,7 +216,8 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
|
||||
_differ: any;
|
||||
_scrollSub: any;
|
||||
_scrollEndSub: any;
|
||||
_init: boolean;
|
||||
_resizeSub: any;
|
||||
_init: boolean = false;
|
||||
_lastEle: boolean;
|
||||
_hdrFn: Function;
|
||||
_ftrFn: Function;
|
||||
@ -229,6 +230,7 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
|
||||
scrollTop: 0,
|
||||
};
|
||||
_queue: number;
|
||||
_recordSize: number = 0;
|
||||
|
||||
@ContentChild(VirtualItem) _itmTmp: VirtualItem;
|
||||
@ContentChild(VirtualHeader) _hdrTmp: VirtualHeader;
|
||||
@ -376,24 +378,23 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
|
||||
private _plt: Platform,
|
||||
private _ctrl: ViewController,
|
||||
private _config: Config,
|
||||
private _dom: DomController) {
|
||||
|
||||
private _dom: DomController
|
||||
) {
|
||||
// hide the virtual scroll element with opacity so we don't
|
||||
// see jank as it loads up, but we're still able to read
|
||||
// dimensions because it's still rendered and only opacity hidden
|
||||
this._renderer.setElementClass(_elementRef.nativeElement, 'virtual-loading', true);
|
||||
this.setElementClass('virtual-loading', true);
|
||||
|
||||
// wait for the content to be rendered and has readable dimensions
|
||||
_ctrl.readReady.subscribe(() => {
|
||||
this._init = true;
|
||||
|
||||
if (this._hasChanges()) {
|
||||
this.readUpdate();
|
||||
if (isPresent(this._changes())) {
|
||||
this.readUpdate(true);
|
||||
|
||||
// wait for the content to be writable
|
||||
var subscription = _ctrl.writeReady.subscribe(() => {
|
||||
subscription.unsubscribe();
|
||||
this.writeUpdate();
|
||||
this.writeUpdate(true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -405,34 +406,54 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
|
||||
* @private
|
||||
*/
|
||||
ngDoCheck() {
|
||||
if (this._init && this._hasChanges()) {
|
||||
// only continue if we've already initialized
|
||||
// and if there actually are changes
|
||||
this.readUpdate();
|
||||
this.writeUpdate();
|
||||
// only continue if we've already initialized
|
||||
if (!this._init) {
|
||||
return;
|
||||
}
|
||||
|
||||
// and if there actually are changes
|
||||
const changes = this._changes();
|
||||
if (!isPresent(changes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let needClean = false;
|
||||
if (changes) {
|
||||
changes.forEachOperation((item: any, _: number, cindex: number) => {
|
||||
if (item.previousIndex != null || (cindex < this._recordSize)) {
|
||||
needClean = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
needClean = true;
|
||||
}
|
||||
this._recordSize = this._records.length;
|
||||
|
||||
this.readUpdate(needClean);
|
||||
this.writeUpdate(needClean);
|
||||
}
|
||||
|
||||
readUpdate(needClean: boolean) {
|
||||
if (needClean) {
|
||||
// reset everything
|
||||
console.debug(`virtual-scroll, readUpdate: slow path`);
|
||||
this._cells.length = 0;
|
||||
this._nodes.length = 0;
|
||||
this._itmTmp.viewContainer.clear();
|
||||
|
||||
// ******** DOM READ ****************
|
||||
this.calcDimensions();
|
||||
} else {
|
||||
console.debug(`virtual-scroll, readUpdate: fast path`);
|
||||
}
|
||||
}
|
||||
|
||||
readUpdate() {
|
||||
console.debug(`virtual-scroll, readUpdate`);
|
||||
|
||||
// reset everything
|
||||
this._cells.length = 0;
|
||||
this._nodes.length = 0;
|
||||
this._itmTmp.viewContainer.clear();
|
||||
|
||||
// ******** DOM READ ****************
|
||||
calcDimensions(this._data, this._elementRef.nativeElement,
|
||||
this.approxItemWidth, this.approxItemHeight,
|
||||
this.approxHeaderWidth, this.approxHeaderHeight,
|
||||
this.approxFooterWidth, this.approxFooterHeight,
|
||||
this.bufferRatio);
|
||||
}
|
||||
|
||||
writeUpdate() {
|
||||
writeUpdate(needClean: boolean) {
|
||||
console.debug(`virtual-scroll, writeUpdate`);
|
||||
const data = this._data;
|
||||
const stopAtHeight = (data.scrollTop + data.renderHeight);
|
||||
data.scrollDiff = SCROLL_DIFFERENCE_MINIMUM + 1;
|
||||
|
||||
const stopAtHeight = ((this._data.scrollTop || 0) + this._data.renderHeight);
|
||||
processRecords(stopAtHeight,
|
||||
this._records,
|
||||
this._cells,
|
||||
@ -441,86 +462,116 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
|
||||
this._data);
|
||||
|
||||
// ******** DOM WRITE ****************
|
||||
this.renderVirtual();
|
||||
this.renderVirtual(needClean);
|
||||
}
|
||||
|
||||
private _hasChanges() {
|
||||
return (isPresent(this._records) && isPresent(this._differ) && isPresent(this._differ.diff(this._records)));
|
||||
private calcDimensions() {
|
||||
calcDimensions(this._data, this._elementRef.nativeElement,
|
||||
this.approxItemWidth, this.approxItemHeight,
|
||||
this.approxHeaderWidth, this.approxHeaderHeight,
|
||||
this.approxFooterWidth, this.approxFooterHeight,
|
||||
this.bufferRatio);
|
||||
}
|
||||
|
||||
private _changes() {
|
||||
if (isPresent(this._records) && isPresent(this._differ)) {
|
||||
return this._differ.diff(this._records);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* DOM WRITE
|
||||
*/
|
||||
renderVirtual() {
|
||||
renderVirtual(needClean: boolean) {
|
||||
const nodes = this._nodes;
|
||||
const cells = this._cells;
|
||||
const data = this._data;
|
||||
const records = this._records;
|
||||
|
||||
// initialize nodes with the correct cell data
|
||||
if (needClean) {
|
||||
// ******** DOM WRITE ****************
|
||||
updateDimensions(this._plt, nodes, cells, data, true);
|
||||
data.topCell = 0;
|
||||
data.bottomCell = (cells.length - 1);
|
||||
}
|
||||
|
||||
adjustRendered(cells, data);
|
||||
data.bottomCell = (cells.length - 1);
|
||||
|
||||
populateNodeData(data.topCell || 0,
|
||||
data.bottomCell,
|
||||
data.viewWidth, true,
|
||||
cells, records, nodes,
|
||||
this._itmTmp.viewContainer,
|
||||
this._itmTmp.templateRef,
|
||||
this._hdrTmp && this._hdrTmp.templateRef,
|
||||
this._ftrTmp && this._ftrTmp.templateRef, true);
|
||||
populateNodeData(data.topCell, data.bottomCell,
|
||||
data.viewWidth, true,
|
||||
cells, records, nodes,
|
||||
this._itmTmp.viewContainer,
|
||||
this._itmTmp.templateRef,
|
||||
this._hdrTmp && this._hdrTmp.templateRef,
|
||||
this._ftrTmp && this._ftrTmp.templateRef, needClean);
|
||||
|
||||
// ******** DOM WRITE ****************
|
||||
this._cd.detectChanges();
|
||||
if (needClean) {
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
this._plt.raf(() => {
|
||||
// at this point, this fn was called from within another
|
||||
// requestAnimationFrame, so the next dom reads/writes within the next frame
|
||||
// wait a frame before trying to read and calculate the dimensions
|
||||
this._dom.read(() => {
|
||||
// ******** DOM READ ****************
|
||||
initReadNodes(this._plt, nodes, cells, data);
|
||||
});
|
||||
|
||||
// at this point, this fn was called from within another
|
||||
// requestAnimationFrame, so the next dom reads/writes within the next frame
|
||||
// wait a frame before trying to read and calculate the dimensions
|
||||
this._dom.read(() => {
|
||||
// ******** DOM READ ****************
|
||||
initReadNodes(this._plt, nodes, cells, data);
|
||||
});
|
||||
this._dom.write(() => {
|
||||
const ele = this._elementRef.nativeElement;
|
||||
const recordsLength = records.length;
|
||||
const renderer = this._renderer;
|
||||
|
||||
this._dom.write(() => {
|
||||
const ele = this._elementRef.nativeElement;
|
||||
const recordsLength = records.length;
|
||||
const renderer = this._renderer;
|
||||
// update the bound context for each node
|
||||
updateNodeContext(nodes, cells, data);
|
||||
|
||||
// update the bound context for each node
|
||||
updateNodeContext(nodes, cells, data);
|
||||
|
||||
// ******** DOM WRITE ****************
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
(<any>nodes[i].view).detectChanges();
|
||||
}
|
||||
|
||||
if (!this._lastEle) {
|
||||
// add an element at the end so :last-child css doesn't get messed up
|
||||
// ******** DOM WRITE ****************
|
||||
var lastEle: HTMLElement = renderer.createElement(ele, 'div');
|
||||
lastEle.className = 'virtual-last';
|
||||
this._lastEle = true;
|
||||
}
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
(<any>nodes[i].view).detectChanges();
|
||||
}
|
||||
|
||||
// ******** DOM WRITE ****************
|
||||
renderer.setElementClass(ele, 'virtual-scroll', true);
|
||||
if (!this._lastEle) {
|
||||
// add an element at the end so :last-child css doesn't get messed up
|
||||
// ******** DOM WRITE ****************
|
||||
var lastEle: HTMLElement = renderer.createElement(ele, 'div');
|
||||
lastEle.className = 'virtual-last';
|
||||
this._lastEle = true;
|
||||
}
|
||||
|
||||
// ******** DOM WRITE ****************
|
||||
renderer.setElementClass(ele, 'virtual-loading', false);
|
||||
// ******** DOM WRITE ****************
|
||||
this.setElementClass('virtual-scroll', true);
|
||||
|
||||
// ******** DOM WRITE ****************
|
||||
writeToNodes(this._plt, nodes, cells, recordsLength);
|
||||
// ******** DOM WRITE ****************
|
||||
this.setElementClass('virtual-loading', false);
|
||||
|
||||
// ******** DOM WRITE ****************
|
||||
this._setHeight(
|
||||
estimateHeight(recordsLength, cells[cells.length - 1], this._vHeight, 0.25)
|
||||
);
|
||||
// ******** DOM WRITE ****************
|
||||
writeToNodes(this._plt, nodes, cells, recordsLength);
|
||||
|
||||
this._content.imgsUpdate();
|
||||
// ******** DOM WRITE ****************
|
||||
this._setHeight(
|
||||
estimateHeight(recordsLength, cells[cells.length - 1], this._vHeight, 0.25)
|
||||
);
|
||||
|
||||
this._content.imgsUpdate();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
resize() {
|
||||
// only continue if we've already initialized
|
||||
if (!this._init) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug('virtual-list: resized window');
|
||||
this.calcDimensions();
|
||||
this.writeUpdate(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -665,13 +716,9 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
|
||||
this._content.enableJsScroll();
|
||||
}
|
||||
|
||||
this._scrollSub = this._content.ionScroll.subscribe((ev: ScrollEvent) => {
|
||||
this.scrollUpdate(ev);
|
||||
});
|
||||
|
||||
this._scrollEndSub = this._content.ionScrollEnd.subscribe((ev: ScrollEvent) => {
|
||||
this.scrollEnd(ev);
|
||||
});
|
||||
this._resizeSub = this._plt.resize.subscribe(this.resize.bind(this));
|
||||
this._scrollSub = this._content.ionScroll.subscribe(this.scrollUpdate.bind(this));
|
||||
this._scrollEndSub = this._content.ionScrollEnd.subscribe(this.scrollEnd.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
@ -702,14 +749,20 @@ export class VirtualScroll implements DoCheck, AfterContentInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
setElementClass(className: string, add: boolean) {
|
||||
this._renderer.setElementClass(this._elementRef.nativeElement, className, add);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this._resizeSub && this._resizeSub.unsubscribe();
|
||||
this._scrollSub && this._scrollSub.unsubscribe();
|
||||
this._scrollEndSub && this._scrollEndSub.unsubscribe();
|
||||
this._scrollEndSub = this._scrollSub = null;
|
||||
this._hdrFn = this._ftrFn = this._records = this._cells = this._nodes = this._data = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const SCROLL_DIFFERENCE_MINIMUM = 40;
|
||||
|
Reference in New Issue
Block a user