列表视图性能优化
前言
滚动列表,这种东西在游戏中很常见。而cocos creator 中的 ScrollView + Layout
基本可以实现大部分需求,但如果是 背包系统、排行榜、好友列表功能等数据量庞大的列表,ScrollView + Layout
会严重影响性能。
# 一. 实现方式
在创建多个类表项的时候,只需要创建超过屏幕范围数量的 Item
,然后,只需要增加 content
的长度,到所有 item
的长度之后,就可以了。
在滑动的时候,按照上面的逻辑处理,就可以实现循环滚动。
# 二. 表现效果
# 1. 布局方式:
支持: 垂直布局,水平布局,网格布局(水平网格、垂直网格)
# 2. 功能:
- 在末尾增加一项;
- 在末尾增加一列;
- 在指定位置添加一项;
- 删除某一项;
- 更改某一项;
- 手动滚动到顶部;
- 手动滚动到底部;
- 手动滚动到某一项;
- 监听滚动到顶部;
- 监听滚动到底部;
# 三. 代码逻辑
自定义组件: ListView.js
:
/** ============================= 枚举值 ============================= */
/**
* 列表排列方式
*/
const ListType = cc.Enum({
// 水平排列
Horizontal: 1,
// 垂直排列
Vertical: 2,
// 网格排列
Grid: 3
});
/**
* 网格布局中的方向
*/
const GridAxisType = cc.Enum({
// 水平排列
Horizontal: 1,
// 垂直排列
Vertical: 2,
});
/** ================================================================ */
cc.Class({
extends: cc.Component,
editor:{
menu:"自定义组件/ListView"
},
properties: {
/** ============================= 属性面板 ============================= */
listType: {
type: ListType,
default: ListType.Horizontal,
tooltip: '排列方式',
notify: function () {
this.onListTypeChange();
},
},
listItem: {
type: cc.Node,
default: null,
tooltip: '列表项:Node节点',
notify: function () {
this.onListTypeChange();
},
},
itemName: {
default: 'itemName',
tooltip: '列表项:脚本名称',
notify: function () {
this.onListTypeChange();
},
},
gridAxisType: {
type: GridAxisType,
default: GridAxisType.Vertical,
tooltip: '网格布局的方向',
visible: false,
notify: function () {
this.onListTypeChange();
},
},
spaceX: {
type: cc.Float,
default: 0,
tooltip: '列表项: 之间的 x 间距',
visible: true,
notify: function () {
this.onListTypeChange();
},
},
spaceY: {
type: cc.Float,
default: 0,
tooltip: '列表项: 之间的 y 间距',
visible: false,
notify: function () {
this.onListTypeChange();
},
},
paddingTop : {
type: cc.Float,
default: 0,
tooltip: '列表: 上间距',
visible: false,
notify: function () {
this.onListTypeChange();
},
},
paddingBottom : {
type: cc.Float,
default: 0,
tooltip: '列表: 下间距',
visible: false,
notify: function () {
this.onListTypeChange();
},
},
paddingLeft : {
type: cc.Float,
default: 0,
tooltip: '列表: 左间距',
visible: true,
notify: function () {
this.onListTypeChange();
},
},
paddingRight : {
type: cc.Float,
default: 0,
tooltip: '列表: 右间距',
visible: true,
notify: function () {
this.onListTypeChange();
},
},
scrollTopEvent: {
type: cc.Component.EventHandler,
default: [],
tooltip: '列表: 滚动到顶部时会触发该回调'
},
scrollBottomEvent: {
type: cc.Component.EventHandler,
default: [],
tooltip: '列表: 滚动到底部时会触发该回调'
},
},
/** ============================= 系统方法 ============================= */
ctor () {
this.onListTypeChange();
},
onLoad () {
// ====================== 滚动容器 ===============================
// 列表容器
this.scrollView = this.node.getComponent(cc.ScrollView);
// 列表内容容器
this.content = this.scrollView.content;
this.content.anchorX = 0;
this.content.anchorY = 1;
this.content.removeAllChildren();
this._initListener();
//======================== 列表项 ===========================
// 列表项数据
this.itemDataList = [];
// 应创建的实例数量
this.spawnCount = 0;
// 存放列表项实例的数组
this.itemList = [];
// item的高度
this.itemHeight = this.listItem.height;
// item的宽度
this.itemWidth = this.listItem.width;
// 存放不再使用中的列表项 */
this.itemPool = [];
//======================= 计算参数 ==========================
// 距离scrollView中心点的距离,超过这个距离的item会被重置,
// 一般设置为 scrollVIew.height/2 + item.heigt/2 + space,因为这个距离item正好超出scrollView显示范围
this.halfScrollView = 0;
// 上一次content的X值
// 用于和现在content的X值比较,得出是向左还是向右滚动
this.lastContentPosX = 0;
//上一次content的Y值
// 用于和现在content的Y值比较,得出是向上还是向下滚动
this.lastContentPosY = 0;
// 网格行数
this.gridRow = 0;
// 网格列数
this.gridCol = 0;
// 刷新时间,单位s
this.updateTimer = 0;
// 刷新间隔,单位s
this.updateInterval = 0.05;
// 是否滚动容器
this.bScrolling = false;
},
update (dt) {
if (this.bScrolling == false) {
return;
}
this.updateTimer += dt;
if (this.updateTimer < this.updateInterval) {
return;
}
this.updateTimer = 0;
this.bScrolling = false;
this._updateArrange();
},
onDestroy () {
// 清理列表项
for (let i = 0; i < this.itemList.length; i++) {
if (cc.isValid(this.itemList[i], true)) {
this.itemList[i].destory();
}
}
this.itemList.length = 0;
// 清理对象池
for (let i = 0; i < this.itemPool.length; i++) {
if (cc.isValid(this.itemList[i], true)) {
this.itemPool[i].destory();
}
}
this.itemPool.length = 0;
// 清理列表数据
this.itemDataList.length = 0;
},
/** ============================= 公共方法 ============================= */
/**
* 设置列表数据
* (列表数据重复使用,如果列表数据改变,则需要重新设置一遍数据)
*
* @param data item 数据列表
*/
setData (data) {
this.itemDataList = data.slice();
this._updateContent();
},
/**
* 获取列表数据
*/
getListData () {
return this.itemDataList;
},
/**
* 添加一项数据到列表的末尾
*
* @param data 数据
*/
addItem (data) {
this.itemDataList.push(data);
this._updateContent();
},
/**
* 添加一组数据到列表的末尾
*
* @param dataList 数据列表
*/
addItems (dataList) {
for (let i = 0; i < dataList.length; i++) {
let data = dataList[i];
this.itemDataList.push(data);
}
this._updateContent();
},
/**
* 添加一项数据到列表的指定位置
*
* @param data 数据
* @param index 索引 (从 0 开始)
*/
addItemAtIndex (data, index) {
if (this.itemDataList[index] != null || this.itemDataList.length == index) {
this.itemDataList.splice(index, 1, data);
this._updateContent();
}
},
/**
* 删除一项数据
*
* @param index 索引 (从 0 开始)
*/
deleteItem (index) {
if (this.itemDataList[index] != null) {
this.itemDataList.splice(index, 1);
this._updateContent();
}
},
/**
* 改变一项数据
*
* @param data 数据
* @param index 索引 (从 0 开始)
*/
changeItem (data, index) {
if (this.itemDataList[index] != null) {
this.itemDataList[index] = data;
this._updateContent();
}
},
/**
* 滚动到顶部
*/
scrollToTop () {
this.scrollToIndex(0);
},
/**
* 滚动到底部
*/
scrollToBottom () {
this.scrollToIndex(this.itemDataList.length-1);
},
/**
* 滚动到指定索引
*
* @param index 索引
*/
scrollToIndex (index) {
if (this.startScroll) {
console.log('列表还在滚动中');
return;
}
if (this.listType == ListType.Horizontal) {
this._createList(index, cc.v2(index * (this.itemWidth + this.spaceX), 0));
return;
}
if (this.listType == ListType.Vertical) {
this._createList(index, cc.v2(0,index * (this.itemHeight + this.spaceY)));
return;
}
if (this.gridAxisType == GridAxisType.Horizontal) {
let gridCol = parseInt(index / this.gridRow);
let width = gridCol * this.itemWidth + (gridCol - 1) * this.spaceX + this.paddingLeft + this.paddingRight;
this._createList(gridCol * this.gridRow, cc.v2(width, 0));
return;
}
if (this.gridAxisType == GridAxisType.Vertical) {
let gridRow = parseInt(index / this.gridCol);
let height = gridRow * this.itemHeight + (gridRow - 1) * this.spaceY + this.paddingTop + this.paddingBottom;
this._createList(gridRow*this.gridCol, cc.v2(0, height));
return;
}
},
/** ============================= 私有方法 ============================= */
_initListener () {
this.scrollView.node.on('scroll-began', this.onScrollBegin, this);
this.scrollView.node.on('scroll-ended', this.onScrollEnded, this);
this.scrollView.node.on('scrolling', this.onScrolling, this);
if (this.listType == ListType.Horizontal) {
this.scrollView.node.on('scroll-to-left', this.onScrollToTop, this);
this.scrollView.node.on('scroll-to-right', this.onScrollToBottom, this);
} else if (this.listType == ListType.Vertical) {
this.scrollView.node.on('scroll-to-top', this.onScrollToTop, this);
this.scrollView.node.on('scroll-to-bottom', this.onScrollToBottom, this);
} else if (this.listType == ListType.Grid) {
if (this.gridAxisType == GridAxisType.Horizontal) {
this.scrollView.node.on('scroll-to-left', this.onScrollToTop, this);
this.scrollView.node.on('scroll-to-right', this.onScrollToBottom, this);
} else if (this.gridAxisType == GridAxisType.Vertical) {
this.scrollView.node.on('scroll-to-top', this.onScrollToTop, this);
this.scrollView.node.on('scroll-to-bottom', this.onScrollToBottom, this);
}
}
},
/** ok 获取第一个 item 位置 */
_updateContent () {
// 显示列表实例为 0 个
if (this.itemList.length == 0) {
this._countListParam();
this._createList(0, cc.v2(0, 0));
return;
}
if (this.listType == ListType.Horizontal) {
this.itemList.sort(function (a, b) {
return a.x - b.x;
});
} else if (this.listType == ListType.Vertical) {
this.itemList.sort(function (a, b) {
return b.y - a.y;
});
} else if (this.listType == ListType.Grid) {
if (this.gridAxisType == GridAxisType.Horizontal) {
this.itemList.sort(function (a, b) {
return b.y - a.y;
});
this.itemList.sort(function (a, b) {
return a.x - b.x;
});
} else if (this.gridAxisType == GridAxisType.Vertical) {
this.itemList.sort(function (a, b) {
return a.x - b.x;
});
this.itemList.sort(function (a, b) {
return b.y - a.y;
});
}
}
this._countListParam();
let item = this.itemList[0];
let startIndex = item.getComponent(this.itemName).itemIndex;
if (this.listType == ListType.Grid && this.gridAxisType == GridAxisType.Horizontal) {
startIndex += (startIndex + this.spawnCount) % this.gridRow;
} else if (this.listType == ListType.Grid && this.gridAxisType == GridAxisType.Vertical) {
startIndex += (startIndex + this.spawnCount) % this.gridCol;
}
let offset = this.scrollView.getScrollOffset();
offset.x = -offset.x;
console.log('--------', offset);
this._createList(startIndex, offset);
},
/** ok 计算列表参数 */
_countListParam () {
let dataLen = this.itemDataList.length;
// spawnCount, 比当前scrollView容器能放下的item数量再加上2个
if (this.listType == ListType.Horizontal) {
let padding = this.paddingLeft + this.paddingRight;
this.scrollView.horizontal = true;
this.scrollView.vertical = false;
this.content.width = dataLen * this.itemWidth + (dataLen - 1) * this.spaceX + padding;
this.content.height = this.content.parent.height;
this.spawnCount = Math.round(this.scrollView.node.width / (this.itemWidth + this.spaceX)) + 2;
this.halfScrollView = this.scrollView.node.width / 2 + this.itemWidth / 2 + this.spaceX;
return;
}
if (this.listType == ListType.Vertical) {
let padding = this.paddingTop + this.paddingBottom;
this.scrollView.horizontal = false;
this.scrollView.vertical = true;
this.content.width = this.content.parent.width;
this.content.height = dataLen * this.itemHeight + (dataLen - 1) * this.spaceY + padding;
this.spawnCount = Math.round(this.scrollView.node.height / (this.itemHeight + this.spaceY)) + 2;
this.halfScrollView = this.scrollView.node.height / 2 + this.itemHeight / 2 + this.spaceY;
return;
}
if (this.gridAxisType == GridAxisType.Horizontal) {
let paddingL = this.paddingLeft + this.paddingRight;
let paddingT = this.paddingTop + this.paddingBottom;
this.scrollView.horizontal = true;
this.scrollView.vertical = false;
this.content.height = this.content.parent.height;
if (paddingT + this.itemHeight + this.spaceY > this.content.height) {
this.paddingTop = 0;
this.paddingBottom = 0;
console.error("paddingTop 或 paddingBottom 过大");
}
this.gridRow = Math.floor((this.content.height - paddingT) / (this.itemHeight + this.spaceY));
this.gridCol = Math.ceil(dataLen / this.gridRow);
this.content.width = this.gridCol * this.itemWidth + (this.gridCol - 1) * this.spaceX + paddingL;
this.spawnCount = Math.round(this.scrollView.node.width / (this.itemWidth + this.spaceX)) * this.gridRow + this.gridRow * 2;
this.halfScrollView = this.scrollView.node.width / 2 + this.itemWidth / 2 + this.spaceX;
return;
}
if (this.gridAxisType == GridAxisType.Vertical) {
let paddingL = this.paddingLeft + this.paddingRight;
let paddingT = this.paddingTop + this.paddingBottom;
this.scrollView.horizontal = false;
this.scrollView.vertical = true;
this.content.width = this.content.parent.width;
if (paddingL + this.itemWidth + this.spaceX > this.content.width) {
this.paddingLeft = 0;
this.paddingRight = 0;
console.error("paddingTop 或 paddingBottom 过大");
}
this.gridCol = Math.floor((this.content.width - paddingL) / (this.itemWidth + this.spaceX));
this.gridRow = Math.ceil(dataLen / this.gridCol);
this.content.height = this.gridRow * this.itemHeight + (this.gridRow - 1) * this.spaceY + paddingT;
this.spawnCount = Math.round(this.scrollView.node.height / (this.itemHeight + this.spaceY)) * this.gridCol + this.gridCol * 2;
this.halfScrollView = this.scrollView.node.height / 2 + this.itemHeight / 2 + this.spaceY;
return;
}
},
/**
* ok 创建列表
*
* @param startIndex 起始显示的数据索引 0 表示第一项
* @param offset scrollView 偏移量
*/
_createList (startIndex, offset) {
if (this.itemDataList.length > this.spawnCount && (startIndex + this.spawnCount - 1) >= this.itemDataList.length) {
// 当需要显示的长度 > 虚拟列表长度, 删除末尾几个数据时,列表需要重置位置到最低端
startIndex = this.itemDataList.length - this.spawnCount;
offset = this.scrollView.getMaxScrollOffset();
} else if (this.itemDataList.length <= this.spawnCount) {
// 当需要显示的长度 <= 虚拟列表长度,隐藏多余的虚拟列表项
startIndex = 0;
}
for (let i = 0; i < this.spawnCount; i++) {
let item = null;
if (i + startIndex < this.itemDataList.length) {
// 需要显示的数据索引在范围内: item显示
item = this.itemList[i];
if (item == null) {
item = this._getItem();
this.itemList.push(item);
item.parent = this.content;
}
} else {
// 需要显示的数据索引超过了数据范围,item 隐藏
if (this.itemList.length > (this.itemDataList.length - startIndex)) {
item = this.itemList.pop();
item.removeFromParent();
this.itemPool.push(item);
}
continue;
}
let itemComponent = item.getComponent(this.itemName);
itemComponent.itemIndex = i + startIndex;
itemComponent.data = this.itemDataList[i + startIndex];
itemComponent.dataChanged();
// 因为content的锚点X是0 :
// 所以item的x值: 是content.with/2表示居中,锚点Y是1,
// 所以item的y值: 从content顶部向下是0到负无穷。所以item.y= -item.height/2时,是在content的顶部。
if (this.listType == ListType.Horizontal) {
let x = item.width * (0.5 + i + startIndex) + this.spaceX * (i + startIndex) + this.paddingLeft;
let y = -this.content.height / 2;
item.setPosition(x, y);
} else if (this.listType == ListType.Vertical) {
let x = this.content.width / 2;
let y = -item.height * (0.5 + i + startIndex) - this.spaceY * (i + startIndex) - this.paddingTop;
item.setPosition(x, y);
} else if (this.listType == ListType.Grid) {
if (this.gridAxisType == GridAxisType.Horizontal) {
let row = (i + startIndex) % this.gridRow;
let col = Math.floor((i + startIndex) / this.gridRow);
let x = item.width * (0.5 + col) + this.spaceX * col + this.paddingLeft;
let y = -item.height * (0.5 + row) - this.spaceY * row - this.paddingTop;
item.setPosition(x, y);
item.opacity = 255;
} else if (this.gridAxisType == GridAxisType.Vertical) {
let row = Math.floor((i + startIndex) / this.gridCol);
let col = (i + startIndex) % this.gridCol;
let x = item.width * (0.5 + col) + this.spaceX * col + this.paddingLeft;
let y = -item.height * (0.5 + row) - this.spaceY * row - this.paddingTop;
item.setPosition(x, y);
item.opacity = 255;
}
}
}
this.scrollView.scrollToOffset(offset);
},
/** ok 从对象池中获取节点 */
_getItem () {
if (this.itemPool.length == 0) {
return cc.instantiate(this.listItem);
} else {
return this.itemPool.pop();
}
},
/** ok 获取item在scrollView的局部坐标 */
_getPositionInView(item) {
let worldPos = item.parent.convertToWorldSpaceAR(item.position);
let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos);
return viewPos;
},
/** ok 更新排列 */
_updateArrange () {
if (this.listType == ListType.Horizontal) {
this._updateHorizontal();
return;
}
if (this.listType == ListType.Vertical) {
this._updateVertical();
return;
}
if (this.gridAxisType == GridAxisType.Horizontal) {
this._updateGridHorizontal();
return;
}
if (this.gridAxisType == GridAxisType.Vertical) {
this._updateGridVertical();
return;
}
},
/** ok 更新水平排列 */
_updateHorizontal () {
let items = this.itemList;
let item = null;
let bufferZone = this.halfScrollView;
let isRight = this.scrollView.content.x > this.lastContentPosX;
let offset = (this.itemWidth + this.spaceX) * items.length;
for (let i = 0; i < items.length; i++) {
item = items[i];
let viewPos = this._getPositionInView(item);
if (isRight) {
//item右滑时,超出了scrollView右边界,将item移动到左方复用,item移动到左方的位置必须不超过content的左边界
if (viewPos.x > bufferZone && item.x - offset - this.paddingLeft > 0) {
let itemComponent = item.getComponent(this.itemName);
let itemIndex = itemComponent.itemIndex - items.length;
itemComponent.itemIndex = itemIndex;
itemComponent.data = this.itemDataList[itemIndex];
itemComponent.dataChanged();
item.x = item.x - offset;
}
} else {
//item左滑时,超出了scrollView左边界,将item移动到右方复用,item移动到右方的位置必须不超过content的右边界
if (viewPos.x < -bufferZone && item.x + offset + this.paddingRight < this.content.width) {
let itemComponent = item.getComponent(this.itemName);
let itemIndex = itemComponent.itemIndex + items.length;
itemComponent.itemIndex = itemIndex;
itemComponent.data = this.itemDataList[itemIndex];
itemComponent.dataChanged();
item.x = item.x + offset;
}
}
}
this.lastContentPosX = this.scrollView.content.x;
},
/** ok 更新垂直排列 */
_updateVertical () {
let items = this.itemList;
let item = null;
let bufferZone = this.halfScrollView;
let isUp = this.scrollView.content.y > this.lastContentPosY;
let offset = (this.itemHeight + this.spaceY) * items.length;
for (let i = 0; i < items.length; i++) {
item = items[i];
let viewPos = this._getPositionInView(item);
if (isUp) {
//item上滑时,超出了scrollView上边界,将item移动到下方复用,item移动到下方的位置必须不超过content的下边界
if (viewPos.y > bufferZone && item.y - offset - this.paddingBottom > -this.content.height) {
let itemComponent = item.getComponent(this.itemName);
let itemIndex = itemComponent.itemIndex + items.length;
itemComponent.itemIndex = itemIndex;
itemComponent.data = this.itemDataList[itemIndex];
itemComponent.dataChanged();
item.y = item.y - offset;
}
} else {
//item下滑时,超出了scrollView下边界,将item移动到上方复用,item移动到上方的位置必须不超过content的上边界
if (viewPos.y < -bufferZone && item.y + offset + this.paddingTop < 0) {
let itemComponent = item.getComponent(this.itemName);
let itemIndex = itemComponent.itemIndex - items.length;
itemComponent.itemIndex = itemIndex;
itemComponent.data = this.itemDataList[itemIndex];
itemComponent.dataChanged();
item.y = item.y + offset;
}
}
}
this.lastContentPosY = this.scrollView.content.y;
},
/** ok 更新网格水平排列 */
_updateGridHorizontal () {
let items = this.itemList;
let item = null;
let bufferZone = this.halfScrollView;
let isRight = this.scrollView.content.x > this.lastContentPosX;
let offset = (this.itemWidth + this.spaceX) * (this.spawnCount / this.gridRow);
for (let i = 0; i < items.length; i++) {
item = items[i];
let viewPos = this._getPositionInView(item);
if (isRight) {
//item右滑时,超出了scrollView右边界,将item移动到左方复用,item移动到左方的位置必须不超过content的左边界
if (viewPos.x > bufferZone && item.x - offset - this.paddingLeft > 0) {
let itemComponent = item.getComponent(this.itemName);
let itemIndex = itemComponent.itemIndex - (this.spawnCount / this.gridRow) * this.gridRow;
if (this.itemDataList[itemIndex] != null) {
item.x = item.x - offset;
itemComponent.itemIndex = itemIndex;
itemComponent.data = this.itemDataList[itemIndex];
itemComponent.dataChanged();
item.opacity = 255;
} else {
item.x = item.x - offset;
itemComponent.itemIndex = itemIndex;
item.opacity = 0;
}
}
} else {
//item左滑时,超出了scrollView左边界,将item移动到右方复用,item移动到右方的位置必须不超过content的右边界
if (viewPos.x < -bufferZone && item.x + offset + this.paddingRight < this.content.width) {
let itemComponent = item.getComponent(this.itemName);
let itemIndex = itemComponent.itemIndex + (this.spawnCount / this.gridRow) * this.gridRow;
if (this.itemDataList[itemIndex] != null) {
item.x = item.x + offset;
itemComponent.itemIndex = itemIndex;
itemComponent.data = this.itemDataList[itemIndex];
itemComponent.dataChanged();
item.opacity = 255;
} else {
item.x = item.x + offset;
itemComponent.itemIndex = itemIndex;
item.opacity = 0;
}
}
}
}
this.lastContentPosX = this.scrollView.content.x;
},
/** ok 更新网格垂直排列 */
_updateGridVertical () {
let items = this.itemList;
let item = null;
let bufferZone = this.halfScrollView;
let isUp = this.scrollView.content.y > this.lastContentPosY;
let offset = (this.itemHeight + this.spaceY) * (this.spawnCount / this.gridCol);
for (let i = 0; i < items.length; i++) {
item = items[i];
let viewPos = this._getPositionInView(item);
if (isUp) {
//item上滑时,超出了scrollView上边界,将item移动到下方复用,item移动到下方的位置必须不超过content的下边界
if (viewPos.y > bufferZone && item.y - offset - this.paddingBottom > -this.content.height) {
let itemComponent = item.getComponent(this.itemName);
let itemIndex = itemComponent.itemIndex + (this.spawnCount / this.gridCol) * this.gridCol;
if (this.itemDataList[itemIndex] != null) {
item.y = item.y - offset;
itemComponent.itemIndex = itemIndex;
itemComponent.data = this.itemDataList[itemIndex];
itemComponent.dataChanged();
item.opacity = 255;
} else {
item.y = item.y - offset;
itemComponent.itemIndex = itemIndex;
item.opacity = 0;
}
}
} else {
//item下滑时,超出了scrollView下边界,将item移动到上方复用,item移动到上方的位置必须不超过content的上边界
if (viewPos.y < -bufferZone && item.y + offset + this.paddingTop < 0) {
let itemComponent = item.getComponent(this.itemName);
let itemIndex = itemComponent.itemIndex - (this.spawnCount / this.gridCol) * this.gridCol;
if (this.itemDataList[itemIndex] != null) {
item.y = item.y + offset;
itemComponent.itemIndex = itemIndex;
itemComponent.data = this.itemDataList[itemIndex];
itemComponent.dataChanged();
item.opacity = 255;
} else {
item.y = item.y + offset;
itemComponent.itemIndex = itemIndex;
item.opacity = 0;
}
}
}
}
this.lastContentPosY = this.scrollView.content.y;
},
/** ============================= 属性面板 ============================= */
/**
* 列表排列方式改变
*/
onListTypeChange () {
cc.Class.Attr.setClassAttr(this, 'gridAxisType', 'visible', false);
cc.Class.Attr.setClassAttr(this, 'spaceX', 'visible', false);
cc.Class.Attr.setClassAttr(this, 'spaceY', 'visible', false);
cc.Class.Attr.setClassAttr(this, 'paddingTop', 'visible', false);
cc.Class.Attr.setClassAttr(this, 'paddingBottom', 'visible', false);
cc.Class.Attr.setClassAttr(this, 'paddingLeft', 'visible', false);
cc.Class.Attr.setClassAttr(this, 'paddingRight', 'visible', false);
if (this.listType == ListType.Grid) {
cc.Class.Attr.setClassAttr(this, 'gridAxisType', 'visible', true);
cc.Class.Attr.setClassAttr(this, 'spaceX', 'visible', true);
cc.Class.Attr.setClassAttr(this, 'spaceY', 'visible', true);
cc.Class.Attr.setClassAttr(this, 'paddingLeft', 'visible', true);
cc.Class.Attr.setClassAttr(this, 'paddingRight', 'visible', true);
cc.Class.Attr.setClassAttr(this, 'paddingTop', 'visible', true);
cc.Class.Attr.setClassAttr(this, 'paddingBottom', 'visible', true);
return;
}
if (this.listType == ListType.Horizontal) {
cc.Class.Attr.setClassAttr(this, 'spaceX', 'visible', true);
cc.Class.Attr.setClassAttr(this, 'paddingLeft', 'visible', true);
cc.Class.Attr.setClassAttr(this, 'paddingRight', 'visible', true);
return;
}
if (this.listType == ListType.Vertical) {
cc.Class.Attr.setClassAttr(this, 'spaceY', 'visible', true);
cc.Class.Attr.setClassAttr(this, 'paddingTop', 'visible', true);
cc.Class.Attr.setClassAttr(this, 'paddingBottom', 'visible', true);
return;
}
},
/** 开始滚动 */
onScrollBegin () {
this.startScroll = true;
},
/** 滚动结束 */
onScrollEnded () {
this.startScroll = false;
},
/** 列表滚动 */
onScrolling () {
this.bScrolling = true;
},
/** 列表滚动到顶部 */
onScrollToTop () {
if (this.scrollTopEvent.length > 0) {
this.scrollTopEvent[0].emit();
}
},
/** 列表滚动到底部 */
onScrollToBottom () {
if (this.scrollBottomEvent.length > 0) {
this.scrollBottomEvent[0].emit();
}
},
});
# 四. 使用方式
# 1. 控制面板
属性 | 功能 |
---|---|
listType | 排列方式:水平、垂直、网格三种方式 |
listItem | 列表项:node 节点 |
itemName | 列表项:脚本名称 |
gridAxisType | 网格布局方向:(水平、垂直) (lsitType = ListType.Grid 时会显示) |
spaceX | 列表项间的 X 轴间距 (lsitType = ListType.Horizontal 和 lsitType = ListType.Grid 时会显示) |
spaceY | 列表项间的 Y 轴间距 (lsitType = ListType.Vertical 和 lsitType = ListType.Grid 时会显示) |
paddingTop | 列表上间距 (lsitType = ListType.Vertical 和 lsitType = ListType.Grid 时会显示) |
paddingBottom | 列表下间距 (lsitType = ListType.Vertical 和 lsitType = ListType.Grid 时会显示) |
paddingLeft | 列表上间距 (lsitType = ListType.Horizontal 和 lsitType = ListType.Grid 时会显示) |
paddingRight | 列表上间距 (lsitType = ListType.Horizontal 和 lsitType = ListType.Grid 时会显示) |
scrollTopEvent | 列表: 滚动到 顶部/左部 时会触发该回调 |
scrollBottomEvent | 列表: 滚动到 底部/右部 时会触发该回调 |
# 2. 代码
# a. 导入自定义组件:
import ListView from "../public_util/ListView";
# b. 定义组件:
properties: {
titleL: cc.Label,
listView: ListView,
},
# c. 初始化列表:
this.listView.setData(this.list);
# d.Item代码:
cc.Class({
extends: cc.Component,
properties: {
indexL: cc.Label,
nameL: cc.Label,
// 数据
data: {
default: null,
visible: false,
},
// 索引
itemIndex: {
default: 0,
visible: false
}
},
/** 列表数据改变时触发 */
dataChanged () {
this.indexL.string = '第' + this.itemIndex + '位';
this.nameL.string = '名称: ' + this.data.name;
},
});
# e. 常用方法:
/** 在末尾添加一项 */
onClickAddOne () {
let data = {name: window.Utils.getRandomInt(0, 100)};
this.list.push(data);
this.listView.addItem(data);
},
/** 在末尾添加一组 */
onClickAddList () {
let list = [];
for (let i = 0; i < window.Utils.getRandomInt(2, 6); i++) {
let data = {name: window.Utils.getRandomInt(0, 100)};
list.push(data);
this.list.push(data);
}
this.listView.addItems(list);
},
/** 随机移除一项 */
onClickRemove () {
let index = window.Utils.getRandomInt(0, this.list.length);
if (this.list[index] != null) {
this.list.splice(index, 1);
}
this.listView.deleteItem(index);
},
/** 更新某项数据 */
onClickUpdate () {
let data = {name: window.Utils.getRandomInt(0, 100)};
this.list[7] = data;
this.listView.changeItem(data, 7);
},
/** 手动滚动到顶部 */
onClickScrollToTop () {
this.listView.scrollToTop();
},
/** 手动滚动到底部 */
onClickScrollToBottom () {
this.listView.scrollToBottom();
},
/** 手动滚动到某一项 */
onClickScrollToIndex () {
this.listView.scrollToIndex(8);
},
/** 回调: 滑到底部 */
onScrollBottom () {
// 做上拉加载逻辑
this.onClickAddList();
console.log('滑到底部了。。。。。。');
},
/** 回调: 滑到顶部 */
onScrollTop () {
console.log('滑到顶部了。。。。。。');
},