CocosCreate之ScrollView動態更新數據的實現原理


  ScrollView是比較常用的UI組件之一,游戲中的任務榜、排行榜都少不了它,實際使用中存在一個問題,例如:在排行榜中要顯示前100名玩家,如果真的把這100名玩家的信息全部創建,並加載進ScrollView,對移動設備的寶貴內存會是巨大的浪費。其實玩家在屏幕上總能看到的最多只有7、8項而已,所以實際上只用創建比顯示多一點的數量,再通過緩沖區實時動態更新數據,就可以給玩家呈現出盡可能多數量的列表(依內存而定,理論上無限),即節省內存,也不影響性能。

        在CocosCreate官方提供的example中,有個ScrollView的例子,就使用了動態更新數據,我把它摘出來做為一個單獨的工程,加上中文注釋,並對代碼稍做改動,運行截圖如下所示:

  可以看到,列表中顯示一共有100行,但一屏最多展示7.5行,而實際上在內存中只真正創建了15項,所有看到的這100行,都是在上下滾動事件中,通過動態更新這15項的坐標和內容來實現的。要了解它的運行原理,先看下圖:

                         

  如圖把ScrollView分成三部分,按區域從小到大依次是:

1、屏幕可見區。指屏幕上玩家可看可操作的列表區域,在此demo中有7.5行;

2、緩沖區。指內存中真正創建了的列表所占的區域,在此demo中有15行;

3、content區。指整個ScrollView要顯示的區域,在此demo中有100行;

  下面再分三種情況講解:

1、剛初始化完成時:此時在右側按鈕上提示有100行,實際上只創建了第1-15行,而玩家能看到的是第1-7行。如果玩家想要看到更多,必然會向上或向下滾動屏幕;

2、向上滾動時:在移動設備上,如果玩家想要看到下面的行,所做的操作是觸摸往上滑動,則整個content區往上移動,也帶動content區的item往上移動,update函數會不斷遍歷所創建的15項item,如果檢測到某item的y坐標超出了緩沖區的上邊界(該item已經被玩家看過或不想再看),則把該item往下移動一個緩沖區的高度(移動該item到玩家即將看到的位置),並更新它的顯示ID;

3、向下滾動時:同理,content區的item往下移動,update不斷遍歷所創建的15項item,如果檢測到某item的y坐標越過了緩沖區的下邊界(該item已經被玩家看過或不想再看),則把該item往上移動一個緩沖區的高度(移動該item到玩家即將看到的位置),並更新它的顯示ID;

  關鍵代碼如下所示:

    // 返回item在ScrollView空間的坐標值
    getPositionInView: function (item) {
        let worldPos = item.parent.convertToWorldSpaceAR(item.position);
        let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos);
        return viewPos;
    },

    // 每幀調用一次。根據滾動位置動態更新item的坐標和顯示(所以spawnCount可以比totalCount少很多)
    update: function(dt) {
        this.updateTimer += dt;
        if (this.updateTimer < this.updateInterval) {
            return; // we don't need to do the math every frame
        }
        this.updateTimer = 0;
        let items = this.items;
        // 如果當前content的y坐標小於上次記錄值,則代表往下滾動,否則往上。
        let isDown = this.scrollView.content.y < this.lastContentPosY;
        // 實際創建項占了多高(即它們的高度累加)
        let offset = (this.itemTemplate.height + this.spacing) * items.length;
        let newY = 0;

        // 遍歷數組,更新item的位置和顯示
        for (let i = 0; i < items.length; ++i) {
            let viewPos = this.getPositionInView(items[i]);
            if (isDown) {
                // 提前計算出該item的新的y坐標
                newY = items[i].y + offset;
                // 如果往下滾動時item已經超出緩沖矩形,且newY未超出content上邊界,
                // 則更新item的坐標(即上移了一個offset的位置),同時更新item的顯示內容
                if (viewPos.y < -this.bufferZone && newY < 0) {
                    items[i].setPositionY(newY);
                    let item = items[i].getComponent('Item');
                    let itemId = item.itemID - items.length; // update item id
                    item.updateItem(i, itemId);
                }
            } else {
                // 提前計算出該item的新的y坐標
                newY = items[i].y - offset;
                // 如果往上滾動時item已經超出緩沖矩形,且newY未超出content下邊界,
                // 則更新item的坐標(即下移了一個offset的位置),同時更新item的顯示內容
                if (viewPos.y > this.bufferZone && newY > -this.content.height) {
                    items[i].setPositionY(newY);
                    let item = items[i].getComponent('Item');
                    let itemId = item.itemID + items.length;
                    item.updateItem(i, itemId);
                }
            }
        }

        // 更新lastContentPosY和總項數顯示
        this.lastContentPosY = this.scrollView.content.y;
        this.lblTotalItems.string = "Total Items: " + this.totalCount;
},

  在此demo中,ScrollView列表顯示的item其實是個按鈕,而它做為預制資源,其實可以在Creator中編輯成各種UI,並不局限於按鈕形式。

  最后,附上該Creator工程完整源代碼的github地址:https://github.com/foupwang/CocosCreatorScrollViewDemo.git


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM