Start of virtual scrolling for lists

This commit is contained in:
Max Lynch
2015-06-14 14:43:47 -05:00
parent 603a61d070
commit 8676deb325
5 changed files with 219 additions and 5 deletions

View File

@ -15,5 +15,19 @@ export class Content {
// but we should be able to stamp out this behavior with a base IonicComponent
// or something, so all elements have a domElement reference or a getElement() method
this.domElement = elementRef.domElement;
setTimeout(() => {
this.scrollElement = this.domElement.children[0];
});
}
addScrollEventListener(handler) {
if(!this.scrollElement) { return; }
this.scrollElement.addEventListener('scroll', handler);
return () => {
this.scrollElement.removeEventListener('scroll', handler);
}
}
}

View File

@ -3,26 +3,52 @@ import {Component, Directive} from 'angular2/src/core/annotations_impl/annotatio
import {View} from 'angular2/src/core/annotations_impl/view';
import {IonicComponent} from 'ionic/config/component'
import {ListVirtualScroll} from './virtual'
import * as util from 'ionic/util';
@Component({
selector: 'ion-list'
selector: 'ion-list',
properties: [
'items',
'virtual',
'content'
]
})
@View({
template: `<content></content>`
})
export class List {
constructor() {
}
constructor(
elementRef: ElementRef
) {
this.domElement = elementRef.domElement;
this.config = List.config.invoke(this);
setTimeout(() => {
console.log('Content', this.content);
console.log('Virtual?', this.virtual);
console.log('Items?', this.items.length, 'of \'em');
if(util.isDefined(this.virtual)) {
this._initVirtualScrolling();
}
})
}
_initVirtualScrolling() {
if(!this.content) {
return;
}
this._virtualScrollingManager = new ListVirtualScroll(this);
}
setItemTemplate(item) {
this.itemTemplate = item;
}
}
new IonicComponent(List, {
propClasses: ['inset']
})

View File

@ -0,0 +1,50 @@
import {bootstrap, NgFor, ProtoViewRef, ViewContainerRef} from 'angular2/angular2'
import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations';
import {View} from 'angular2/src/core/annotations_impl/view';
import {Parent} from 'angular2/src/core/annotations_impl/visibility';
import {Content, List, Item} from 'ionic/ionic';
@Component({ selector: 'ion-app' })
@View({
templateUrl: 'main.html',
directives: [Content, List, Item, ItemCellTemplate, NgFor]
})
class IonicApp {
constructor() {
console.log('IonicApp Start')
this.items = []
for(let i = 0; i < 1000; i++) {
this.items.push({
title: 'Item ' + i
})
}
}
}
/*
Used to find and register headers in a view, and this directive's
content will be moved up to the common navbar location, and created
using the same context as the view's content area.
*/
@Directive({
selector: 'template[cell]'
})
export class ItemCellTemplate {
constructor(@Parent() list: List, viewContainer: ViewContainerRef, protoViewRef: ProtoViewRef) {
console.log('Item cell template', list, viewContainer, protoViewRef);
this.protoViewRef = protoViewRef;
this.viewContainer = viewContainer;
list.setItemTemplate(this);
}
}
export function main() {
bootstrap(IonicApp);
}

View File

@ -0,0 +1,15 @@
<ion-view nav-title="List">
<ion-content class="padding" #content>
<ion-list inset virtual [items]="items" [content]="content">
<ion-item *cell>
{{$item.title}}
</ion-item>
</ion-list>
<f style="height: 15000px; width: 100%; background-color: green"></f>
</ion-content>
</ion-view>

View File

@ -0,0 +1,109 @@
export class ListVirtualScroll {
constructor(list) {
this.list = list;
this.content = this.list.content;
this.viewportHeight = this.content.domElement.offsetHeight;
this.viewContainer = this.list.itemTemplate.viewContainer;
this.itemHeight = 60;
this.shownItems = {};
this.enteringItems = [];
this.leavingItems = [];
// Compute the initial sizes
this.resize();
this.content.addScrollEventListener((event) => {
this._handleVirtualScroll(event);
});
}
resize() {
this.viewportHeight = this.content.domElement.offsetHeight;
this.viewportScrollHeight = this.content.scrollElement.scrollHeight;
this.virtualHeight = this.list.items.length * this.itemHeight;
this.itemsPerScreen = this.viewportHeight / this.itemHeight;
console.log('VIRTUAL: resize(viewportHeight:', this.viewportHeight,
'viewportScrollHeight:', this.viewportScrollHeight, 'virtualHeight:', this.virtualHeight,
', itemsPerScreen:', this.itemsPerScreen, ')');
}
_handleVirtualScroll(event) {
let item;
let shownItemRef;
let st = event.target.scrollTop;
let sh = event.target.scrollHeight;
let topIndex = Math.floor(st / this.itemHeight);
let bottomIndex = Math.floor((st / this.itemHeight) + this.itemsPerScreen);
let items = this.list.items;
// Key iterate the shown items map
// and compare the index to our index range,
// pushing the items to remove to our leaving
// list if they're ouside this range.
for(let i in this.shownItems) {
if(i < topIndex || i > bottomIndex) {
this.leavingItems.push(this.shownItems[i]);
delete this.shownItems[i];
}
}
let realIndex = 0;
// Iterate the set of items that will be rendered, using the
// index from the actual items list as the map for the
// virtual items we draw
for(let i = topIndex, realIndex = 0; i < bottomIndex && i < items.length; i++, realIndex++) {
item = items[i];
console.log('Drawing item', i);
shownItemRef = this.shownItems[i];
// Is this a new item?
if(!shownItemRef) {
let itemView = this.viewContainer.create(this.list.itemTemplate.protoViewRef, realIndex);
itemView.setLocal('\$implicit', item);
itemView.setLocal('\$item', item);
shownItemRef = new VirtualItemRef(item, i, realIndex, itemView);
this.shownItems[i] = shownItemRef;
this.enteringItems.push(shownItemRef);
}
//tuple.view = viewContainer.create(protoViewRef, tuple.record.currentIndex);
}
while(this.leavingItems.length) {
let itemRef = this.leavingItems.pop();
this.viewContainer.remove(itemRef.realIndex);
}
console.log('VIRTUAL SCROLL: scroll(scrollTop:', st, 'topIndex:', topIndex, 'bottomIndex:', bottomIndex, ')');
console.log('Container has', this.list.domElement.children.length, 'children');
}
cellAtIndex(index) {
}
}
class VirtualItemRef {
constructor(item, index, realIndex, view) {
this.item = item;
this.index = index;
this.realIndex = realIndex;
this.view = view;
}
}