cocos creator + TypeScript 解决ScrollView加载大量Item导致卡顿的问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lxt610/article/details/90084836


1、引言

  我们在平常的开发中常常碰到列表类的数据处理!典型的像玩家列表这种可能数量非常庞大,可能有几百个!我们假设一次全部创建可能一下子就导致app安顿崩溃!下面我们带着问题一起分析。

2、问题分析

  点击这里查看上一篇Unity的相关介绍。这里就不再做详细分析了。

3、代码部分

/**
 * 滑动方向枚举
 */
export enum ScrollDirEnum {
    Vertical,
    Horizon
}

const {ccclass, property} = cc._decorator;

@ccclass
export default class UIScrollControl extends cc.Component {


    /** 滑动框体 - UI组件 */
    private scroll_rect:cc.ScrollView

    /** 可见区域大小 */
    private _show_area_size:cc.Size

    /** 滑动方向 */
    private dir:ScrollDirEnum

    /** 总共数量 */
    private total_count:number

    /**实际创建的item数量 */
    private total_show_item_count: number;

    /** 滑动子对象 - 列表 */
    private all_child_list:Array<cc.Node> = new Array<cc.Node>()

    /** item之间的间距 */
    private distance:number = 0

    /** content初始大小 */
    private start_content_size:cc.Size;

    /**标识 */
    private is_start:boolean = false

    private maxIdx:number = 0;

    /** 刷新回调 */
    private refreshCallBack:Function

    /**
     * 初始化控制
     * @param _temp_node 克隆对象
     * @param _total_count 成员总数
     * @param _size Item水平或竖直方向的距离
     * @param _dir 滑动方向
     * @param callBack 刷新回调
     */
    initControl(_temp_node:cc.Node,_total_count:number,_size:cc.Size,_dir:ScrollDirEnum,callBack:Function):void
    {

        if(callBack)
        {
            this.refreshCallBack = callBack
        }
        
        if (this.is_start == false)
        {
            this.scroll_rect = this.node.getComponent(cc.ScrollView);
            if (this.scroll_rect == null || this.scroll_rect.content == null)
            {
                cc.log("ScrollRect组件错误");
                return;
            }

            this.scroll_rect.content.parent.setAnchorPoint(cc.v2(0.5,0.5))
            let _mask_widget:cc.Widget = this.scroll_rect.content.parent.getComponent(cc.Widget)

            if(_mask_widget == null)
            {
                _mask_widget = this.scroll_rect.content.parent.addComponent(cc.Widget)
            }

            //上下左右对齐边界
            _mask_widget.isAlignLeft = true
            _mask_widget.left = 0

            _mask_widget.isAlignRight = true
            _mask_widget.right = 0

            _mask_widget.isAlignTop = true
            _mask_widget.top = 0

            _mask_widget.isAlignBottom = true
            _mask_widget.bottom = 0

            this._show_area_size = new cc.Size(this.scroll_rect.node.getContentSize())
            this.scroll_rect.content.setContentSize(this._show_area_size)
            this.start_content_size = this.scroll_rect.content.getContentSize();
        }

        if(_temp_node == null)
        {
            cc.log("_temp_node == null")
            return
        }

        this.scroll_rect.content.setContentSize(this.start_content_size)

        this.clear()

        this.total_count = _total_count
        this.dir = _dir as ScrollDirEnum

        if(_dir == ScrollDirEnum.Vertical)
        {
            this.scroll_rect.content.setAnchorPoint(cc.v2(0.5,1))

            this.scroll_rect.content.setContentSize(cc.size(this.start_content_size.width,this.total_count*_size.height))

            this.scroll_rect.content.setPosition(cc.v2(0,-this.total_count*_size.height/2))

            let _tempCount:number = Math.floor(this.start_content_size.height/_size.height)

            this.total_show_item_count = _tempCount + 2

            if(this.total_count <= this.total_show_item_count)
            {
                this.total_show_item_count = this.total_count
            }

            this.distance = _size.height
        }
        else if(_dir == ScrollDirEnum.Horizon)
        {
            this.scroll_rect.content.setAnchorPoint(cc.v2(0,0.5))
            this.scroll_rect.content.setContentSize(cc.size(this.total_count*_size.width,this.start_content_size.height))

            this.scroll_rect.content.setPosition(cc.v2(this.total_count*_size.width/2,0))
            let _tempCount:number = Math.floor(this.start_content_size.width/_size.width)

            this.total_show_item_count = _tempCount + 2

            if(this.total_count <= this.total_show_item_count)
            {
                this.total_show_item_count = this.total_count
            }

            this.distance = _size.width
        }

        let eventHandler = new cc.Component.EventHandler();
        eventHandler.target = this.node;
        eventHandler.component = "UIScrollControl";
        eventHandler.handler = "OnScroll";
        this.scroll_rect.scrollEvents.push(eventHandler); 

        this.is_start == true
        this.maxIdx = 0
    
        this.initShowAreaItems(_temp_node)
    }

    private clear()
    {
        this.scroll_rect.content.removeAllChildren()
        this.all_child_list = []
    }

    /**初始化可见的item */
    private initShowAreaItems(_temp_node:cc.Node)
    {

        for(let i = 0; i < this.total_show_item_count;i++)
        {
            //cc.log(" i = " + i)
            let curPos:cc.Vec2 = cc.v2(0,0)
            let node:cc.Node = cc.instantiate(_temp_node)
            this.scroll_rect.content.addChild(node)
            node.active = true
            node.opacity = 255
            
            if(this.dir == ScrollDirEnum.Vertical)
            {
                curPos.y = -this.distance/2 - this.distance * i
            }
            else if(this.dir == ScrollDirEnum.Horizon)
            {
                curPos.x = this.distance/2 + this.distance * i
            }

            node.name = `cell_${i}`
            node.setAnchorPoint(cc.v2(0.5,0.5))
            node.setPosition(curPos)

            this.onRefresh(node,i,i)

            this.all_child_list.push(node)
        }
        this.scroll_rect.scrollToTop()
    }

    /**滑动事件 */
    private OnScroll()
    {
        //获取滚动视图相对于左上角原点的当前滚动偏移
        let scrollOffset: cc.Vec2 = this.scroll_rect.getScrollOffset();
        let offset:number = 0;

        if(this.dir == ScrollDirEnum.Vertical)
        {
            offset = scrollOffset.y
        }
        else if(this.dir == ScrollDirEnum.Horizon)
        {
            //水平的offset是负数,为什么会有这么sb的设计,将它取反和垂直方向的统一一下
            offset = -scrollOffset.x
        }

        this.refreshLayout(offset)
    }

    /** 强行刷新布局 */
    private refreshLayout(_curOffset:number)
    {
        let offset:number = _curOffset

        //最大高度,超过该高度,不刷新
        let _max_rect_size:number = this.total_count * this.distance

        if (offset < 0 || offset + this._show_area_size.height >= _max_rect_size)
        {
            //cc.log("无法滚动, offset = " + offset + ", offset + this._show_area_size.height >= _max_rect_size >= _max_rect_size = " + (offset + this._show_area_size.height >= _max_rect_size))
            cc.log("无法滚动, offset = " + offset)
            return
        }

        let _index:number = 0 //从0开始
         let _min_index:number = Math.floor(offset / this.distance);

        //miniIdx到maxIdx都会刷新
        for (let i = 0; i < this.total_show_item_count; i++) 
        {
            let node:cc.Node = this.all_child_list[i];
            _index = _min_index + i;
            this.refreshItem(_index, i, node);
        }
        this.maxIdx = _min_index + this.total_show_item_count
    }

    /**
     * 
     * @param _index UI该刷新的第几个元素
     * @param _node_index 
     * @param node 
     */
    refreshItem(_index:number,_node_index:number,node:cc.Node)
    {
        if (_index < 0 || _index >= this.total_count)
        {
            cc.log("索引越界, _index = " + _index + ", this.total_count = " + this.total_count)
            return;
        }

        if (node == null) {
            cc.log("node == null");
            return;
        }

        let curPosition :cc.Vec2 = cc.Vec2.ZERO

        if (this.dir == ScrollDirEnum.Horizon)
        {
            curPosition.x = this.distance / 2 + this.distance * _index;
        }
        else if(this.dir == ScrollDirEnum.Vertical)
        {
            curPosition.y = - this.distance / 2 - this.distance * _index;
        }

        node.setPosition(curPosition)
        this.onRefresh(node, _index, _node_index);
    }

    /**
     * 
     * @param node 
     * @param _index 
     * @param nodeIndex 
     */
    private onRefresh(node:cc.Node,_index:number,nodeIndex:number)
    {
        //cc.log("--------------- _index = " + _index)
        if(this.refreshCallBack != null)
        {
            this.refreshCallBack(node,_index,nodeIndex)
        }
    }
}

  由于代码注释比较详尽,我在这里就不做说明了。

4、使用举例

4.1、场景搭建

在这里插入图片描述
  如上图所示,我们创建新场景,创建一个按钮和一个ScrollView控件,在制作一个Item用于克隆,然后把上面的UIScrollControl脚本挂载在ScrollView控件上。

4.2、测试

  创建一个Test.ts脚本,挂在Canvas上。代码如下:


import UIScrollControl from "./UIScrollControl";

const {ccclass, property} = cc._decorator;

@ccclass
export default class Test extends cc.Component {

    @property(cc.Node) itemNode: cc.Node = null;
    @property(UIScrollControl) scrollViewCtrl: UIScrollControl = null;

    strArr:Array<string> = ["夏","商","周朝","秦朝","西楚","汉朝","三国","两晋","五代", "十国", "隋", "唐","辽", "宋", "夏","金","元","明","清","民国"];

    onClick()
    {
        this.scrollViewCtrl.initControl(this.itemNode,this.strArr.length,cc.size(200,60),0,
	        (_node:cc.Node,_index:number,_nodeIndex:number)=>{
	            let label:cc.Label = _node.getComponentInChildren(cc.Label)
	            label.string = this.strArr[_index]
	        })
    }
}

指定好对象,点击按钮,一起来见证奇迹吧!

4.3、效果展示

在这里插入图片描述

4.4、Demo下载

  为了方便大家使用这里直接给出下载链接,点击这里即可下载完整工程。!

5、结束语


The End
  好了,今天的分享就到这里,如有不足之处,还望大家及时指正,随时欢迎探讨交流!!!


喜欢的朋友们,请帮顶、点赞、评论!您的肯定是我写作的不竭动力!

猜你喜欢

转载自blog.csdn.net/lxt610/article/details/90084836