手机应用中,List控件必不可少,而对于List控件,最重要的是其滑动的效果如何。尤其是对于混合开发来说,本来性能就不如原生应用,对List的优化尤显重要。使用过Ionic 1的开发者应该都有如下体会:当<ion-list></ion-list>中包含了大量的<ion-item></ion-item>时,滚动的卡顿效果尤其明显。

Ionic 2提供了VirtualScroll来改善大量数据List的响应问题。Virtual Scroll在HTML5混合应用开发中是一个重要的概念,其基本思路是“所见即所建”,即根据界面的需要对资源进行创建、回收、缓存等操作,活跃页面对应活跃资源。下面通过例子说明Ionic 2的VirtualScroll的使用方式及注意事项:

创建Ionic 2应用

首先我们创建一个新的Ionic 2应用,Ionic 2创建工程的方式和Ionic 1很相似:

$ ionic start virtual-scroll tabs --v2 --ts

这里我们使用了Ionic 2提供的tabs模板工程创建我们的工程,我们在一个tab页面中使用Virtual Scroll,另一个tab页面中使用普通的方式创建列表,以方便进行对比。

创建provider提供数据

为演示出使用Virtual Scroll的效果,我们刻意将数据量放大,比如3000条数据,这样我们创建一个provider提供数据,以便在两个tab页面中都能够使用:

$ ionic g provider items-provider

执行完成后,Ionic CLI会帮我们创建app/providers/items-provider.ts,里面的默认内容我们此处用不到,可以删去,下面是完成后的内容:

import {Injectable} from 'angular2/core';
import 'rxjs/add/operator/map';

@Injectable()
export class ItemsProvider {
    data: Array<any> = [];

    constructor() { }

    loadItems() {
        for(let i=0; i<3000; i++) {
            this.data.push({
                title: `Item${i}`,
                content: `Item${i} content`,
                avatar: 'https://avatars.io/facebook/random'+i
            });
        }

        return Promise.resolve(this.data);
    }
}

loadItems方法是提供数据的主体。

在tab页面中获取数据

上面提到,我们需要使用两个tab页面进行对比,其中一个使用普通的列表,另一个使用Virtual Scroll创建列表。这里我们选择tab1(不使用Virtual Scroll)和tab2(使用Virtual Scroll)进行对比。 首先我们为他们提供数据:

引入provider:

分别在app/pages/page1/page1.tsapp/pages/page2/page2.ts头部引入上一步创建的ItemsProvider

import {ItemsProvider} from '../../providers/items-provider/items-provider';

做完这一步还不够,我们还需要将ItemsProvider作为依赖注入,但在进行依赖注入时要注意保证provider的单例性,即如果我们在所有用到ItemsProvider的组件中都将其注入一次,那就会得到ItemsProvider的多个实例,破换了其单例性。我们需要在这些组件的父组件中注入provider,对于tab页面来说,其父组件为app/pages/tabs/tabs.ts,我们可以在其@Page修饰器中注入ItemsProvider依赖,不过不要忘记引入:

import {ItemsProvider} from '../../providers/items-provider/items-provider';

//inject provider once in parent component
@Page({
    providers: [ItemsProvider]
})

获取数据

分别在app/pages/page1/page1.tsapp/pages/page2/page2.tsngOnInit()函数中获取数据:

items: Array<any> = [];
ngOnInit() {
    this.itemsProvider.loadItems().then(data => {
        this.items = data;
    });
}

注意,对于耗时操作,如网络读取等,尽量不要放在constructor中进行,constructor中应该只进行一些简单的初始化工作。下面是完整的app/pages/page1/page1.ts

import {Page} from 'ionic-angular';
import {ItemsProvider} from '../../providers/items-provider/items-provider';

@Page({
    templateUrl: 'build/pages/page1/page1.html'
})
export class Page1 {
    items: Array<any> = [];
    constructor(private itemsProvider: ItemsProvider) {
    }

    ngOnInit() {
        this.itemsProvider.loadItems().then(data => {
            this.items = data;
            console.log(this.items);
        });
    }
}

app/pages/page1/page2.ts中的内容和app/pages/page1/page1.ts没有什么区别,只是templateUrl和类名不一致而已。

创建列表

普通列表

修改app/pages/page1/page1.html文件,创建列表:

<ion-navbar *navbar>
    <ion-title>Tab 1</ion-title>
</ion-navbar>

<ion-content padding class="page1">
    <ion-list>
        <ion-item *ngFor="#item of items">
            <ion-avatar item-left>
                <ion-img [src]="item.avatar"></ion-img>
            </ion-avatar>
            <h2>{{item.title}}</h2>
            <p>{{item.content}}</p>
        </ion-item>
    </ion-list>
</ion-content>

这里我们只是使用了*ngFor遍历所有数据并创建列表。此时可以运行应用,查看普通列表的滑动效果。

Virtual Scroll列表

修改app/pages/page1/page2.html文件,创建列表:

<ion-navbar *navbar>
    <ion-title>
        Tab 2
    </ion-title>
</ion-navbar>

<ion-content class="page2">
    <ion-list [virtualScroll]='items'>
        <ion-item *virtualItem='#item'>
            <ion-avatar item-left>
                <ion-img [src]="item.avatar"></ion-img>
            </ion-avatar>
            <h2>{{ item.title }}</h2>
            <p>{{ item.content }}</p>
        </ion-item>
    </ion-list>
</ion-content>

这里的语法和创建普通列表稍有差别,[virtualScroll]是我们的数据源,数组类型,对应于*ngFor里面的数据源,而*virtualItem表示数据源中的单条数据。即使语法稍微不同,但其中概念一致,都需要遍历数据源中的数据,并创建列表。

此时运行应用,可以很直观地看到tab1和tab2中两种不同方式列表滑动效果的不同。

Virtual Scroll使用注意事项

  • 使用<ion-img>标签代替<img>标签,<ion-img>标签能够阻止不必要的网络请求,提升性能,并在请求未成功前提供默认的图片,以防图片空缺;
  • 图片的尺寸不应变化;
  • 使用approxItemWidthapproxItemHeight属性能够提升Virtual Scroll的性能,不过它们只是近似值,仅作为计算的参考;
  • 数据源变化会造成Virtual Scroll的重新构造,非常耗费性能,尽量不要改变数据源;

approxItemWidth和approxItemHeight的使用方式

approxItemWidth是列表项宽度的近似值,可以使用px%作为单位,默认值是100%,字符串类型;approxItemHeight是列表项高度的近似值,使用px作为单位,默认值是40px,字符串类型。使用它们可以提升性能,下面是其使用方式:

<ion-list [virtualScroll]='items' [approxItemHeight]="'36px'" [approxItemWidth]="'90%'">

注意赋值时要使用"''"的方式,使用""''会产生错误。