没有艰辛,便无所获。
本组件依赖于ScrollView组件。请这样挂载:
滑动列表初始化:
import TableView from "../../../component/TableView";
/**
* 初始化滚动条目
* @author 神兽白泽
*/
const { ccclass, property } = cc._decorator;
@ccclass
export default class ListTest extends cc.Component {
@property({ type: cc.ScrollView, displayName: '滚动视图' })
private tableview: TableView = null;
start() {
this.initScollView();
}
/**
* 初始化滚动视图
*/
private initScollView() {
// 测试数据 - 理论无上限 只要内存装得下
let c_test_list = [];
for (let i = 0; i < 3000; i++) {
const id = i + 1;
let data: any = {};// 建议逻辑类里写类型
data.id = id;
data.reward_type = i % 3;
data.count = id;
data.login_day = id;
data.video_limit = 0;
c_test_list.push(data);
}
// 初始化滑动列表
this.tableview.node.emit('srollview-init', c_test_list)
// 数据列表的 一条下标
let index = 10;
// 滚动到这条下标的位置
this.tableview.contentMoveByIndex(index)
}
}
滑动列表条目:
const { ccclass, property } = cc._decorator;
/**
* 滑动列表条目类 数据分发
* @author 神兽白泽
*/
@ccclass
export default class ListTestItem extends cc.Component {
onLoad() {
}
/**
* list_data
* @param list_data 条目数据
*/
public async setItemData(list_data) {
cc.log("条目数据:", list_data);
}
}
组件脚本:TableView.ts
const { ccclass, property } = cc._decorator;
/** 动态加载的滚动列表
* - 滑动列表动态加载
* - 支持多种类型预制体混合滚动
*
* - item脚本需要有函数 setItemData 用于接收数据
*
* - 多个prefab混合时,要为每条item指定 pfbType
*
* - item、content锚点y需要为1其他为0.5
* @author 神兽白泽
*/
@ccclass
export default class TableView extends cc.Component {
@property({ displayName: '预制条目', tooltip: '脚本必须实现 setItemData 方法用于接收数据!', type: [cc.Prefab] })
private pre_item: cc.Prefab[] = [];
@property({ displayName: '脚本名', tooltip: '脚本必须实现 setItemData 方法用于接收数据!', type: [cc.String] })
private scr_item: string[] = [];
/** 间距 */
@property({ displayName: '间距' })
private itemInterval: number = 0;
/** 数据 */
private itemData: any = null;
/** 滚动列表组件 */
private scrollView: cc.ScrollView = null;
/** item预制体组 */
private item: Array<any> = null;
/** 滑动列表的内容节点 */
private content: cc.Node = null;
/** 列表高 */
private layerHeight: any = null;
/** ? */
private firstItemIndex: number = 0;
private lastItemIndex: number = 0;
private firstItemData: any = {};
private lastItemData: any = {};
private itemArr: Array<any> = [];
private itemNode: Array<any> = [];
private itemPosMap: any = new Map();
private initItemData: boolean = true;
private count: number = 0;
private itemCanMoveDown: boolean = null;
private itemCanMoveUp: boolean = null;
onLoad() {
this.initPro();
// 测试
// let itemData = [];
// for (let i = 0; i < 20; i++) {
// let test_data = {
// pfbType: 0,
// }
// itemData.push(test_data);
// }
// this.init(itemData);
}
private initPro() {
this.scrollView = this.node.getComponent(cc.ScrollView);
this.content = this.scrollView.content;
this.item = this.pre_item;
this.layerHeight = this.node.height;
for (let i = 0; i < this.item.length; i++) {
this.itemNode[i] = cc.instantiate(this.item[i]);
}
this.node.on('srollview-init', this.init, this);
this.node.on('srollview-update', this.updateList, this);
}
/**
* @param itemData 列表数据
* @param isSkipInit 跳过初始化
* @returns
*/
public init(itemData: Array<any>, isSkipInit: boolean = false) {
if (!itemData[0]) {
return false;
}
this.clearItem();
this.itemData = itemData; //item数据
this.firstItemIndex = 0;
this.lastItemIndex = 0;
this.firstItemData = {};
this.lastItemData = {};
this.itemArr = [];
this.itemPosMap = new Map();
this.initItemData = true;
this.count = 0;
if (isSkipInit) return;
// this.itemNode = [];
// for (let i = 0; i < this.item.length; i++) {
// this.itemNode[i] = cc.instantiate(this.item[i]);
// }
// cc.log(this.testTime(0));// 消耗计时
this.initItem();
this.initItemPos(0);
this.scrollView.node.on('scrolling', this.callback, this);
// cc.log('tableView结束:' + this.testTime());
}
/**
* 刷新列表
* - 仅支持使用1种预制体时,更新data数据
* @param itemData
*/
public updateList(itemData: Array<any>) {
this.itemData = itemData;
const old_count = this.itemArr.length;
for (let i = 0; i < old_count; i++) {
if (this.itemData[i]) {
if (!this.itemArr[i]) {
let y = 0;
if (i > 0) {
y = this.itemArr[i - 1].y - this.itemArr[i - 1].height - this.itemInterval;// 下一条目纵坐标
}
let item_node = this.addItemNode(i, y);
this.updateContentHeigh(this.itemArr[i].height - item_node.y);
}
let comp_script = this.itemArr[i].getComponent(this.scr_item[this.itemData[i].pfbType || 0]);
comp_script && comp_script.setItemData(this.itemData[i], this);
} else {
this.itemArr[i].destroy();
this.itemArr[i] = null;
}
}
}
private initItemPos(index: number) {
let item_data_count = this.itemData.length;
for (let i = index; i < item_data_count; i++) {
let obj: any = {}
let y;
if (i === 0) {
obj.startPos = 0;
} else {
obj.startPos = this.itemPosMap.get(i - 1).endPos;
}
let j = this.itemData[i].pfbType || 0;
obj.endPos = obj.startPos + this.itemNode[j].height + this.itemInterval;
this.itemPosMap.set(i, obj);
}
if (item_data_count > 0) {
this.updateContentHeigh(this.itemPosMap.get(item_data_count - 1).endPos);
}
}
/**
* 实例化所有用到的item,控制实例化item的数目,暂定超出两个
*/
private initItem() {
let j = 0;
for (let i = 0; i < this.itemData.length; i++) {
if (this.content.height > this.layerHeight) {
j++
if (j > 2) {
break;
}
}
let y = 0;
if (i > 0) {
y = this.itemArr[i - 1].y - this.itemArr[i - 1].height - this.itemInterval;// 下一条目纵坐标
}
let item_node = this.addItemNode(i, y);
this.updateContentHeigh(this.itemArr[i].height - item_node.y);
}
}
/**
* 添加条目节点
* @param i 编号
* @param y 坐标y
*/
private addItemNode(i: number, y: number) {
let pfbType = this.itemData[i].pfbType || 0;
let item = cc.instantiate(this.itemNode[pfbType]);
item.parent = this.content;
item.pfbType = pfbType;
item.index = i;
if (i === 0) {
item.y = 0;
} else {
item.y = y;
}
item.x = 0;
//对item赋值
let comp_script = item.getComponent(this.scr_item[pfbType]);
comp_script && comp_script.setItemData(this.itemData[i], this);
this.itemArr.push(item);
return item;
// cc.log('生成itemNode' + i);
}
/**
* 更新centent高
* @param num
*/
private updateContentHeigh(num: number) {
this.content.height = num > this.layerHeight ? num : this.layerHeight;
// cc.log('滚动条高度:', this.content.height);
}
/**
* 触摸滚动条的函数回调
* @param event
* @param eventType
* @returns
*/
private callback(event, eventType) {
// cc.log(event && event.type || eventType)
if (this.content.height > this.layerHeight) {
let firstItemPos = this.scrollView.getScrollOffset().y;
let lastItemPos = firstItemPos + this.layerHeight;
if (firstItemPos < 0) return;
if (this.initItemData) {
// cc.log('111:%o', this.itemPosMap)
this.initItemData = false;
this.updateFirstItemIndex();
this.itemCanMoveDown = true;
this.updateLastItemIndex();
this.itemCanMoveDown = false;
}
//超出边界直接返回.
//滚动条向上滑动可能会触发的函数
if (firstItemPos > this.firstItemData.endPos) {
if (this.lastItemIndex + 1 < this.itemData.length) {
this.updateFirstItemIndex();
}
this.count++;
}
if (lastItemPos > this.lastItemData.endPos) {
if (this.lastItemIndex + 1 < this.itemData.length) {
this.itemCanMoveDown = true;
this.updateLastItemIndex();
this.itemCanMoveDown = false;
}
}
//滚动条向下滑动可能会触发的函数
if (lastItemPos < this.lastItemData.startPos) {
this.updateLastItemIndex();
this.count--;
}
if (firstItemPos < this.firstItemData.startPos) {
this.itemCanMoveUp = true;
this.updateFirstItemIndex();
this.itemCanMoveUp = false;
}
}
}
private updateFirstItemIndex() {
let num = this.firstItemIndex;
if (this.itemCanMoveUp && num > this.getItemIndex()[0] && num > 0) {
this.itemMoveUp(this.firstItemIndex - 1);
}
this.updateItemIndex();
}
private updateLastItemIndex() {
let num = this.lastItemIndex;
if (this.itemCanMoveDown && num < this.getItemIndex()[1] && num + 1 < this.itemData.length) {
this.itemMoveDown(this.lastItemIndex + 1);
}
this.updateItemIndex();
}
private updateItemIndex() {
//cc.log(this.firstItemIndex, this.lastItemIndex, this.itemArr, this.itemData)
}
/**
* 得到滚动条此时状态下应有的itemNode元素,包括滚动条上方一个,滚动条下方一个
* @returns
*/
private getItemIndex() {
let firstItemPos = this.scrollView.getScrollOffset().y;
let lastItemPos = firstItemPos + this.layerHeight;
let arr = [];
this.itemPosMap.forEach((value, key) => {
let status1 = value.startPos <= firstItemPos && value.endPos > firstItemPos;
let status2 = value.startPos >= firstItemPos && value.endPos < lastItemPos;
let status3 = value.startPos <= lastItemPos && value.endPos > lastItemPos;
if (status1) {
this.firstItemData.startPos = value.startPos;
this.firstItemData.endPos = value.endPos;
this.firstItemIndex = key;
arr.push(key);
}
if (status3) {
this.lastItemData.startPos = value.startPos;
this.lastItemData.endPos = value.endPos;
this.lastItemIndex = key;
arr.push(key);
}
})
return arr;
}
/**
* 滚动到顶部【滚动条顺序是从上到下开始遍历】
* @param num
* @returns
*/
private itemMoveUp(num: number) {
if (num < 0 || this.lastItemIndex + 1 < num || num + 1 > this.itemData.length) {
return;
}
if (!this.hasItem(num)) {
this.itemMove(num, -this.itemPosMap.get(num).startPos);
}
num++;
return this.itemMoveUp(num);
}
/** 滚动到底部 */
public itemMoveDown(num: number) {
if (num < 0 || this.firstItemIndex - 1 > num || num + 1 > this.itemData.length) {
return;
}
if (!this.hasItem(num)) {
this.itemMove(num, -this.itemPosMap.get(num).startPos);
}
num--;
return this.itemMoveDown(num);
}
/**
* 判断指定index位置是否存在itemNode
* @param index 节点下标
* @returns
*/
private hasItem(index: number) {
for (let i = 0; i < this.itemArr.length; i++) {
if (this.itemArr[i].index === index) {
return true;
}
}
return false;
}
/**
* 移动条目
* 逻辑判断,第一种情况,修改itemArr数组的某个对象,第二种情况实例化一个新itemNode
* @param index 条目节点标记
* @param y
* @returns
*/
private itemMove(index: number, y: number) {
for (let i = 0; i < this.itemArr.length; i++) {
//index存在-1的情况,类似于在缓存池里的item.
let status1 = this.itemArr[i].index < this.firstItemIndex - 1 ? true : false;
let status2 = this.itemArr[i].index > this.lastItemIndex + 1 ? true : false;
let status3 = this.itemArr[i].pfbType === (this.itemData[index].pfbType || 0);
//cc.log('item的索引', this.firstItemIndex, this.lastItemIndex)
if (status1 && status3 || status2 && status3) {
//cc.log(i, index, this.itemArr, this.content.height);
//给item赋值还有设置位置
this.itemArr[i].index = index;
this.itemArr[i].y = y;
let comp_script = this.itemArr[i].getComponent(this.scr_item[this.itemArr[i].pfbType]);
comp_script && comp_script.setItemData(this.itemData[index], this);
return;
}
}
this.addItemNode(index, y);
}
/**
* 内容滚动到指定下标位置
* @param index 条目index
*/
public contentMoveByIndex(index: number) {
let pos_y = this.itemPosMap.get(index).startPos;
this.scrollView.scrollToOffset(new cc.Vec2(this.content.x, pos_y), 0.5);
}
/**
* 得到相关位置的排序index
* 方法待验证,可能有问题
* @param pos 坐标
* @returns
*/
public getPosIndex(pos: cc.Vec2) {
for (let [key, value] of this.itemPosMap.entries()) {
if (value.endPos > pos && value.startPos <= pos) {
return key;
}
}
}
/**
* 添加条目
* @param obj 数据对象
* @returns
*/
public addItem(obj: any) {
this.itemData.push(obj);
this.initItemPos(this.itemData.length - 1);
let endPos = this.itemPosMap.get(this.itemData.length - 1).endPos;
if (endPos - this.layerHeight > 0) {
let startPos = endPos - this.layerHeight;
//得到当前的firstItemIndex;
for (let i = this.itemData.length - 1; i >= 0; i--) {
if (this.itemPosMap.get(i).endPos > startPos && this.itemPosMap.get(i).startPos <= startPos) {
this.firstItemIndex = i;
}
}
this.scrollView.scrollToBottom();
this.lastItemIndex = this.itemData.length - 1;
let num = this.firstItemIndex - 1 > 0 ? (this.firstItemIndex - 1) : 0;
this.itemMoveUp(num);
return true;
} else {
this.firstItemIndex = 0;
this.lastItemIndex = this.itemData.length - 1;
this.itemMoveUp(this.firstItemIndex);
return false;
}
}
/**
* 清理条目
*/
public clearItem() {
this.itemData = [];
this.itemPosMap.clear();
this.scrollView.scrollToTop();
this.content.height = 0;
for (let i in this.itemArr) {
// this.itemArr[i].index = -1;
// this.itemArr[i].y = 3000;
this.itemArr[i].destroy();
}
}
/**
* 删除指定条目
* @param i 节点.index
* @returns
*/
public deleteItem(i: number) {
this.itemData.splice(i, 1);
const item_data_count = this.itemData.length;
if (item_data_count <= 0) return;
this.initItemPos(item_data_count - 1);
//改变this.itemArr的内容
for (let j = 0; j < this.itemArr.length; j++) {
if (this.itemArr[j].index === i) {
this.itemArr[j].index = -1;
this.itemArr[j].y = 3000;
}
if (this.itemArr[j].index > i) {
let num = this.itemArr[j].index;
this.itemArr[j].y = -this.itemPosMap.get(num - 1).startPos;
this.itemArr[j].index = num - 1;
}
}
this.updateContentHeigh(this.itemPosMap.get(this.itemData.length - 1).endPos);
if (!this.lastItemIndex) this.getItemIndex();// 初始化时没有初始化此值,会导致为0.不能创建新的item,此处校验
this.itemMoveUp(this.firstItemIndex);//
}
/**
* 重置指定item
* @param index 节点下标
* @param item_data 新的数据
*/
public resetItemData(index: number, item_data?: any) {
for (let i = 0; i < this.itemArr.length; i++) {
if (this.itemArr[i].index === index) {
if (item_data) this.itemData[i] = item_data;
let comp_script = this.itemArr[i].getComponent(this.scr_item[this.itemArr[i].pfbType]);
comp_script && comp_script.setItemData(this.itemData[index], this);
break;
}
}
}
private lastResetItemInfoHeight: any = null;
private lastResetItemIndex: any = null;
/**
* 重置条目大小
* @param index 节点下标
* @param infoHeight 新的高度
*/
public resetItemSize(index: number, infoHeight: number) {
let func = (function (index, infoHeight) {
for (let i = 0; i < this.itemArr.length; i++) {
if (this.itemArr[i].index > index) {
this.itemArr[i].y -= infoHeight;
}
}
for (let [key, value] of this.itemPosMap.entries()) {
if (key === index) {
value.endPos += infoHeight;
}
if (key > index) {
value.endPos += infoHeight;
value.startPos += infoHeight;
}
}
this.lastResetItemInfoHeight = infoHeight;
this.lastResetItemIndex = index;
}).bind(this);
if (this.lastResetItemIndex !== null && this.lastResetItemInfoHeight) {
if (this.lastResetItemIndex === index) {
func(this.lastResetItemIndex, -this.lastResetItemInfoHeight);
this.lastResetItemIndex = null;
this.lastResetItemInfoHeight = 0;
} else {
func(this.lastResetItemIndex, -this.lastResetItemInfoHeight);
func(index, infoHeight);
}
} else {
func(index, infoHeight);
}
this.itemMoveUp(this.firstItemIndex);
this.updateContentHeigh(this.itemPosMap.get(this.itemData.length - 1).endPos);
}
}
整理不易,关注收藏不迷路。
目录:CocosCreator经典笔记_神兽白泽-CSDN博客
笔者qq、微信:1302109196
qq群:415468592