原理
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();
}
}
http://www.wazhlh.com/content/uploadfile/201807/682a1532078710.png
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();
}
}
最终效果图:
本文转自:虣虣博客,让游戏开发更简单
声明:若无特殊注明,本文皆为( 虣虣 )原创,转载请保留文章出处(http://www.wazhlh.com/?post=15)。