原理
ScrollView是比较常用的UI组件之一,游戏中的任务榜、排行榜都少不了它,也是UI界面最费的地方。当数据量大的时候,但是效率不好。
为了解决这个问题也有很多方案:
-
方案一: 用摄像机代替panel进行裁切和移动
-
方案二: 显示区域外的Item的Active关闭。这个做法不治本,但是确实能让流畅度提高很多。
-
方案三: 用脚本实现了循环改变子物体位置的功能。
方法1: 效果实在弊端也多,毕竟多了一个摄像机,割裂了UI间的层次关系,斟酌使用。
方法2: 简单实用,效果有限。
方法3:做法复杂,从根本解决问题,应该在开发早期就写好功能,直接使用。(本文采用这种方法)
先看原理图:
1、屏幕可见区。指屏幕上玩家可看可操作的列表区域。
2、缓冲区。指内存中真正创建了的列表所占的区域。
3、content区。指整个ScrollView要显示的区域。
原理就是这样,但实际上为了方便,我改成了这样。
什么都不说了。实干才是硬道理。直接上源码。
ScrollView性能优化实现
const { ccclass, property } = cc._decorator;
@ccclass export default class BaseScrollView extends cc.ScrollView {
@property(cc.Prefab) cellItemPrefab: cc.Prefab = null; @property(cc.ScrollView) scrollView: cc.ScrollView = null; @property({ tooltip: "是否是垂直滚动" } || cc.Boolean) _horizontal: boolean = false; @property({ tooltip: "是否是垂直滚动" }) set horizontal(value) { this._horizontal = value; this._vertical = !value; } @property({ tooltip: "是否是垂直滚动" }) get horizontal() { return this._horizontal; } @property({ tooltip: "是否是水平滚动" } || cc.Boolean) _vertical: boolean = true; @property({ tooltip: "是否是水平滚动" }) set vertical(value) { this._horizontal = !value; this._vertical = value; } @property({ tooltip: "是否是水平滚动" }) get vertical() { return this._vertical; } @property(cc.Float) spacing: number = 10; /** 存放 cell 的列表 */ cellItemList: cc.Node[] = []; /** cell 大小 */ cellItemTempSize: cc.Size = null; /** 滑动之前的 content 的位置 */ lastContentPosition: cc.Vec2 = cc.v2(0, 0); cellDatalist: any[] = []; isUpdateFrame: boolean = true; start() { this.scrollView.content.on("position-changed", this._updateContentView.bind(this)); // if (this._vertical) { // this.scrollView.content.on("position-changed", this._updateVerticalContentView.bind(this)); // } else { // this.scrollView.content.on("position-changed", this._updateHorizontalContentView.bind(this)); // } this.initUI(); } /** 初始化UI */ initUI() { // TODO 由子类继承,并实现 } /** 初始化cellData的数据 */ initCellDataList(cellDataList: any[]) { this.cellDatalist = cellDataList; } /** 创建cell List列表 */ createCellList() { if (this._vertical) { this._createVerticalCellList(); } else { this._createHorizontalCellList(); } } _createVerticalCellList() { let count = 10; for (var i = 0; i < this.cellDatalist.length; i++) { if (i > count - 1) { return; } var node = cc.instantiate(this.cellItemPrefab); if (i == 0) { this.cellItemTempSize = node.getContentSize(); count = Math.ceil(this.node.height / node.height) * 2; let height = this.cellDatalist.length * (this.cellItemTempSize.height + this.spacing); this.scrollView.content.setContentSize(cc.size(this.scrollView.content.width, height)); } node["cellID"] = i; this.scrollView.content.addChild(node); this.cellItemList.push(node); let logicComponent = this.cellItemList[i].getComponent(this.cellItemPrefab.name); if (logicComponent && logicComponent.updateView) { logicComponent.updateView(i, this.cellDatalist[i]); } node.y = - i * (this.cellItemTempSize.height + this.spacing); } } _createHorizontalCellList() { let count = 10; for (var i = 0; i < this.cellDatalist.length; i++) { if (i > count - 1) { return; } var node = cc.instantiate(this.cellItemPrefab); if (i == 0) { this.cellItemTempSize = node.getContentSize(); count = Math.ceil(this.node.width / node.width) * 2; let width = this.cellDatalist.length * (this.cellItemTempSize.width + this.spacing); this.scrollView.content.setContentSize(cc.size(width, this.scrollView.content.height)); } node["cellID"] = i; this.scrollView.content.addChild(node); this.cellItemList.push(node); let logicComponent = this.cellItemList[i].getComponent(this.cellItemPrefab.name); if (logicComponent && logicComponent.updateView) { logicComponent.updateView(i, this.cellDatalist[i]); } node.x = (this.cellItemTempSize.width + this.spacing) * i; } } _getPositionInView(item: cc.Node) { let worldPos = item.parent.convertToWorldSpaceAR(item.position); let viewPos = this.node.convertToNodeSpaceAR(worldPos); return viewPos; } _updateContentView() { if (this._vertical) { if (this.isUpdateFrame) { this.isUpdateFrame = false; this.scheduleOnce(this._updateVerticalContentView.bind(this), 0); } } else { if (this.isUpdateFrame) { this.isUpdateFrame = false; this.scheduleOnce(this._updateHorizontalContentView.bind(this), 0); } } } _updateVerticalContentView() { let isDown = this.scrollView.content.y < this.lastContentPosition.y; let offsetY = (this.cellItemTempSize.height + this.spacing) * this.cellItemList.length; let offset = offsetY / 4; let newY = 0; for (var i = 0; i < this.cellItemList.length; i++) { let viewPos = this._getPositionInView(this.cellItemList[i]); if (isDown) { newY = this.cellItemList[i].y + offsetY; if (viewPos.y < -(offset * 3) && newY <= 0) { this.cellItemList[i].y = newY; let idx = this.cellItemList[i]["cellID"] - this.cellItemList.length; let logicComponent = this.cellItemList[i].getComponent(this.cellItemPrefab.name); if (logicComponent && logicComponent.updateView) { logicComponent.updateView(idx, this.cellDatalist[idx]); } this.cellItemList[i]["cellID"] = idx; } } else { newY = this.cellItemList[i].y - offsetY; if (viewPos.y > offset && newY > -this.scrollView.content.height) { this.cellItemList[i].y = newY; let idx = this.cellItemList[i]["cellID"] + this.cellItemList.length; let logicComponent = this.cellItemList[i].getComponent(this.cellItemPrefab.name); if (logicComponent && logicComponent.updateView) { logicComponent.updateView(idx, this.cellDatalist[idx]); } this.cellItemList[i]["cellID"] = idx; } } } this.lastContentPosition = this.scrollView.content.position; this.isUpdateFrame = true; } _updateHorizontalContentView() { let isLeft = this.scrollView.content.x < this.lastContentPosition.x; let offsetX = (this.cellItemTempSize.width + this.spacing) * this.cellItemList.length; let offset = offsetX / 4; let newX = 0; for (var i = 0; i < this.cellItemList.length; i++) { let viewPos = this._getPositionInView(this.cellItemList[i]); if (isLeft) { newX = this.cellItemList[i].x + offsetX; if (viewPos.x < -offset && newX < this.scrollView.content.width) { this.cellItemList[i].x = newX; let idx = this.cellItemList[i]["cellID"] + this.cellItemList.length; let logicComponent = this.cellItemList[i].getComponent(this.cellItemPrefab.name); if (logicComponent && logicComponent.updateView) { logicComponent.updateView(idx, this.cellDatalist[idx]); } this.cellItemList[i]["cellID"] = idx; } } else { newX = this.cellItemList[i].x - offsetX; if (viewPos.x > offset * 3 && newX >= 0) { this.cellItemList[i].x = newX; let idx = this.cellItemList[i]["cellID"] - this.cellItemList.length; let logicComponent = this.cellItemList[i].getComponent(this.cellItemPrefab.name); if (logicComponent && logicComponent.updateView) { logicComponent.updateView(idx, this.cellDatalist[idx]); } this.cellItemList[i]["cellID"] = idx; } } } this.lastContentPosition = this.scrollView.content.position; this.isUpdateFrame = true; }
} const {ccclass, property} = cc._decorator; @ccclass export default class BaseCell extends cc.Component {
updateView(idx: number, data: any) { // TODO 子类继承 }
}
言外话题:此次实现检查并刷新ScrollView cell 位置,我并没有放在update里面,而是当ScrollView滑动时才每帧刷新。有人会说,为啥不直接放在update里面做更新。考虑到ScrollView在没有滑动是至少update会有一次判断(其实此处的性能也可以直接忽略)。
Demo1(水平滑动)
import BaseScrollView from "./BaseScrollView";
const {ccclass, property} = cc._decorator;
@ccclass
export default class RankScrollView extends BaseScrollView {
/** 继承父类的方法 */
initUI() {
let list: number[] = [];
for (let i = 0; i < 100; i++) {
list.push(i);
}
this.initCellDataList(list);
this.createCellList();
}
}
import BaseCell from "./BaseCell";
const {ccclass, property} = cc._decorator;
@ccclass
export default class Ranktem extends BaseCell {
@property(cc.Label)
lable: cc.Label = null;
updateView(idx: number, data: any) {
this.lable.string = (data as number).toString();
}
}
Demo2(垂直滑动)
import BaseCell from "./BaseCell";
const {ccclass, property} = cc._decorator; @ccclass export default class Ranktem extends BaseCell {
@property(cc.Label) lable: cc.Label = null; updateView(idx: number, data: any) { this.lable.string = (data as number).toString(); }
}
import BaseCell from "./BaseCell";
const {ccclass, property} = cc._decorator; @ccclass export default class ShopItem extends BaseCell {
@property(cc.Label) lable: cc.Label = null; updateView(idx: number, data: any) { this.lable.string = (data as number).toString(); }
}
最终效果图:
关注【游戏讲坛】微信公众号,获取最新动态!

膜拜

Reply请问为什么要写成this.scheduleOnce(this._updateVerticalContentView.bind(this), 0);
Reply
你好有git地址吗
Reply