CocosCreator之动态加载滑动列表

没有艰辛,便无所获。

本组件依赖于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

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值