cocosCreator2.x滑动组件全面版一体化(虚拟列表+分页列表+普通列表改良版)

/******************************************
 * @author kL <klk0@qq.com>
 * @date 2019/6/6
 * @doc 列表组件.
 * @end
 ******************************************/
/* eslint-disable */
const { ccclass, property, disallowMultiple, menu, executionOrder, requireComponent } =
    cc._decorator;

import ListItem from './ListItem';

enum TemplateType {
    NODE = 1,
    PREFAB = 2,
}

enum SlideType {
    NORMAL = 1, //普通
    ADHERING = 2, //粘附模式,将强制关闭滚动惯性
    PAGE = 3, //页面模式,将强制关闭滚动惯性
}

enum SelectedType {
    NONE = 0,
    SINGLE = 1, //单选
    MULT = 2, //多选
}

@ccclass
@disallowMultiple()
@menu('自定义组件/List')
@requireComponent(cc.ScrollView)
//脚本生命周期回调的执行优先级。小于 0 的脚本将优先执行,大于 0 的脚本将最后执行。该优先级只对 onLoad, onEnable, start, update 和 lateUpdate 有效,对 onDisable 和 onDestroy 无效。
@executionOrder(-5000)
export default class List extends cc.Component {
    //模板类型
    @property({ type: cc.Enum(TemplateType), tooltip: CC_DEV && '模板类型' })
    private templateType: TemplateType = TemplateType.NODE;
    //模板Item(Node)
    @property({
        type: cc.Node,
        tooltip: CC_DEV && '模板Item',
        visible() {
            return this.templateType == TemplateType.NODE;
        },
    })
    tmpNode: cc.Node = null;
    //模板Item(Prefab)
    @property({
        type: cc.Prefab,
        tooltip: CC_DEV && '模板Item',
        visible() {
            return this.templateType == TemplateType.PREFAB;
        },
    })
    tmpPrefab: cc.Prefab = null;
    //滑动模式
    @property()
    private _slideMode: SlideType = SlideType.NORMAL;
    @property({
        type: cc.Enum(SlideType),
        tooltip: CC_DEV && '滑动模式',
    })
    set slideMode(val: SlideType) {
        this._slideMode = val;
    }
    get slideMode() {
        return this._slideMode;
    }
    //翻页作用距离
    @property({
        type: cc.Float,
        range: [0, 1, 0.1],
        tooltip: CC_DEV && '翻页作用距离',
        slide: true,
        visible() {
            return this._slideMode == SlideType.PAGE;
        },
    })
    public pageDistance = 0.3;
    //页面改变事件
    @property({
        type: cc.Component.EventHandler,
        tooltip: CC_DEV && '页面改变事件',
        visible() {
            return this._slideMode == SlideType.PAGE;
        },
    })
    private pageChangeEvent: cc.Component.EventHandler = new cc.Component.EventHandler();
    //是否为虚拟列表(动态列表)
    @property()
    private _virtual = true;
    @property({
        type: cc.Boolean,
        tooltip: CC_DEV && '是否为虚拟列表(动态列表)',
    })
    set virtual(val: boolean) {
        if (val != null) this._virtual = val;
        if (!CC_DEV && this._numItems != 0) {
            this._onScrolling();
        }
    }
    get virtual() {
        return this._virtual;
    }
    //是否为循环列表
    @property({
        tooltip: CC_DEV && '是否为循环列表',
        visible() {
            const val: boolean = /*this.virtual &&*/ this.slideMode == SlideType.NORMAL;
            if (!val) this.cyclic = false;
            return val;
        },
    })
    public cyclic = false;
    //缺省居中
    @property({
        tooltip: CC_DEV && 'Item数量不足以填满Content时,是否居中显示Item(不支持Grid布局)',
        visible() {
            return this.virtual;
        },
    })
    public lackCenter = false;
    //缺省可滑动
    @property({
        tooltip: CC_DEV && 'Item数量不足以填满Content时,是否可滑动',
        visible() {
            const val: boolean = this.virtual && !this.lackCenter;
            if (!val) this.lackSlide = false;
            return val;
        },
    })
    public lackSlide = false;
    //刷新频率
    @property({ type: cc.Integer })
    private _updateRate = 0;
    @property({
        type: cc.Integer,
        range: [0, 6, 1],
        tooltip: CC_DEV && '刷新频率(值越大刷新频率越低、性能越高)',
        slide: true,
    })
    set updateRate(val: number) {
        if (val >= 0 && val <= 6) {
            this._updateRate = val;
        }
    }
    get updateRate() {
        return this._updateRate;
    }
    //分帧渲染(每帧渲染的Item数量(<=0时关闭分帧渲染))
    @property({
        type: cc.Integer,
        range: [0, 12, 1],
        tooltip: CC_DEV && '逐帧渲染时,每帧渲染的Item数量(<=0时关闭分帧渲染)',
        slide: true,
    })
    public frameByFrameRenderNum = 0;
    //渲染事件(渲染器)
    @property({
        type: cc.Component.EventHandler,
        tooltip: CC_DEV && '渲染事件(渲染器)',
    })
    private renderEvent: cc.Component.EventHandler = new cc.Component.EventHandler();

    //选择模式
    @property({
        type: cc.Enum(SelectedType),
        tooltip: CC_DEV && '选择模式',
    })
    public selectedMode: SelectedType = SelectedType.NONE;
    @property({
        tooltip: CC_DEV && '是否重复响应单选事件',
        visible() {
            return this.selectedMode == SelectedType.SINGLE;
        },
    })
    public repeatEventSingle = false;
    //触发选择事件
    @property({
        type: cc.Component.EventHandler,
        tooltip: CC_DEV && '触发选择事件或点击事件',
    })
    private selectedEvent: cc.Component.EventHandler = new cc.Component.EventHandler();

    //触发选择事件
    @property({
        type: cc.Component.EventHandler,
        tooltip: CC_DEV && '初始化择事件',
    })
    private initEvent: cc.Component.EventHandler = new cc.Component.EventHandler();

    @property({
        type: cc.Component.EventHandler,
        tooltip: CC_DEV && '回收入池事件',
    })
    private recoveryEvent: cc.Component.EventHandler = new cc.Component.EventHandler();

    @property({
        tooltip: CC_DEV && '列表是否有点击事件',
    })
    public hasListClickEvent: boolean = false;

    @property({
        type: cc.Component.EventHandler,
        tooltip: CC_DEV && '点击列表事件',
        visible() {
            return this.hasListClickEvent;
        },
    })
    private listClickEvent: cc.Component.EventHandler = new cc.Component.EventHandler();

    @property({
        type: cc.Component.EventHandler,
        tooltip: CC_DEV && '点击列表事件',
    })
    private refreshItemEvent: cc.Component.EventHandler = new cc.Component.EventHandler();

    @property({
        //    type: cc.Boolean,
        tooltip: CC_DEV && '初始化时是否删除所有子节点',
    })
    public clearChilds: boolean = true;
    //当前选择id
    private _selectedId = -1; //
    private _lastSelectedId: number;
    private multSelected: number[];

    private _clickFlag: boolean = false;

    set selectedId(val: number) {
        const t: any = this;
        const item = t.getItemByListId(val);
        switch (t.selectedMode) {
            case SelectedType.SINGLE: {
                if (!t.repeatEventSingle && val == t._selectedId) return;
                // item = t.getItemByListId(val);
                // if (!item && val >= 0)
                //     return;
                let listItem: ListItem;
                if (t._selectedId >= 0) t._lastSelectedId = t._selectedId;
                //如果<0则取消选择,把_lastSelectedId也置空吧,如果以后有特殊需求再改吧。
                else t._lastSelectedId = null;
                t._selectedId = val;
                if (item) {
                    listItem = item.getComponent(ListItem);
                    listItem.selected = true;
                    if (t.selectedEvent && listItem) {
                        cc.Component.EventHandler.emitEvents(
                            [t.selectedEvent],
                            item,
                            val % this._actualNumItems,
                            t._lastSelectedId == null
                                ? null
                                : t._lastSelectedId % this._actualNumItems
                        );
                    }
                }
                if (t._lastSelectedId >= 0 && t._lastSelectedId != t._selectedId) {
                    const lastItem: any = t.getItemByListId(t._lastSelectedId);
                    if (lastItem) {
                        let cmp = lastItem.getComponent(ListItem);
                        cmp ? (cmp.selected = false) : null;
                    }
                }
                break;
            }
            case SelectedType.MULT: {
                // item = t.getItemByListId(val);
                if (!item) return;
                const listItem = item.getComponent(ListItem);
                if (t._selectedId >= 0) t._lastSelectedId = t._selectedId;
                t._selectedId = val;
                const bool = !listItem.selected;
                listItem.selected = bool;
                const sub: number = t.multSelected.indexOf(val);
                if (bool && sub < 0) {
                    t.multSelected.push(val);
                } else if (!bool && sub >= 0) {
                    t.multSelected.splice(sub, 1);
                }
                if (t.selectedEvent) {
                    cc.Component.EventHandler.emitEvents(
                        [t.selectedEvent],
                        item,
                        val % this._actualNumItems,
                        t._lastSelectedId == null ? null : t._lastSelectedId % this._actualNumItems,
                        bool
                    );
                }
                break;
            }
            default: {
                if (t.selectedEvent && item) {
                    cc.Component.EventHandler.emitEvents(
                        [t.selectedEvent],
                        item,
                        val % this._actualNumItems,
                        t._lastSelectedId == null ? null : t._lastSelectedId % this._actualNumItems
                    );
                }
            }
        }
    }
    get selectedId() {
        return this._selectedId;
    } //
    private _forceUpdate = false;
    private _align: number;
    private _horizontalDir: number;
    private _verticalDir: number;
    private _startAxis: number;
    private _alignCalcType: number;
    public content: cc.Node;
    private firstListId: number;
    public displayItemNum: number;
    private _updateDone = true;
    private _updateCounter: number;
    public _actualNumItems: number;
    private _cyclicNum: number;
    private _cyclicPos1: number;
    private _cyclicPos2: number;
    //列表数量
    @property({
        serializable: false,
    })
    private _numItems = 0;
    set numItems(val: number) {
        const t = this;
        if (!t.checkInited(false)) return;
        if (val == null || val < 0) {
            cc.error('numItems set the wrong::', val);
            return;
        }
        t._actualNumItems = t._numItems = val;
        t._forceUpdate = true;

        if (t._virtual) {
            t._resizeContent();
            if (t.cyclic) {
                t._numItems = t._cyclicNum * t._numItems;
            }
            t._onScrolling();
            if (!t.frameByFrameRenderNum && t.slideMode == SlideType.PAGE)
                t.curPageNum = t.nearestListId;
        } else {
            if (t.cyclic) {
                t._resizeContent();
                t._numItems = t._cyclicNum * t._numItems;
            }
            const layout: cc.Layout = t.content.getComponent(cc.Layout);
            if (layout) {
                layout.enabled = true;
            }
            t._delRedundantItem();

            t.firstListId = 0;
            if (t.frameByFrameRenderNum > 0) {
                //先渲染几个出来
                const len: number =
                    t.frameByFrameRenderNum > t._numItems ? t._numItems : t.frameByFrameRenderNum;
                for (let n = 0; n < len; n++) {
                    t._createOrUpdateItem2(n);
                }
                if (t.frameByFrameRenderNum < t._numItems) {
                    t._updateCounter = t.frameByFrameRenderNum;
                    t._updateDone = false;
                }
            } else {
                for (let n = 0; n < t._numItems; n++) {
                    t._createOrUpdateItem2(n);
                }
                t.displayItemNum = t._numItems;
            }
        }
    }
    get numItems() {
        return this._actualNumItems;
    }

    private _inited = false;
    private _scrollView: cc.ScrollView;
    get scrollView() {
        return this._scrollView;
    }
    private _layout: cc.Layout;
    private _resizeMode: cc.Layout.ResizeMode;
    private _topGap: number;
    private _rightGap: number;
    private _bottomGap: number;
    private _leftGap: number;

    private _columnGap: number;
    private _lineGap: number;
    private _colLineNum: number;

    private _lastDisplayData: number[];
    public displayData: any[];
    private _pool: cc.NodePool;

    private _itemTmp: any;
    private _needUpdateWidget = false;
    private _itemSize: cc.Size;
    private _sizeType: boolean;

    public _customSize: any;

    private frameCount: number;
    private _aniDelRuning = false;
    private _aniDelCB: Function;
    private _aniDelItem: any;
    private _aniDelBeforePos: cc.Vec2;
    private _aniDelBeforeScale: number;
    private viewTop: number;
    private viewRight: number;
    private viewBottom: number;
    private viewLeft: number;

    private _doneAfterUpdate = false;

    private elasticTop: number;
    private elasticRight: number;
    private elasticBottom: number;
    private elasticLeft: number;

    private scrollToListId: number;

    private adhering = false;

    private _adheringBarrier = false;
    private nearestListId: number;

    public curPageNum = 0;
    private _beganPos: number;
    private _scrollPos: number;
    private curScrollIsTouch: boolean; //当前滑动是否为手动

    private _scrollToListId: number;
    private _scrollToEndTime: number;
    private _scrollToSo: any;

    private _lack: boolean;
    private _allItemSize: number;
    private _allItemSizeNoEdge: number;

    private _scrollItem: any; //当前控制 ScrollView 滚动的 Item

    //----------------------------------------------------------------------------

    onLoad() {
        this._init();
    }

    onDestroy() {
        const t: any = this;
        if (cc.isValid(t._itemTmp)) t._itemTmp.destroy();
        if (cc.isValid(t.tmpNode)) t.tmpNode.destroy();
        t._pool && t._pool.clear();
    }

    onEnable() {
        // if (!CC_EDITOR)
        this._registerEvent();
        this._init();
        // 处理重新显示后,有可能上一次的动画移除还未播放完毕,导致动画卡住的问题
        if (this._aniDelRuning) {
            this._aniDelRuning = false;
            if (this._aniDelItem) {
                if (this._aniDelBeforePos) {
                    this._aniDelItem.position = this._aniDelBeforePos;
                    delete this._aniDelBeforePos;
                }
                if (this._aniDelBeforeScale) {
                    this._aniDelItem.scale = this._aniDelBeforeScale;
                    delete this._aniDelBeforeScale;
                }
                delete this._aniDelItem;
            }
            if (this._aniDelCB) {
                this._aniDelCB();
                delete this._aniDelCB;
            }
        }
    }

    onDisable() {
        // if (!CC_EDITOR)
        this._unregisterEvent();
    }
    //注册事件
    _registerEvent() {
        const t: any = this;
        t.node.on(cc.Node.EventType.TOUCH_START, t._onTouchStart, t, true);
        t.node.on('touch-up', t._onTouchUp, t);
        t.node.on(cc.Node.EventType.TOUCH_CANCEL, t._onTouchCancelled, t, true);
        t.node.on('scroll-began', t._onScrollBegan, t, true);
        t.node.on('scroll-ended', t._onScrollEnded, t, true);
        t.node.on('scrolling', t._onScrolling, t, true);
        t.node.on(cc.Node.EventType.SIZE_CHANGED, t._onSizeChanged, t);
    }
    //卸载事件
    _unregisterEvent() {
        const t: any = this;
        t.node.off(cc.Node.EventType.TOUCH_START, t._onTouchStart, t, true);
        t.node.off('touch-up', t._onTouchUp, t);
        t.node.off(cc.Node.EventType.TOUCH_CANCEL, t._onTouchCancelled, t, true);
        t.node.off('scroll-began', t._onScrollBegan, t, true);
        t.node.off('scroll-ended', t._onScrollEnded, t, true);
        t.node.off('scrolling', t._onScrolling, t, true);
        t.node.off(cc.Node.EventType.SIZE_CHANGED, t._onSizeChanged, t);
    }
    //初始化各种..
    _init() {
        const t: any = this;
        if (t._inited) return;

        t._scrollView = t.node.getComponent(cc.ScrollView);

        t.content = t._scrollView.content;
        if (!t.content) {
            cc.error(t.node.name + "'s cc.ScrollView unset content!");
            return;
        }

        t._layout = t.content.getComponent(cc.Layout);

        t._align = t._layout.type; //排列模式
        t._resizeMode = t._layout.resizeMode; //自适应模式
        t._startAxis = t._layout.startAxis;

        t._topGap = t._layout.paddingTop; //顶边距
        t._rightGap = t._layout.paddingRight; //右边距
        t._bottomGap = t._layout.paddingBottom; //底边距
        t._leftGap = t._layout.paddingLeft; //左边距

        t._columnGap = t._layout.spacingX; //列距
        t._lineGap = t._layout.spacingY; //行距

        t._colLineNum; //列数或行数(非GRID模式则=1,表示单列或单行);

        t._verticalDir = t._layout.verticalDirection; //垂直排列子节点的方向
        t._horizontalDir = t._layout.horizontalDirection; //水平排列子节点的方向

        t.setTemplateItem(
            cc.instantiate(t.templateType == TemplateType.PREFAB ? t.tmpPrefab : t.tmpNode)
        );

        // 特定的滑动模式处理
        if (t._slideMode == SlideType.ADHERING || t._slideMode == SlideType.PAGE) {
            t._scrollView.inertia = false;
            t._scrollView._onMouseWheel = function () {
                return;
            };
        }
        if (!t.virtual)
            // lackCenter 仅支持 Virtual 模式
            t.lackCenter = false;

        t._lastDisplayData = []; //最后一次刷新的数据
        t.displayData = []; //当前数据
        t._pool = new cc.NodePool(); //这是个池子..
        t._forceUpdate = false; //是否强制更新
        t._updateCounter = 0; //当前分帧渲染帧数
        t._updateDone = true; //分帧渲染是否完成

        t.curPageNum = 0; //当前页数

        if (t.cyclic || 0) {
            t._scrollView._processAutoScrolling = this._processAutoScrolling.bind(t);
            t._scrollView._startBounceBackIfNeeded = function () {
                return false;
            };
            // t._scrollView._scrollChildren = function () {
            //     return false;
            // }
        }

        switch (t._align) {
            case cc.Layout.Type.HORIZONTAL: {
                switch (t._horizontalDir) {
                    case cc.Layout.HorizontalDirection.LEFT_TO_RIGHT:
                        t._alignCalcType = 1;
                        break;
                    case cc.Layout.HorizontalDirection.RIGHT_TO_LEFT:
                        t._alignCalcType = 2;
                        break;
                }
                break;
            }
            case cc.Layout.Type.VERTICAL: {
                switch (t._verticalDir) {
                    case cc.Layout.VerticalDirection.TOP_TO_BOTTOM:
                        t._alignCalcType = 3;
                        break;
                    case cc.Layout.VerticalDirection.BOTTOM_TO_TOP:
                        t._alignCalcType = 4;
                        break;
                }
                break;
            }
            case cc.Layout.Type.GRID: {
                switch (t._startAxis) {
                    case cc.Layout.AxisDirection.HORIZONTAL:
                        switch (t._verticalDir) {
                            case cc.Layout.VerticalDirection.TOP_TO_BOTTOM:
                                t._alignCalcType = 3;
                                break;
                            case cc.Layout.VerticalDirection.BOTTOM_TO_TOP:
                                t._alignCalcType = 4;
                                break;
                        }
                        break;
                    case cc.Layout.AxisDirection.VERTICAL:
                        switch (t._horizontalDir) {
                            case cc.Layout.HorizontalDirection.LEFT_TO_RIGHT:
                                t._alignCalcType = 1;
                                break;
                            case cc.Layout.HorizontalDirection.RIGHT_TO_LEFT:
                                t._alignCalcType = 2;
                                break;
                        }
                        break;
                }
                break;
            }
        }
        // 清空 content
        // t.content.children.forEach((child: cc.Node) => {
        //     child.removeFromParent();
        //     if (child != t.tmpNode && child.isValid)
        //         child.destroy();
        // });
        if (t.clearChilds) {
            t.content.removeAllChildren();
        } else {
            if (cc.isValid(t.tmpNode)) t.tmpNode.destroy();
        }
        t._inited = true;
    }
    /**
     * 为了实现循环列表,必须覆写cc.ScrollView的某些函数
     * @param {Number} dt
     */
    _processAutoScrolling(dt: number) {
        const brakingFactor = 1;
        this._scrollView['_autoScrollAccumulatedTime'] += dt * (1 / brakingFactor);

        let percentage: number = Math.min(
            1,
            this._scrollView['_autoScrollAccumulatedTime'] /
                this._scrollView['_autoScrollTotalTime']
        );
        if (this._scrollView['_autoScrollAttenuate']) {
            const time: number = percentage - 1;
            percentage = time * time * time * time * time + 1;
        }

        const newPosition: any = this._scrollView['_autoScrollStartPosition'].add(
            this._scrollView['_autoScrollTargetDelta'].mul(percentage)
        );
        const EPSILON: number = this._scrollView['getScrollEndedEventTiming']();
        const reachedEnd: boolean = Math.abs(percentage - 1) <= EPSILON;

        const fireEvent: boolean =
            Math.abs(percentage - 1) <= this._scrollView['getScrollEndedEventTiming']();
        if (fireEvent && !this._scrollView['_isScrollEndedWithThresholdEventFired']) {
            this._scrollView['_dispatchEvent']('scroll-ended-with-threshold');
            this._scrollView['_isScrollEndedWithThresholdEventFired'] = true;
        }

        if (reachedEnd) {
            this._scrollView['_autoScrolling'] = false;
        }

        const deltaMove: any = newPosition.sub(this._scrollView.getContentPosition());
        this._scrollView['_moveContent'](this._scrollView['_clampDelta'](deltaMove), reachedEnd);
        this._scrollView['_dispatchEvent']('scrolling');

        // scollTo API controll move
        if (!this._scrollView['_autoScrolling']) {
            this._scrollView['_isBouncing'] = false;
            this._scrollView['_scrolling'] = false;
            this._scrollView['_dispatchEvent']('scroll-ended');
        }
    }
    //设置模板Item
    setTemplateItem(item: any) {
        if (!item) return;
        const t: any = this;
        t._itemTmp = item;

        if (t._resizeMode == cc.Layout.ResizeMode.CHILDREN) t._itemSize = t._layout.cellSize;
        else t._itemSize = cc.size(item.width, item.height);

        //获取ListItem,如果没有就取消选择模式
        let com = item.getComponent(ListItem);
        let remove = false;
        if (!com) remove = true;
        // if (com) {
        //     if (!com._btnCom && !item.getComponent(cc.Button)) {
        //         remove = true;
        //     }
        // }
        if (remove) {
            t.selectedMode = SelectedType.NONE;
        }
        com = item.getComponent(cc.Widget);
        if (com && com.enabled) {
            t._needUpdateWidget = true;
        }
        if (t.selectedMode == SelectedType.MULT) t.multSelected = [];

        switch (t._align) {
            case cc.Layout.Type.HORIZONTAL:
                t._colLineNum = 1;
                t._sizeType = false;
                break;
            case cc.Layout.Type.VERTICAL:
                t._colLineNum = 1;
                t._sizeType = true;
                break;
            case cc.Layout.Type.GRID:
                switch (t._startAxis) {
                    case cc.Layout.AxisDirection.HORIZONTAL:
                        //计算列数
                        const trimW: number = t.content.width - t._leftGap - t._rightGap;
                        t._colLineNum = Math.floor(
                            (trimW + t._columnGap) / (t._itemSize.width + t._columnGap)
                        );
                        t._sizeType = true;
                        break;
                    case cc.Layout.AxisDirection.VERTICAL:
                        //计算行数
                        const trimH: number = t.content.height - t._topGap - t._bottomGap;
                        t._colLineNum = Math.floor(
                            (trimH + t._lineGap) / (t._itemSize.height + t._lineGap)
                        );
                        t._sizeType = false;
                        break;
                }
                break;
        }
    }
    /**
     * 检查是否初始化
     * @param {Boolean} printLog 是否打印错误信息
     * @returns
     */
    checkInited(printLog = true) {
        if (!this._inited) {
            if (printLog) cc.error('List initialization not completed!');
            return false;
        }
        return true;
    }
    //禁用 Layout 组件,自行计算 Content Size
    _resizeContent() {
        const t: any = this;
        let result: number;

        switch (t._align) {
            case cc.Layout.Type.HORIZONTAL: {
                if (t._customSize) {
                    const fixed: any = t._getFixedSize(null);
                    result =
                        t._leftGap +
                        fixed.val +
                        t._itemSize.width * (t._numItems - fixed.count) +
                        t._columnGap * (t._numItems - 1) +
                        t._rightGap;
                } else {
                    result =
                        t._leftGap +
                        t._itemSize.width * t._numItems +
                        t._columnGap * (t._numItems - 1) +
                        t._rightGap;
                }
                break;
            }
            case cc.Layout.Type.VERTICAL: {
                if (t._customSize) {
                    const fixed: any = t._getFixedSize(null);
                    result =
                        t._topGap +
                        fixed.val +
                        t._itemSize.height * (t._numItems - fixed.count) +
                        t._lineGap * (t._numItems - 1) +
                        t._bottomGap;
                } else {
                    result =
                        t._topGap +
                        t._itemSize.height * t._numItems +
                        t._lineGap * (t._numItems - 1) +
                        t._bottomGap;
                }
                break;
            }
            case cc.Layout.Type.GRID: {
                //网格模式不支持居中
                if (t.lackCenter) t.lackCenter = false;
                switch (t._startAxis) {
                    case cc.Layout.AxisDirection.HORIZONTAL:
                        const lineNum: number = Math.ceil(t._numItems / t._colLineNum);
                        result =
                            t._topGap +
                            t._itemSize.height * lineNum +
                            t._lineGap * (lineNum - 1) +
                            t._bottomGap;
                        break;
                    case cc.Layout.AxisDirection.VERTICAL:
                        const colNum: number = Math.ceil(t._numItems / t._colLineNum);
                        result =
                            t._leftGap +
                            t._itemSize.width * colNum +
                            t._columnGap * (colNum - 1) +
                            t._rightGap;
                        break;
                }
                break;
            }
        }

        const layout: cc.Layout = t.content.getComponent(cc.Layout);
        if (layout) layout.enabled = false;

        t._allItemSize = result;
        t._allItemSizeNoEdge =
            t._allItemSize - (t._sizeType ? t._topGap + t._bottomGap : t._leftGap + t._rightGap);

        if (t.cyclic) {
            let totalSize: number = t._sizeType ? t.node.height : t.node.width;

            t._cyclicPos1 = 0;
            totalSize -= t._cyclicPos1;
            t._cyclicNum = Math.ceil(totalSize / t._allItemSizeNoEdge) + 1;
            const spacing: number = t._sizeType ? t._lineGap : t._columnGap;
            t._cyclicPos2 = t._cyclicPos1 + t._allItemSizeNoEdge + spacing;
            t._cyclicAllItemSize =
                t._allItemSize +
                t._allItemSizeNoEdge * (t._cyclicNum - 1) +
                spacing * (t._cyclicNum - 1);
            t._cycilcAllItemSizeNoEdge = t._allItemSizeNoEdge * t._cyclicNum;
            t._cycilcAllItemSizeNoEdge += spacing * (t._cyclicNum - 1);
            // cc.log('_cyclicNum ->', t._cyclicNum, t._allItemSizeNoEdge, t._allItemSize, t._cyclicPos1, t._cyclicPos2);
        }

        t._lack = !t.cyclic && t._allItemSize < (t._sizeType ? t.node.height : t.node.width);
        const slideOffset: number = (!t._lack || !t.lackCenter) && t.lackSlide ? 0 : 0.1;

        let targetWH: number = t._lack
            ? (t._sizeType ? t.node.height : t.node.width) - slideOffset
            : t.cyclic
            ? t._cyclicAllItemSize
            : t._allItemSize;
        if (targetWH < 0) targetWH = 0;

        if (t._sizeType) {
            t.content.height = targetWH;
        } else {
            t.content.width = targetWH;
        }

        // cc.log('_resizeContent()  numItems =', t._numItems, ',content =', t.content);
    }

    //滚动进行时...
    _onScrolling(ev: cc.Event = null) {
        if (this.frameCount == null) this.frameCount = this._updateRate;
        if (!this._forceUpdate && ev && ev.type != 'scroll-ended' && this.frameCount > 0) {
            this.frameCount--;
            return;
        } else this.frameCount = this._updateRate;

        if (this._aniDelRuning) return;
        if (this._clickFlag) {
            this._clickFlag = false;
        }
        //循环列表处理
        if (this.cyclic) {
            let scrollPos: any = this.content.getPosition();
            scrollPos = this._sizeType ? scrollPos.y : scrollPos.x;

            const addVal =
                this._allItemSizeNoEdge + (this._sizeType ? this._lineGap : this._columnGap);
            const add: any = this._sizeType ? cc.v2(0, addVal) : cc.v2(addVal, 0);

            switch (this._alignCalcType) {
                case 1: //单行HORIZONTAL(LEFT_TO_RIGHT)、网格VERTICAL(LEFT_TO_RIGHT)
                    if (scrollPos > -this._cyclicPos1) {
                        this.content.x = -this._cyclicPos2;
                        if (this._scrollView.isAutoScrolling()) {
                            this._scrollView['_autoScrollStartPosition'] =
                                this._scrollView['_autoScrollStartPosition'].sub(add);
                        }
                        // if (this._beganPos) {
                        //     this._beganPos += add;
                        // }
                    } else if (scrollPos < -this._cyclicPos2) {
                        this.content.x = -this._cyclicPos1;
                        if (this._scrollView.isAutoScrolling()) {
                            this._scrollView['_autoScrollStartPosition'] =
                                this._scrollView['_autoScrollStartPosition'].add(add);
                        }
                        // if (this._beganPos) {
                        //     this._beganPos -= add;
                        // }
                    }
                    break;
                case 2: //单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT)
                    if (scrollPos < this._cyclicPos1) {
                        this.content.x = this._cyclicPos2;
                        if (this._scrollView.isAutoScrolling()) {
                            this._scrollView['_autoScrollStartPosition'] =
                                this._scrollView['_autoScrollStartPosition'].add(add);
                        }
                    } else if (scrollPos > this._cyclicPos2) {
                        this.content.x = this._cyclicPos1;
                        if (this._scrollView.isAutoScrolling()) {
                            this._scrollView['_autoScrollStartPosition'] =
                                this._scrollView['_autoScrollStartPosition'].sub(add);
                        }
                    }
                    break;
                case 3: //单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM)
                    if (scrollPos < this._cyclicPos1) {
                        this.content.y = this._cyclicPos2;
                        if (this._scrollView.isAutoScrolling()) {
                            this._scrollView['_autoScrollStartPosition'] =
                                this._scrollView['_autoScrollStartPosition'].add(add);
                        }
                    } else if (scrollPos > this._cyclicPos2) {
                        this.content.y = this._cyclicPos1;
                        if (this._scrollView.isAutoScrolling()) {
                            this._scrollView['_autoScrollStartPosition'] =
                                this._scrollView['_autoScrollStartPosition'].sub(add);
                        }
                    }
                    break;
                case 4: //单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP)
                    if (scrollPos > -this._cyclicPos1) {
                        this.content.y = -this._cyclicPos2;
                        if (this._scrollView.isAutoScrolling()) {
                            this._scrollView['_autoScrollStartPosition'] =
                                this._scrollView['_autoScrollStartPosition'].sub(add);
                        }
                    } else if (scrollPos < -this._cyclicPos2) {
                        this.content.y = -this._cyclicPos1;
                        if (this._scrollView.isAutoScrolling()) {
                            this._scrollView['_autoScrollStartPosition'] =
                                this._scrollView['_autoScrollStartPosition'].add(add);
                        }
                    }
                    break;
            }
        }

        this._calcViewPos();

        let vTop: number, vRight: number, vBottom: number, vLeft: number;
        if (this._sizeType) {
            vTop = this.viewTop;
            vBottom = this.viewBottom;
        } else {
            vRight = this.viewRight;
            vLeft = this.viewLeft;
        }

        if (this._virtual) {
            this.displayData = [];
            let itemPos: any;

            let curId = 0;
            let endId: number = this._numItems - 1;

            if (this._customSize) {
                let breakFor = false;
                //如果该item的位置在可视区域内,就推入displayData
                for (; curId <= endId && !breakFor; curId++) {
                    itemPos = this._calcItemPos(curId);
                    switch (this._align) {
                        case cc.Layout.Type.HORIZONTAL:
                            if (itemPos.right >= vLeft && itemPos.left <= vRight) {
                                this.displayData.push(itemPos);
                            } else if (curId != 0 && this.displayData.length > 0) {
                                breakFor = true;
                            }
                            break;
                        case cc.Layout.Type.VERTICAL:
                            if (itemPos.bottom <= vTop && itemPos.top >= vBottom) {
                                this.displayData.push(itemPos);
                            } else if (curId != 0 && this.displayData.length > 0) {
                                breakFor = true;
                            }
                            break;
                        case cc.Layout.Type.GRID:
                            switch (this._startAxis) {
                                case cc.Layout.AxisDirection.HORIZONTAL:
                                    if (itemPos.bottom <= vTop && itemPos.top >= vBottom) {
                                        this.displayData.push(itemPos);
                                    } else if (curId != 0 && this.displayData.length > 0) {
                                        breakFor = true;
                                    }
                                    break;
                                case cc.Layout.AxisDirection.VERTICAL:
                                    if (itemPos.right >= vLeft && itemPos.left <= vRight) {
                                        this.displayData.push(itemPos);
                                    } else if (curId != 0 && this.displayData.length > 0) {
                                        breakFor = true;
                                    }
                                    break;
                            }
                            break;
                    }
                }
            } else {
                const ww: number = this._itemSize.width + this._columnGap;
                const hh: number = this._itemSize.height + this._lineGap;
                switch (this._alignCalcType) {
                    case 1: //单行HORIZONTAL(LEFT_TO_RIGHT)、网格VERTICAL(LEFT_TO_RIGHT)
                        curId = (vLeft - this._leftGap) / ww;
                        endId = (vRight - this._leftGap) / ww;
                        break;
                    case 2: //单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT)
                        curId = (-vRight - this._rightGap) / ww;
                        endId = (-vLeft - this._rightGap) / ww;
                        break;
                    case 3: //单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM)
                        curId = (-vTop - this._topGap) / hh;
                        endId = (-vBottom - this._topGap) / hh;
                        break;
                    case 4: //单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP)
                        curId = (vBottom - this._bottomGap) / hh;
                        endId = (vTop - this._bottomGap) / hh;
                        break;
                }
                curId = Math.floor(curId) * this._colLineNum;
                endId = Math.ceil(endId) * this._colLineNum;
                endId--;
                if (curId < 0) curId = 0;
                if (endId >= this._numItems) endId = this._numItems - 1;
                for (; curId <= endId; curId++) {
                    this.displayData.push(this._calcItemPos(curId));
                }
            }
            this._delRedundantItem();
            if (this.displayData.length <= 0 || !this._numItems) {
                //if none, delete all.
                this._lastDisplayData = [];
                return;
            }
            this.firstListId = this.displayData[0].id;
            this.displayItemNum = this.displayData.length;

            const len: number = this._lastDisplayData.length;

            let haveDataChange: boolean = this.displayItemNum != len;
            if (haveDataChange) {
                // 如果是逐帧渲染,需要排序
                if (this.frameByFrameRenderNum > 0) {
                    this._lastDisplayData.sort((a, b) => {
                        return a - b;
                    });
                }
                // 因List的显示数据是有序的,所以只需要判断数组长度是否相等,以及头、尾两个元素是否相等即可。
                haveDataChange =
                    this.firstListId != this._lastDisplayData[0] ||
                    this.displayData[this.displayItemNum - 1].id != this._lastDisplayData[len - 1];
            }

            if (this._forceUpdate || haveDataChange) {
                //如果是强制更新
                if (this.frameByFrameRenderNum > 0) {
                    // if (this._updateDone) {
                    // this._lastDisplayData = [];
                    //逐帧渲染
                    if (this._numItems > 0) {
                        if (!this._updateDone) {
                            this._doneAfterUpdate = true;
                        } else {
                            this._updateCounter = 0;
                        }
                        this._updateDone = false;
                    } else {
                        this._updateCounter = 0;
                        this._updateDone = true;
                    }
                    // }
                } else {
                    //直接渲染
                    this._lastDisplayData = [];
                    // cc.log('List Display Data II::', this.displayData);
                    for (let c = 0; c < this.displayItemNum; c++) {
                        this._createOrUpdateItem(this.displayData[c]);
                    }
                    this._forceUpdate = false;
                }
            }
            this._calcNearestItem();
        }
    }
    //计算可视范围
    _calcViewPos() {
        const scrollPos: any = this.content.getPosition();
        switch (this._alignCalcType) {
            case 1: //单行HORIZONTAL(LEFT_TO_RIGHT)、网格VERTICAL(LEFT_TO_RIGHT)
                this.elasticLeft = scrollPos.x > 0 ? scrollPos.x : 0;
                this.viewLeft = (scrollPos.x < 0 ? -scrollPos.x : 0) - this.elasticLeft;
                this.viewRight = this.viewLeft + this.node.width;
                this.elasticRight =
                    this.viewRight > this.content.width
                        ? Math.abs(this.viewRight - this.content.width)
                        : 0;
                this.viewRight += this.elasticRight;
                // cc.log(this.elasticLeft, this.elasticRight, this.viewLeft, this.viewRight);
                break;
            case 2: //单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT)
                this.elasticRight = scrollPos.x < 0 ? -scrollPos.x : 0;
                this.viewRight = (scrollPos.x > 0 ? -scrollPos.x : 0) + this.elasticRight;
                this.viewLeft = this.viewRight - this.node.width;
                this.elasticLeft =
                    this.viewLeft < -this.content.width
                        ? Math.abs(this.viewLeft + this.content.width)
                        : 0;
                this.viewLeft -= this.elasticLeft;
                // cc.log(this.elasticLeft, this.elasticRight, this.viewLeft, this.viewRight);
                break;
            case 3: //单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM)
                this.elasticTop = scrollPos.y < 0 ? Math.abs(scrollPos.y) : 0;
                this.viewTop = (scrollPos.y > 0 ? -scrollPos.y : 0) + this.elasticTop;
                this.viewBottom = this.viewTop - this.node.height;
                this.elasticBottom =
                    this.viewBottom < -this.content.height
                        ? Math.abs(this.viewBottom + this.content.height)
                        : 0;
                this.viewBottom += this.elasticBottom;
                // cc.log(this.elasticTop, this.elasticBottom, this.viewTop, this.viewBottom);
                break;
            case 4: //单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP)
                this.elasticBottom = scrollPos.y > 0 ? Math.abs(scrollPos.y) : 0;
                this.viewBottom = (scrollPos.y < 0 ? -scrollPos.y : 0) - this.elasticBottom;
                this.viewTop = this.viewBottom + this.node.height;
                this.elasticTop =
                    this.viewTop > this.content.height
                        ? Math.abs(this.viewTop - this.content.height)
                        : 0;
                this.viewTop -= this.elasticTop;
                // cc.log(this.elasticTop, this.elasticBottom, this.viewTop, this.viewBottom);
                break;
        }
    }
    //计算位置 根据id
    _calcItemPos(id: number) {
        let width: number,
            height: number,
            top: number,
            bottom: number,
            left: number,
            right: number,
            itemX: number,
            itemY: number;
        switch (this._align) {
            case cc.Layout.Type.HORIZONTAL:
                switch (this._horizontalDir) {
                    case cc.Layout.HorizontalDirection.LEFT_TO_RIGHT: {
                        if (this._customSize) {
                            const fixed: any = this._getFixedSize(id);
                            left =
                                this._leftGap +
                                (this._itemSize.width + this._columnGap) * (id - fixed.count) +
                                (fixed.val + this._columnGap * fixed.count);
                            const cs: number = this._customSize[id];
                            width = cs > 0 ? cs : this._itemSize.width;
                        } else {
                            left = this._leftGap + (this._itemSize.width + this._columnGap) * id;
                            width = this._itemSize.width;
                        }
                        if (this.lackCenter) {
                            left -= this._leftGap;
                            const offset: number =
                                this.content.width / 2 - this._allItemSizeNoEdge / 2;
                            left += offset;
                        }
                        right = left + width;
                        return {
                            id: id,
                            left: left,
                            right: right,
                            x: left + this._itemTmp.anchorX * width,
                            y: this._itemTmp.y,
                        };
                    }
                    case cc.Layout.HorizontalDirection.RIGHT_TO_LEFT: {
                        if (this._customSize) {
                            const fixed: any = this._getFixedSize(id);
                            right =
                                -this._rightGap -
                                (this._itemSize.width + this._columnGap) * (id - fixed.count) -
                                (fixed.val + this._columnGap * fixed.count);
                            const cs: number = this._customSize[id];
                            width = cs > 0 ? cs : this._itemSize.width;
                        } else {
                            right = -this._rightGap - (this._itemSize.width + this._columnGap) * id;
                            width = this._itemSize.width;
                        }
                        if (this.lackCenter) {
                            right += this._rightGap;
                            const offset: number =
                                this.content.width / 2 - this._allItemSizeNoEdge / 2;
                            right -= offset;
                        }
                        left = right - width;
                        return {
                            id: id,
                            right: right,
                            left: left,
                            x: left + this._itemTmp.anchorX * width,
                            y: this._itemTmp.y,
                        };
                    }
                }
                break;
            case cc.Layout.Type.VERTICAL: {
                switch (this._verticalDir) {
                    case cc.Layout.VerticalDirection.TOP_TO_BOTTOM: {
                        if (this._customSize) {
                            const fixed: any = this._getFixedSize(id);
                            top =
                                -this._topGap -
                                (this._itemSize.height + this._lineGap) * (id - fixed.count) -
                                (fixed.val + this._lineGap * fixed.count);
                            const cs: number = this._customSize[id];
                            height = cs > 0 ? cs : this._itemSize.height;
                        } else {
                            top = -this._topGap - (this._itemSize.height + this._lineGap) * id;
                            height = this._itemSize.height;
                        }
                        if (this.lackCenter) {
                            top += this._topGap;
                            const offset: number =
                                this.content.height / 2 - this._allItemSizeNoEdge / 2;
                            top -= offset;
                        }
                        bottom = top - height;
                        return {
                            id: id,
                            top: top,
                            bottom: bottom,
                            x: this._itemTmp.x,
                            y: bottom + this._itemTmp.anchorY * height,
                        };
                    }
                    case cc.Layout.VerticalDirection.BOTTOM_TO_TOP: {
                        if (this._customSize) {
                            const fixed: any = this._getFixedSize(id);
                            bottom =
                                this._bottomGap +
                                (this._itemSize.height + this._lineGap) * (id - fixed.count) +
                                (fixed.val + this._lineGap * fixed.count);
                            const cs: number = this._customSize[id];
                            height = cs > 0 ? cs : this._itemSize.height;
                        } else {
                            bottom = this._bottomGap + (this._itemSize.height + this._lineGap) * id;
                            height = this._itemSize.height;
                        }
                        if (this.lackCenter) {
                            bottom -= this._bottomGap;
                            const offset: number =
                                this.content.height / 2 - this._allItemSizeNoEdge / 2;
                            bottom += offset;
                        }
                        top = bottom + height;
                        return {
                            id: id,
                            top: top,
                            bottom: bottom,
                            x: this._itemTmp.x,
                            y: bottom + this._itemTmp.anchorY * height,
                        };
                        break;
                    }
                }
            }
            case cc.Layout.Type.GRID: {
                const colLine: number = Math.floor(id / this._colLineNum);
                switch (this._startAxis) {
                    case cc.Layout.AxisDirection.HORIZONTAL: {
                        switch (this._verticalDir) {
                            case cc.Layout.VerticalDirection.TOP_TO_BOTTOM: {
                                top =
                                    -this._topGap -
                                    (this._itemSize.height + this._lineGap) * colLine;
                                bottom = top - this._itemSize.height;
                                itemY = bottom + this._itemTmp.anchorY * this._itemSize.height;
                                break;
                            }
                            case cc.Layout.VerticalDirection.BOTTOM_TO_TOP: {
                                bottom =
                                    this._bottomGap +
                                    (this._itemSize.height + this._lineGap) * colLine;
                                top = bottom + this._itemSize.height;
                                itemY = bottom + this._itemTmp.anchorY * this._itemSize.height;
                                break;
                            }
                        }
                        itemX =
                            this._leftGap +
                            (id % this._colLineNum) * (this._itemSize.width + this._columnGap);
                        switch (this._horizontalDir) {
                            case cc.Layout.HorizontalDirection.LEFT_TO_RIGHT: {
                                itemX += this._itemTmp.anchorX * this._itemSize.width;
                                itemX -= this.content.anchorX * this.content.width;
                                break;
                            }
                            case cc.Layout.HorizontalDirection.RIGHT_TO_LEFT: {
                                itemX += (1 - this._itemTmp.anchorX) * this._itemSize.width;
                                itemX -= (1 - this.content.anchorX) * this.content.width;
                                itemX *= -1;
                                break;
                            }
                        }
                        return {
                            id: id,
                            top: top,
                            bottom: bottom,
                            x: itemX,
                            y: itemY,
                        };
                    }
                    case cc.Layout.AxisDirection.VERTICAL: {
                        switch (this._horizontalDir) {
                            case cc.Layout.HorizontalDirection.LEFT_TO_RIGHT: {
                                left =
                                    this._leftGap +
                                    (this._itemSize.width + this._columnGap) * colLine;
                                right = left + this._itemSize.width;
                                itemX = left + this._itemTmp.anchorX * this._itemSize.width;
                                itemX -= this.content.anchorX * this.content.width;
                                break;
                            }
                            case cc.Layout.HorizontalDirection.RIGHT_TO_LEFT: {
                                right =
                                    -this._rightGap -
                                    (this._itemSize.width + this._columnGap) * colLine;
                                left = right - this._itemSize.width;
                                itemX = left + this._itemTmp.anchorX * this._itemSize.width;
                                itemX += (1 - this.content.anchorX) * this.content.width;
                                break;
                            }
                        }
                        itemY =
                            -this._topGap -
                            (id % this._colLineNum) * (this._itemSize.height + this._lineGap);
                        switch (this._verticalDir) {
                            case cc.Layout.VerticalDirection.TOP_TO_BOTTOM: {
                                itemY -= (1 - this._itemTmp.anchorY) * this._itemSize.height;
                                itemY += (1 - this.content.anchorY) * this.content.height;
                                break;
                            }
                            case cc.Layout.VerticalDirection.BOTTOM_TO_TOP: {
                                itemY -= this._itemTmp.anchorY * this._itemSize.height;
                                itemY += this.content.anchorY * this.content.height;
                                itemY *= -1;
                                break;
                            }
                        }
                        return {
                            id: id,
                            left: left,
                            right: right,
                            x: itemX,
                            y: itemY,
                        };
                    }
                }
                break;
            }
        }
    }
    //计算已存在的Item的位置
    _calcExistItemPos(id: number) {
        const item: any = this.getItemByListId(id);
        if (!item) return null;
        const data: any = {
            id: id,
            x: item.x,
            y: item.y,
        };
        if (this._sizeType) {
            data.top = item.y + item.height * (1 - item.anchorY);
            data.bottom = item.y - item.height * item.anchorY;
        } else {
            data.left = item.x - item.width * item.anchorX;
            data.right = item.x + item.width * (1 - item.anchorX);
        }
        return data;
    }
    //获取Item位置
    getItemPos(id: number) {
        if (this._virtual) return this._calcItemPos(id);
        else {
            if (this.frameByFrameRenderNum) return this._calcItemPos(id);
            else return this._calcExistItemPos(id);
        }
    }
    //获取固定尺寸
    _getFixedSize(listId: number) {
        if (!this._customSize) return null;
        if (listId == null) listId = this._numItems;
        let fixed = 0;
        let count = 0;
        for (const id in this._customSize) {
            if (parseInt(id) < listId) {
                fixed += this._customSize[id];
                count++;
            }
        }
        return {
            val: fixed,
            count: count,
        };
    }
    //滚动结束时..
    _onScrollBegan() {
        this._beganPos = this._sizeType ? this.viewTop : this.viewLeft;
    }
    //滚动结束时..
    _onScrollEnded() {
        const t: any = this;
        t.curScrollIsTouch = false;
        if (t.scrollToListId != null) {
            const item: any = t.getItemByListId(t.scrollToListId);
            t.scrollToListId = null;
            if (item) {
                cc.tween(item).to(0.1, { scale: 1.06 }).to(0.1, { scale: 1 }).start();
            }
        }
        t._onScrolling();

        if (t._slideMode == SlideType.ADHERING && !t.adhering) {
            //cc.log(t.adhering, t._scrollView.isAutoScrolling(), t._scrollView.isScrolling());
            t.adhere();
        } else if (t._slideMode == SlideType.PAGE) {
            if (t._beganPos != null && t.curScrollIsTouch) {
                this._pageAdhere();
            } else {
                t.adhere();
            }
        }
    }
    // 触摸时
    _onTouchStart(ev, captureListeners) {
        if (this._scrollView['hasNestedViewGroup'](ev, captureListeners)) return;
        this.curScrollIsTouch = true;
        this._clickFlag = true;
        const isMe = ev.eventPhase === cc.Event.AT_TARGET && ev.target === this.node;
        if (!isMe) {
            let itemNode: any = ev.target;
            while (itemNode._listId == null && itemNode.parent) itemNode = itemNode.parent;
            this._scrollItem = itemNode._listId != null ? itemNode : ev.target;
        }
    }
    //触摸抬起时..
    _onTouchUp() {
        if (this._clickFlag && this.listClickEvent) {
            cc.Component.EventHandler.emitEvents([this.listClickEvent]);
        }
        this._clickFlag = false;
        const t: any = this;
        t._scrollPos = null;
        if (t._slideMode == SlideType.ADHERING) {
            if (this.adhering) this._adheringBarrier = true;
            t.adhere();
        } else if (t._slideMode == SlideType.PAGE) {
            if (t._beganPos != null) {
                this._pageAdhere();
            } else {
                t.adhere();
            }
        }
        this._scrollItem = null;
    }

    _onTouchCancelled(ev, captureListeners) {
        const t = this;
        if (t._scrollView['hasNestedViewGroup'](ev, captureListeners) || ev.simulate) return;

        t._scrollPos = null;
        if (t._slideMode == SlideType.ADHERING) {
            if (t.adhering) t._adheringBarrier = true;
            t.adhere();
        } else if (t._slideMode == SlideType.PAGE) {
            if (t._beganPos != null) {
                t._pageAdhere();
            } else {
                t.adhere();
            }
        }
        this._scrollItem = null;
    }
    //当尺寸改变
    _onSizeChanged() {
        if (this.checkInited(false)) this._onScrolling();
    }
    //当Item自适应
    _onItemAdaptive(item) {
        // if (this.checkInited(false)) {
        if (
            (!this._sizeType && item.width != this._itemSize.width) ||
            (this._sizeType && item.height != this._itemSize.height)
        ) {
            if (!this._customSize) this._customSize = {};
            const val = this._sizeType ? item.height : item.width;
            if (this._customSize[item._listId] != val) {
                this._customSize[item._listId] = val;
                this._resizeContent();
                // this.content.children.forEach((child: cc.Node) => {
                //     this._updateItemPos(child);
                // });
                this.updateAll();
                // 如果当前正在运行 scrollTo,肯定会不准确,在这里做修正
                if (this._scrollToListId != null) {
                    this._scrollPos = null;
                    this.unschedule(this._scrollToSo);
                    this.scrollTo(
                        this._scrollToListId,
                        Math.max(0, this._scrollToEndTime - new Date().getTime() / 1000)
                    );
                }
            }
        }
        // }
    }
    //PAGE粘附
    _pageAdhere() {
        const t = this;
        if (
            !t.cyclic &&
            (t.elasticTop > 0 || t.elasticRight > 0 || t.elasticBottom > 0 || t.elasticLeft > 0)
        )
            return;
        const curPos = t._sizeType ? t.viewTop : t.viewLeft;
        const dis = (t._sizeType ? t.node.height : t.node.width) * t.pageDistance;
        const canSkip = Math.abs(t._beganPos - curPos) > dis;
        if (canSkip) {
            const timeInSecond = 0.5;
            switch (t._alignCalcType) {
                case 1: //单行HORIZONTAL(LEFT_TO_RIGHT)、网格VERTICAL(LEFT_TO_RIGHT)
                case 4: //单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP)
                    if (t._beganPos > curPos) {
                        t.prePage(timeInSecond);
                        // cc.log('_pageAdhere   PPPPPPPPPPPPPPP');
                    } else {
                        t.nextPage(timeInSecond);
                        // cc.log('_pageAdhere   NNNNNNNNNNNNNNN');
                    }
                    break;
                case 2: //单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT)
                case 3: //单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM)
                    if (t._beganPos < curPos) {
                        t.prePage(timeInSecond);
                    } else {
                        t.nextPage(timeInSecond);
                    }
                    break;
            }
        } else if (
            t.elasticTop <= 0 &&
            t.elasticRight <= 0 &&
            t.elasticBottom <= 0 &&
            t.elasticLeft <= 0
        ) {
            t.adhere();
        }
        t._beganPos = null;
    }
    //粘附
    adhere() {
        const t: any = this;
        if (!t.checkInited()) return;
        if (t.elasticTop > 0 || t.elasticRight > 0 || t.elasticBottom > 0 || t.elasticLeft > 0)
            return;
        t.adhering = true;
        t._calcNearestItem();
        const offset: number =
            (t._sizeType ? t._topGap : t._leftGap) / (t._sizeType ? t.node.height : t.node.width);
        const timeInSecond = 0.7;
        t.scrollTo(t.nearestListId, timeInSecond, offset);
    }
    //Update..
    update() {
        if (this.frameByFrameRenderNum <= 0 || this._updateDone) return;
        // cc.log(this.displayData.length, this._updateCounter, this.displayData[this._updateCounter]);
        if (this._virtual) {
            const len: number =
                this._updateCounter + this.frameByFrameRenderNum > this.displayItemNum
                    ? this.displayItemNum
                    : this._updateCounter + this.frameByFrameRenderNum;
            for (let n: number = this._updateCounter; n < len; n++) {
                const data: any = this.displayData[n];
                if (data) {
                    this._createOrUpdateItem(data);
                }
            }

            if (this._updateCounter >= this.displayItemNum - 1) {
                //最后一个
                if (this._doneAfterUpdate) {
                    this._updateCounter = 0;
                    this._updateDone = false;
                    // if (!this._scrollView.isScrolling())
                    this._doneAfterUpdate = false;
                } else {
                    this._updateDone = true;
                    this._delRedundantItem();
                    this._forceUpdate = false;
                    this._calcNearestItem();
                    if (this.slideMode == SlideType.PAGE) this.curPageNum = this.nearestListId;
                }
            } else {
                this._updateCounter += this.frameByFrameRenderNum;
            }
        } else {
            if (this._updateCounter < this._numItems) {
                const len: number =
                    this._updateCounter + this.frameByFrameRenderNum > this._numItems
                        ? this._numItems
                        : this._updateCounter + this.frameByFrameRenderNum;
                for (let n: number = this._updateCounter; n < len; n++) {
                    this._createOrUpdateItem2(n);
                }
                this._updateCounter += this.frameByFrameRenderNum;
            } else {
                this._updateDone = true;
                this._calcNearestItem();
                if (this.slideMode == SlideType.PAGE) this.curPageNum = this.nearestListId;
            }
        }
    }

    _initCreatedItem(item) {
        if (this.initEvent) {
            cc.Component.EventHandler.emitEvents([this.initEvent], item);
        }
    }
    /**
     * 创建或更新Item(虚拟列表用)
     * @param {Object} data 数据
     */
    _createOrUpdateItem(data: any) {
        let item: any = this.getItemByListId(data.id);
        if (!item) {
            //如果不存在
            let canGet: boolean = this._pool.size() > 0;
            if (canGet) {
                item = this._pool.get();
                // cc.log('从池中取出::   旧id =', item['_listId'], ',新id =', data.id, item);
            } else {
                item = cc.instantiate(this._itemTmp);
                if (item) {
                    canGet = true;
                    this._initCreatedItem(item);
                }
                // cc.log('新建::', data.id, item);
            }
            if (!canGet || !cc.isValid(item)) {
                item = cc.instantiate(this._itemTmp);
                canGet = false;
                this._initCreatedItem(item);
            }
            if (item._listId != data.id) {
                item._listId = data.id;
                item.setContentSize(this._itemSize);
            }
            item.setPosition(cc.v2(data.x, data.y));
            this._resetItemSize(item);
            this.content.addChild(item);
            if (canGet && this._needUpdateWidget) {
                const widget: cc.Widget = item.getComponent(cc.Widget);
                if (widget) widget.updateAlignment();
            }
            item.setSiblingIndex(this.content.childrenCount - 1);

            const listItem: ListItem = item.getComponent(ListItem);

            if (listItem) {
                item['listItem'] = listItem;
                listItem.listId = data.id;
                listItem.list = this;
                listItem._registerEvent();

                if (this.renderEvent) {
                    cc.Component.EventHandler.emitEvents(
                        [this.renderEvent],
                        item,
                        data.id % this._actualNumItems
                    );
                }
            }
        } else if (this._forceUpdate && this.renderEvent) {
            //强制更新
            item.setPosition(cc.v2(data.x, data.y));
            this._resetItemSize(item);
            // cc.log('ADD::', data.id, item);
            if (this.renderEvent) {
                cc.Component.EventHandler.emitEvents(
                    [this.renderEvent],
                    item,
                    data.id % this._actualNumItems
                );
            }
        }
        this._resetItemSize(item);

        this._updateListItem(item['listItem']);
        if (this._lastDisplayData.indexOf(data.id) < 0) {
            this._lastDisplayData.push(data.id);
        }
    }
    //创建或更新Item(非虚拟列表用)
    _createOrUpdateItem2(listId: number) {
        let item: any = this.content.children[listId];
        let listItem: ListItem;
        if (!item) {
            //如果不存在
            item = cc.instantiate(this._itemTmp);
            item._listId = listId;
            this.content.addChild(item);
            listItem = item.getComponent(ListItem);
            item['listItem'] = listItem;
            if (listItem) {
                listItem.listId = listId;
                listItem.list = this;
                listItem._registerEvent();
            }
            if (this.renderEvent) {
                cc.Component.EventHandler.emitEvents(
                    [this.renderEvent],
                    item,
                    listId % this._actualNumItems
                );
            }
        } else if (this._forceUpdate && this.renderEvent) {
            //强制更新
            item._listId = listId;
            if (listItem) listItem.listId = listId;
            if (this.renderEvent) {
                cc.Component.EventHandler.emitEvents(
                    [this.renderEvent],
                    item,
                    listId % this._actualNumItems
                );
            }
        }
        this._updateListItem(listItem);
        if (this._lastDisplayData.indexOf(listId) < 0) {
            this._lastDisplayData.push(listId);
        }
    }

    _updateListItem(listItem: ListItem) {
        if (!listItem) return;
        if (this.selectedMode > SelectedType.NONE) {
            const item: any = listItem.node;
            switch (this.selectedMode) {
                case SelectedType.SINGLE:
                    listItem.selected = this.selectedId == item._listId;
                    break;
                case SelectedType.MULT:
                    listItem.selected = this.multSelected.indexOf(item._listId) >= 0;
                    break;
            }
        }
    }
    //仅虚拟列表用
    _resetItemSize(item: any) {
        return;
        let size: number;
        if (this._customSize && this._customSize[item._listId]) {
            size = this._customSize[item._listId];
        } else {
            if (this._colLineNum > 1) item.setContentSize(this._itemSize);
            else size = this._sizeType ? this._itemSize.height : this._itemSize.width;
        }
        if (size) {
            if (this._sizeType) item.height = size;
            else item.width = size;
        }
    }
    /**
     * 更新Item位置
     * @param {Number||Node} listIdOrItem
     */
    _updateItemPos(listIdOrItem: any) {
        const item: any = isNaN(listIdOrItem) ? listIdOrItem : this.getItemByListId(listIdOrItem);
        const pos: any = this.getItemPos(item._listId);
        item.setPosition(pos.x, pos.y);
    }
    /**
     * 设置多选
     * @param {Array} args 可以是单个listId,也可是个listId数组
     * @param {Boolean} bool 值,如果为null的话,则直接用args覆盖
     */
    setMultSelected(args: any, bool: boolean) {
        const t: any = this;
        if (!t.checkInited()) return;
        if (!Array.isArray(args)) {
            args = [args];
        }
        if (bool == null) {
            t.multSelected = args;
        } else {
            let listId: number, sub: number;
            if (bool) {
                for (let n: number = args.length - 1; n >= 0; n--) {
                    listId = args[n];
                    sub = t.multSelected.indexOf(listId);
                    if (sub < 0) {
                        t.multSelected.push(listId);
                    }
                }
            } else {
                for (let n: number = args.length - 1; n >= 0; n--) {
                    listId = args[n];
                    sub = t.multSelected.indexOf(listId);
                    if (sub >= 0) {
                        t.multSelected.splice(sub, 1);
                    }
                }
            }
        }
        t._forceUpdate = true;
        t._onScrolling();
    }
    /**
     * 获取多选数据
     * @returns
     */
    getMultSelected() {
        return this.multSelected;
    }
    /**
     * 多选是否有选择
     * @param {number} listId 索引
     * @returns
     */
    hasMultSelected(listId: number) {
        return this.multSelected && this.multSelected.indexOf(listId) >= 0;
    }
    /**
     * 更新指定的Item
     * @param {Array} args 单个listId,或者数组
     * @returns
     */
    updateItem(args: any) {
        if (!this.checkInited()) return;
        if (!Array.isArray(args)) {
            args = [args];
        }
        for (let n = 0, len: number = args.length; n < len; n++) {
            const listId: number = args[n];
            const item: any = this.getItemByListId(listId);
            if (item)
                cc.Component.EventHandler.emitEvents(
                    [this.renderEvent],
                    item,
                    listId % this._actualNumItems
                );
        }
    }
    /**
     * 更新全部
     */
    updateAll() {
        if (!this.checkInited()) return;
        this.numItems = this.numItems;
    }
    /**
     * 根据ListID获取Item
     * @param {Number} listId
     * @returns
     */
    getItemByListId(listId: number) {
        if (this.content) {
            for (let n: number = this.content.childrenCount - 1; n >= 0; n--) {
                const item: any = this.content.children[n];
                if (item._listId == listId) return item;
            }
        }
    }

    getAllCreatedItems() {
        if (this.content) {
            return this.content.children.filter((item) => {
                return item.getComponent(ListItem);
            });
        }
        return null;
    }
    /**
     * 获取在显示区域外的Item
     * @returns
     */
    _getOutsideItem() {
        let item: any;
        const result: any[] = [];
        for (let n: number = this.content.childrenCount - 1; n >= 0; n--) {
            item = this.content.children[n];
            if (
                !this.displayData.find((d) => d.id == item._listId) &&
                item.getComponent(ListItem)
            ) {
                result.push(item);
            }
        }

        return result;
    }
    //删除显示区域以外的Item
    _delRedundantItem() {
        if (this._virtual) {
            const arr: any[] = this._getOutsideItem();
            for (let n: number = arr.length - 1; n >= 0; n--) {
                const item: any = arr[n];
                if (
                    (this._scrollItem && item._listId == this._scrollItem._listId) ||
                    !cc.isValid(item, true) ||
                    !item.getComponent(ListItem)
                )
                    continue;
                item.isCached = true;
                cc.Component.EventHandler.emitEvents([this.recoveryEvent], item);
                this._pool.put(item);
                for (let m: number = this._lastDisplayData.length - 1; m >= 0; m--) {
                    if (this._lastDisplayData[m] == item._listId) {
                        this._lastDisplayData.splice(m, 1);
                        break;
                    }
                }
            }
            // cc.log('存入::', str, '    pool.length =', this._pool.length);
        } else {
            while (this.content.childrenCount > this._numItems) {
                this._delSingleItem(this.content.children[this.content.childrenCount - 1]);
            }
        }
    }

    //删除单个Item
    _delSingleItem(item: any) {
        // cc.log('DEL::', item['_listId'], item);
        item.removeFromParent();
        if (item.destroy) item.destroy();
        item = null;
    }
    /**
     * 动效删除Item(此方法只适用于虚拟列表,即_virtual=true)
     * 一定要在回调函数里重新设置新的numItems进行刷新,毕竟本List是靠数据驱动的。
     */
    aniDelItem(listId: number, callFunc: Function, aniType: number) {
        const t: any = this;

        if (!t.checkInited() || t.cyclic || !t._virtual)
            return cc.error('This function is not allowed to be called!');

        if (!callFunc)
            return cc.error(
                'CallFunc are not allowed to be NULL, You need to delete the corresponding index in the data array in the CallFunc!'
            );

        if (t._aniDelRuning) return cc.warn('Please wait for the current deletion to finish!');

        let item: any = t.getItemByListId(listId);
        let listItem: ListItem;
        if (!item) {
            callFunc(listId);
            return;
        } else {
            listItem = item.getComponent(ListItem);
        }
        t._aniDelRuning = true;
        t._aniDelCB = callFunc;
        t._aniDelItem = item;
        t._aniDelBeforePos = item.position;
        t._aniDelBeforeScale = item.scale;
        const curLastId: number = t.displayData[t.displayData.length - 1].id;
        const resetSelectedId: boolean = listItem.selected;
        listItem.showAni(
            aniType,
            () => {
                //判断有没有下一个,如果有的话,创建粗来
                let newId: number;
                if (curLastId < t._numItems - 2) {
                    newId = curLastId + 1;
                }
                if (newId != null) {
                    const newData: any = t._calcItemPos(newId);
                    t.displayData.push(newData);
                    if (t._virtual) t._createOrUpdateItem(newData);
                    else t._createOrUpdateItem2(newId);
                } else t._numItems--;
                if (t.selectedMode == SelectedType.SINGLE) {
                    if (resetSelectedId) {
                        t._selectedId = -1;
                    } else if (t._selectedId - 1 >= 0) {
                        t._selectedId--;
                    }
                } else if (t.selectedMode == SelectedType.MULT && t.multSelected.length) {
                    const sub: number = t.multSelected.indexOf(listId);
                    if (sub >= 0) {
                        t.multSelected.splice(sub, 1);
                    }
                    //多选的数据,在其后的全部减一
                    for (let n: number = t.multSelected.length - 1; n >= 0; n--) {
                        const id: number = t.multSelected[n];
                        if (id >= listId) t.multSelected[n]--;
                    }
                }
                if (t._customSize) {
                    if (t._customSize[listId]) delete t._customSize[listId];
                    const newCustomSize: any = {};
                    let size: number;
                    for (const id in t._customSize) {
                        size = t._customSize[id];
                        const idNumber: number = parseInt(id);
                        newCustomSize[idNumber - (idNumber >= listId ? 1 : 0)] = size;
                    }
                    t._customSize = newCustomSize;
                }
                //后面的Item向前怼的动效
                const sec = 0.2333;
                let tween: cc.Tween, haveCB: boolean;
                for (let n: number = newId != null ? newId : curLastId; n >= listId + 1; n--) {
                    item = t.getItemByListId(n);
                    if (item) {
                        const posData: any = t._calcItemPos(n - 1);
                        tween = cc.tween(item).to(sec, { position: cc.v2(posData.x, posData.y) });
                        if (n <= listId + 1) {
                            haveCB = true;
                            tween.call(() => {
                                t._aniDelRuning = false;
                                callFunc(listId);
                                delete t._aniDelCB;
                            });
                        }
                        tween.start();
                    }
                }
                if (!haveCB) {
                    t._aniDelRuning = false;
                    callFunc(listId);
                    t._aniDelCB = null;
                }
            },
            true
        );
    }
    /**
     * 滚动到..
     * @param {Number} listId 索引(如果<0,则滚到首个Item位置,如果>=_numItems,则滚到最末Item位置)
     * @param {Number} timeInSecond 时间
     * @param {Number} offset 索引目标位置偏移,0-1
     * @param {Boolean} overStress 滚动后是否强调该Item(这只是个实验功能)
     */
    scrollTo(listId: number, timeInSecond = 0.5, offset: number = null, overStress = false) {
        const t = this;
        if (!t.checkInited(false)) return;
        // t._scrollView.stopAutoScroll();
        if (timeInSecond == null)
            //默认0.5
            timeInSecond = 0.5;
        else if (timeInSecond < 0) timeInSecond = 0;
        if (listId < 0) listId = 0;
        else if (listId >= t._numItems) listId = t._numItems - 1;
        // 以防设置了numItems之后layout的尺寸还未更新
        if (!t._virtual && t._layout && t._layout.enabled) t._layout.updateLayout();

        let pos = t.getItemPos(listId);
        if (!pos) {
            return CC_DEV && cc.error('pos is null', listId);
        }
        let targetX: number, targetY: number;

        switch (t._alignCalcType) {
            case 1: //单行HORIZONTAL(LEFT_TO_RIGHT)、网格VERTICAL(LEFT_TO_RIGHT)
                targetX = pos.left;
                if (offset != null) targetX -= t.node.width * offset;
                else targetX -= t._leftGap;
                pos = cc.v2(targetX, 0);
                break;
            case 2: //单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT)
                targetX = pos.right - t.node.width;
                if (offset != null) targetX += t.node.width * offset;
                else targetX += t._rightGap;
                pos = cc.v2(targetX + t.content.width, 0);
                break;
            case 3: //单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM)
                targetY = pos.top;
                if (offset != null) targetY += t.node.height * offset;
                else targetY += t._topGap;
                pos = cc.v2(0, -targetY);
                break;
            case 4: //单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP)
                targetY = pos.bottom + t.node.height;
                if (offset != null) targetY -= t.node.height * offset;
                else targetY -= t._bottomGap;
                pos = cc.v2(0, -targetY + t.content.height);
                break;
        }
        let viewPos: any = t.content.getPosition();
        viewPos = Math.abs(t._sizeType ? viewPos.y : viewPos.x);

        const comparePos = t._sizeType ? pos.y : pos.x;
        const runScroll =
            Math.abs((t._scrollPos != null ? t._scrollPos : viewPos) - comparePos) > 0.5;
        // cc.log(runScroll, t._scrollPos, viewPos, comparePos)

        // t._scrollView.stopAutoScroll();
        if (runScroll) {
            t._scrollView.scrollToOffset(pos, timeInSecond);
            t._scrollToListId = listId;
            t._scrollToEndTime = new Date().getTime() / 1000 + timeInSecond;
            // cc.log(listId, t.content.width, t.content.getPosition(), pos);
            t._scrollToSo = t.scheduleOnce(() => {
                if (!t._adheringBarrier) {
                    t.adhering = t._adheringBarrier = false;
                }
                t._scrollPos = t._scrollToListId = t._scrollToEndTime = t._scrollToSo = null;
                //cc.log('2222222222', t._adheringBarrier)
                if (overStress) {
                    // t.scrollToListId = listId;
                    const item = t.getItemByListId(listId);
                    if (item) {
                        cc.tween(item).to(0.1, { scale: 1.05 }).to(0.1, { scale: 1 }).start();
                    }
                }
            }, timeInSecond + 0.1);

            if (timeInSecond <= 0) {
                t._onScrolling();
            }
        }
    }
    /**
     * 计算当前滚动窗最近的Item
     */
    _calcNearestItem() {
        const t: any = this;
        t.nearestListId = null;
        let data: any, center: number;

        if (t._virtual) t._calcViewPos();

        let vTop: number, vRight: number, vBottom: number, vLeft: number;
        vTop = t.viewTop;
        vRight = t.viewRight;
        vBottom = t.viewBottom;
        vLeft = t.viewLeft;

        let breakFor = false;
        for (let n = 0; n < t.content.childrenCount && !breakFor; n += t._colLineNum) {
            data = t._virtual ? t.displayData[n] : t._calcExistItemPos(n);
            if (data) {
                center = t._sizeType
                    ? (data.top + data.bottom) / 2
                    : (center = (data.left + data.right) / 2);
                switch (t._alignCalcType) {
                    case 1: //单行HORIZONTAL(LEFT_TO_RIGHT)、网格VERTICAL(LEFT_TO_RIGHT)
                        if (data.right >= vLeft) {
                            t.nearestListId = data.id;
                            if (vLeft > center) t.nearestListId += t._colLineNum;
                            breakFor = true;
                        }
                        break;
                    case 2: //单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT)
                        if (data.left <= vRight) {
                            t.nearestListId = data.id;
                            if (vRight < center) t.nearestListId += t._colLineNum;
                            breakFor = true;
                        }
                        break;
                    case 3: //单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM)
                        if (data.bottom <= vTop) {
                            t.nearestListId = data.id;
                            if (vTop < center) t.nearestListId += t._colLineNum;
                            breakFor = true;
                        }
                        break;
                    case 4: //单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP)
                        if (data.top >= vBottom) {
                            t.nearestListId = data.id;
                            if (vBottom > center) t.nearestListId += t._colLineNum;
                            breakFor = true;
                        }
                        break;
                }
            }
        }
        //判断最后一个Item。。。(哎,这些判断真心恶心,判断了前面的还要判断最后一个。。。一开始呢,就只有一个布局(单列布局),那时候代码才三百行,后来就想着完善啊,艹..这坑真深,现在这行数都一千五了= =||)
        data = t._virtual
            ? t.displayData[t.displayItemNum - 1]
            : t._calcExistItemPos(t._numItems - 1);
        if (data && data.id == t._numItems - 1) {
            center = t._sizeType
                ? (data.top + data.bottom) / 2
                : (center = (data.left + data.right) / 2);
            switch (t._alignCalcType) {
                case 1: //单行HORIZONTAL(LEFT_TO_RIGHT)、网格VERTICAL(LEFT_TO_RIGHT)
                    if (vRight > center) t.nearestListId = data.id;
                    break;
                case 2: //单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT)
                    if (vLeft < center) t.nearestListId = data.id;
                    break;
                case 3: //单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM)
                    if (vBottom < center) t.nearestListId = data.id;
                    break;
                case 4: //单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP)
                    if (vTop > center) t.nearestListId = data.id;
                    break;
            }
        }
        // cc.log('t.nearestListId =', t.nearestListId);
    }
    //上一页
    prePage(timeInSecond = 0.5) {
        // cc.log('👈');
        if (!this.checkInited()) return;
        this.skipPage(this.curPageNum - 1, timeInSecond);
    }
    //下一页
    nextPage(timeInSecond = 0.5) {
        // cc.log('👉');
        if (!this.checkInited()) return;
        this.skipPage(this.curPageNum + 1, timeInSecond);
    }
    //跳转到第几页
    skipPage(pageNum: number, timeInSecond: number) {
        const t: any = this;
        if (!t.checkInited()) return;
        if (t._slideMode != SlideType.PAGE)
            return cc.error('This function is not allowed to be called, Must SlideMode = PAGE!');
        if (pageNum < 0 || pageNum >= t._numItems) return;
        if (t.curPageNum == pageNum) return;
        // cc.log(pageNum);
        t.curPageNum = pageNum;
        if (t.pageChangeEvent) {
            cc.Component.EventHandler.emitEvents([t.pageChangeEvent], pageNum);
        }
        t.scrollTo(pageNum, timeInSecond);
    }
    //计算 CustomSize(这个函数还是保留吧,某些罕见的情况的确还是需要手动计算customSize的)
    calcCustomSize(numItems: number) {
        const t: any = this;
        if (!t.checkInited()) return;
        if (!t._itemTmp) return cc.error('Unset template item!');
        if (!t.renderEvent) return cc.error('Unset Render-Event!');
        t._customSize = {};
        const temp: any = cc.instantiate(t._itemTmp);
        t.content.addChild(temp);
        for (let n = 0; n < numItems; n++) {
            cc.Component.EventHandler.emitEvents([t.renderEvent], temp, n);
            if (temp.height != t._itemSize.height || temp.width != t._itemSize.width) {
                t._customSize[n] = t._sizeType ? temp.height : temp.width;
            }
        }
        if (!Object.keys(t._customSize).length) t._customSize = null;
        temp.removeFromParent();
        if (temp.destroy) temp.destroy();
        return t._customSize;
    }

    refreshItems() {
        // refreshItemEvent
        if (this.refreshItemEvent) {
            this.content.children.forEach((value) => {
                if (value.getComponent(ListItem))
                    cc.Component.EventHandler.emitEvents([this.refreshItemEvent], value);
            });
        }
    }

    getChildItems() {
        return this.content.children.filter((value) => {
            return value.getComponent(ListItem);
        });
    }
}



item:

 

/******************************************
 * @author kL <klk0@qq.com>
 * @date 2019/6/6
 * @doc 列表Item组件.
 * 说明:
 *      1、此组件须配合List组件使用。(配套的配套的..)
 * @end
 ******************************************/
/* eslint-disable */
const { ccclass, property, disallowMultiple, menu, executionOrder } = cc._decorator;

import List from './List';

enum SelectedType {
    NONE = 0,
    TOGGLE = 1,
    SWITCH = 2,
}

@ccclass
@disallowMultiple()
@menu('自定义组件/List Item')
@executionOrder(-5001) //先于List
export default class ListItem extends cc.Component {
    //选择模式
    @property({
        type: cc.Enum(SelectedType),
        tooltip: CC_DEV && '选择模式',
    })
    selectedMode: SelectedType = SelectedType.NONE;
    //被选标志
    @property({
        type: cc.Node,
        tooltip: CC_DEV && '被选标识',
        visible() {
            return this.selectedMode > SelectedType.NONE;
        },
    })
    selectedFlag: cc.Node = null;
    //被选择的SpriteFrame
    @property({
        type: cc.SpriteFrame,
        tooltip: CC_DEV && '被选择的SpriteFrame',
        visible() {
            return this.selectedMode == SelectedType.SWITCH;
        },
    })
    selectedSpriteFrame: cc.SpriteFrame = null;
    //未被选择的SpriteFrame
    _unselectedSpriteFrame: cc.SpriteFrame = null;
    //自适应尺寸
    @property({
        tooltip: CC_DEV && '自适应尺寸(宽或高)',
    })
    adaptiveSize = false;
    //选择
    _selected = false;
    set selected(val: boolean) {
        this._selected = val;
        if (!this.selectedFlag) return;
        switch (this.selectedMode) {
            case SelectedType.TOGGLE:
                this.selectedFlag.active = val;
                break;
            case SelectedType.SWITCH:
                const sp: cc.Sprite = this.selectedFlag.getComponent(cc.Sprite);
                if (sp) {
                    sp.spriteFrame = val ? this.selectedSpriteFrame : this._unselectedSpriteFrame;
                }
                break;
        }
    }
    get selected() {
        return this._selected;
    }
    //按钮组件
    private _btnCom: any;
    get btnCom() {
        if (!this._btnCom) this._btnCom = this.node.getComponent(cc.Button);
        return this._btnCom;
    }
    //依赖的List组件
    public list: List;
    //是否已经注册过事件
    private _eventReg = false;
    //序列id
    public listId: number;

    onLoad() {
        // //没有按钮组件的话,selectedFlag无效
        // if (!this.btnCom)
        //     this.selectedMode == SelectedType.NONE;
        //有选择模式时,保存相应的东西
        if (this.selectedMode == SelectedType.SWITCH) {
            const com: cc.Sprite = this.selectedFlag.getComponent(cc.Sprite);
            this._unselectedSpriteFrame = com.spriteFrame;
        }
    }

    onDestroy() {
        this.node.off(cc.Node.EventType.SIZE_CHANGED, this._onSizeChange, this);
    }

    _registerEvent() {
        if (!this._eventReg) {
            // if (this.btnCom && this.list.selectedMode > 0) {
            //     this.btnCom.clickEvents.unshift(this.createEvt(this, 'onClickThis'));
            // }
            // 不是选择模式也应该触发点击事件
            if (this.btnCom) this.btnCom.clickEvents.unshift(this.createEvt(this, 'onClickThis'));
            if (this.adaptiveSize) {
                this.node.on(cc.Node.EventType.SIZE_CHANGED, this._onSizeChange, this);
            }
            this._eventReg = true;
        }
    }

    _onSizeChange() {
        this.list._onItemAdaptive(this.node);
    }
    /**
     * 创建事件
     * @param {cc.Component} component 组件脚本
     * @param {string} handlerName 触发函数名称
     * @param {cc.Node} node 组件所在node(不传的情况下取component.node)
     * @returns cc.Component.EventHandler
     */
    createEvt(component: cc.Component, handlerName: string, node: cc.Node = null) {
        if (!component.isValid) return; //有些异步加载的,节点以及销毁了。
        component['comName'] =
            component['comName'] ||
            component.name
                .match(/\<(.*?)\>/g)
                .pop()
                .replace(/\<|>/g, '');
        const evt = new cc.Component.EventHandler();
        evt.target = node || component.node;
        evt.component = component['comName'];
        evt.handler = handlerName;
        return evt;
    }

    showAni(aniType: number, callFunc: Function, del: boolean) {
        const t: any = this;
        let tween: cc.Tween;
        switch (aniType) {
            case 0: //向上消失
                tween = cc
                    .tween(t.node)
                    .to(0.2, { scale: 0.7 })
                    .by(0.3, { y: t.node.height * 2 });
                break;
            case 1: //向右消失
                tween = cc
                    .tween(t.node)
                    .to(0.2, { scale: 0.7 })
                    .by(0.3, { x: t.node.width * 2 });
                break;
            case 2: //向下消失
                tween = cc
                    .tween(t.node)
                    .to(0.2, { scale: 0.7 })
                    .by(0.3, { y: t.node.height * -2 });
                break;
            case 3: //向左消失
                tween = cc
                    .tween(t.node)
                    .to(0.2, { scale: 0.7 })
                    .by(0.3, { x: t.node.width * -2 });
                break;
            default: //默认:缩小消失
                tween = cc.tween(t.node).to(0.3, { scale: 0.1 });
                break;
        }
        if (callFunc || del) {
            tween.call(() => {
                if (del) {
                    t.list._delSingleItem(t.node);
                    for (let n: number = t.list.displayData.length - 1; n >= 0; n--) {
                        if (t.list.displayData[n].id == t.listId) {
                            t.list.displayData.splice(n, 1);
                            break;
                        }
                    }
                }
                callFunc();
            });
        }
        tween.start();
    }

    onClickThis() {
        this.list.selectedId = this.listId;
    }
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值