import { _decorator, ScrollView, Node, Prefab, Enum, Component, EventHandler, Layout, NodePool, Size, instantiate, size, Widget, v3, UITransform, v2, tween } from "cc"; import li_Component from "./li_Component"; import ScrollviewListItem from "./ScrollviewListItem"; import Utils from "./Utils"; const { ccclass, property, disallowMultiple, menu, executionOrder, requireComponent } = _decorator; enum TemplateType { NODE = 1, PREFAB = 2, } //滑动列表组件 @ccclass @disallowMultiple() @menu('自定义控件/ScrollViewList') @requireComponent(ScrollView) //脚本生命周期回调的执行优先级。小于 0 的脚本将优先执行,大于 0 的脚本将最后执行。该优先级只对 onLoad, onEnable, start, update 和 lateUpdate 有效,对 onDisable 和 onDestroy 无效。 @executionOrder(-4000) export default class ScrollViewList extends li_Component { //模板类型 @property({ type: Enum(TemplateType), tooltip: '模板类型 Node或者Prefab', }) private templateType: TemplateType = TemplateType.NODE; //模板Item(Node) @property({ type: Node, tooltip: '模板Item', visible() { return this.templateType == TemplateType.NODE; } }) tmpNode: Node = null; //模板Item(Prefab) @property({ type: Prefab, tooltip: '模板Item', visible() { return this.templateType == TemplateType.PREFAB; } }) tmpPrefab: Prefab = null; //渲染事件(渲染器) @property({ type: EventHandler, tooltip: '渲染事件(渲染器)', }) private renderEvent: EventHandler = new EventHandler(); //滚动结束触发事件 @property({ type: EventHandler, tooltip: '滚动结束时触发事件', }) private renderEventUpData: EventHandler = new EventHandler(); //当前选择id private _virtual: boolean = true;//是否为虚拟列表(动态列表) private _selectedId: number = -1; private _lastSelectedId: number; private multSelected: number[]; private _forceUpdate: boolean = false; private _align: number; private _horizontalDir: number; private _verticalDir: number; private _startAxis: number; private _alignCalcType: number; public content: Node; private contentUITF: UITransform; private firstListId: number; public displayItemNum: number; private _updateDone: boolean = true; private _updateCounter: number; public _actualNumItems: number; private _cyclicNum: number; private _cyclicPos1: number; private _cyclicPos2: number; //列表数量 @property({ serializable: false }) private _numItems: number = 0; set numItems(val: number) { if (!this.checkInited(false)) return; if (val == null || val < 0) { //hg_utils.hgLog('numItems set the wrong::', val); return; } this._actualNumItems = this._numItems = val; this._forceUpdate = true; if (this._virtual) { this._resizeContent(); this._onScrolling(); } else { let layout: Layout = this.content.getComponent(Layout); if (layout) { layout.enabled = true; } this._delRedundantItem(); this.firstListId = 0; for (let n: number = 0; n < val; n++) { this._createOrUpdateItem2(n); } this.displayItemNum = val; } } get numItems() { return this._actualNumItems; } private _inited: boolean = false; private _scrollView: ScrollView; get scrollView() { return this._scrollView; } private _uitf: UITransform; private _layout: Layout; private _resizeMode//: 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: NodePool; private _itemTmp: Node; private _needUpdateWidget: boolean = false; private _itemSize: Size; private _sizeType: boolean; public _customSize: any; private frameCount: number; private _aniDelRuning: boolean = false; private viewTop: number; private viewRight: number; private viewBottom: number; private viewLeft: number; private _doneAfterUpdate: boolean = false; private elasticTop: number; private elasticRight: number; private elasticBottom: number; private elasticLeft: number; private scrollToListId: number; private adhering: boolean = false; private _adheringBarrier: boolean = false; private nearestListId: number; public curPageNum: number = 0; private _beganPos: number; private _scrollPos: number; private _scrollToListId: number; private _scrollToEndTime: number; private _scrollToSo: any; private _lack: boolean; private _allItemSize: number; private _allItemSizeNoEdge: number; private _scrollItem: any;//当前控制 ScrollView 滚动的 Item set TopGap(val: number) { this._topGap = val } //---------------------------------------------------------------------------- onLoad() { this._init(); } onNodeDestroy() { if (this._itemTmp && this._itemTmp.isValid) this._itemTmp.destroy(); if (this.tmpNode && this.tmpNode.isValid) this.tmpNode.destroy(); while (this._pool && this._pool.size()) { let node = this._pool.get(); node.destroy(); } } onEnable() { this._registerEvent(); this._init(); } onDisable() { this._unregisterEvent(); } //注册事件 _registerEvent() { this.node.on(Node.EventType.TOUCH_START, this._onTouchStart, this); this.node.on(Node.EventType.TOUCH_END, this._onTouchUp, this); this.node.on(Node.EventType.TOUCH_CANCEL, this._onTouchCancelled, this); this.node.on(ScrollView.EventType.SCROLL_BEGAN, this._onScrollBegan, this); this.node.on(ScrollView.EventType.SCROLL_ENDED, this._onScrollEnded, this); this.node.on(ScrollView.EventType.SCROLLING, this._onScrolling, this); this.node.on(Node.EventType.SIZE_CHANGED, this._onSizeChanged, this); } //卸载事件 _unregisterEvent() { this.node.off(Node.EventType.TOUCH_START, this._onTouchStart, this); this.node.off(Node.EventType.TOUCH_END, this._onTouchUp, this); this.node.off(Node.EventType.TOUCH_CANCEL, this._onTouchCancelled, this); this.node.off(ScrollView.EventType.SCROLL_BEGAN, this._onScrollBegan, this); this.node.off(ScrollView.EventType.SCROLL_ENDED, this._onScrollEnded, this); this.node.off(ScrollView.EventType.SCROLLING, this._onScrolling, this); this.node.off(Node.EventType.SIZE_CHANGED, this._onSizeChanged, this); } //初始化各种.. _init() { if (this._inited) return; if (this.tmpPrefab == null && this.tmpNode == null) return; this._scrollView = this.node.getComponent(ScrollView); this.content = this._scrollView.content; if (!this.content) { //hg_utils.hgLog(this.node.name + "'s ScrollView unset content!"); return; } this.contentUITF = this.content.getComponent(UITransform); this._uitf = this.node.getComponent(UITransform); this._layout = this.content.getComponent(Layout); this._align = this._layout.type; //排列模式 this._resizeMode = this._layout.resizeMode; //自适应模式 this._startAxis = this._layout.startAxis; this._topGap = this._layout.paddingTop; //顶边距 this._rightGap = this._layout.paddingRight; //右边距 this._bottomGap = this._layout.paddingBottom; //底边距 this._leftGap = this._layout.paddingLeft; //左边距 this._columnGap = this._layout.spacingX; //列距 this._lineGap = this._layout.spacingY; //行距 this._colLineNum; //列数或行数(非GRID模式则=1,表示单列或单行); this._verticalDir = this._layout.verticalDirection; //垂直排列子节点的方向 this._horizontalDir = this._layout.horizontalDirection; //水平排列子节点的方向 this.setTemplateItem(instantiate(this.templateType == TemplateType.PREFAB ? this.tmpPrefab : this.tmpNode)); this._lastDisplayData = []; //最后一次刷新的数据 this.displayData = []; //当前数据 while (this._pool && this._pool.size()) { let node = this._pool.get(); node.destroy(); } this._pool = new NodePool(); //这是个池子.. this._forceUpdate = false; //是否强制更新 this._updateCounter = 0; //当前分帧渲染帧数 this._updateDone = true; //分帧渲染是否完成 this.curPageNum = 0; //当前页数 switch (this._align) { case Layout.Type.HORIZONTAL: { switch (this._horizontalDir) { case Layout.HorizontalDirection.LEFT_TO_RIGHT: this._alignCalcType = 1; break; case Layout.HorizontalDirection.RIGHT_TO_LEFT: this._alignCalcType = 2; break; } break; } case Layout.Type.VERTICAL: { switch (this._verticalDir) { case Layout.VerticalDirection.TOP_TO_BOTTOM: this._alignCalcType = 3; break; case Layout.VerticalDirection.BOTTOM_TO_TOP: this._alignCalcType = 4; break; } break; } case Layout.Type.GRID: { switch (this._startAxis) { case Layout.AxisDirection.HORIZONTAL: switch (this._verticalDir) { case Layout.VerticalDirection.TOP_TO_BOTTOM: this._alignCalcType = 3; break; case Layout.VerticalDirection.BOTTOM_TO_TOP: this._alignCalcType = 4; break; } break; case Layout.AxisDirection.VERTICAL: switch (this._horizontalDir) { case Layout.HorizontalDirection.LEFT_TO_RIGHT: this._alignCalcType = 1; break; case Layout.HorizontalDirection.RIGHT_TO_LEFT: this._alignCalcType = 2; break; } break; } break; } } this.content.removeAllChildren(); this._inited = true; } /** * 为了实现循环列表,必须覆写ScrollView的某些函数 * @param {Number} dt */ _processAutoScrolling(dt: number) { let brakingFactor: number = 1; this._scrollView['_autoScrollAccumulatedTime'] += dt * (1 / brakingFactor); let percentage: number = Math.min(1, this._scrollView['_autoScrollAccumulatedTime'] / this._scrollView['_autoScrollTotalTime']); if (this._scrollView['_autoScrollAttenuate']) { let time: number = percentage - 1; percentage = time * time * time * time * time + 1; } let astdelta = this._scrollView['_autoScrollTargetDelta']//.mul(percentage) let newDeltav3 = v3(astdelta.x*percentage, astdelta.y*percentage, astdelta.z*percentage) let newPosition: any = this._scrollView['_autoScrollStartPosition'].add(newDeltav3); let EPSILON: number = this._scrollView['getScrollEndedEventTiming'](); let reachedEnd: boolean = Math.abs(percentage - 1) <= EPSILON; let 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; } let deltaMove: any = newPosition.sub(this._scrollView.content.getPosition()); //断点 // this._scrollView['_moveContent'](this._scrollView['_clampDelta'](deltaMove), reachedEnd); this._scrollView['_dispatchEvent'](ScrollView.EventType.SCROLLING); if (!this._scrollView['_autoScrolling']) { this._scrollView['_isBouncing'] = false; this._scrollView['_scrolling'] = false; this._scrollView['_dispatchEvent'](ScrollView.EventType.SCROLL_ENDED); } } //设置模板Item setTemplateItem(item: any) { if (!item) return; if (this.tmpNode == null && this.tmpPrefab == null) { if (item instanceof Prefab) { this.templateType = TemplateType.PREFAB; this.tmpPrefab = item; } else if (item instanceof Node) { this.templateType = TemplateType.NODE; this.tmpNode = item; } this._init(); return; } this._itemTmp = item; if (this._resizeMode == Layout.ResizeMode.CHILDREN){ this._itemSize = this._layout.cellSize; } else{ let uitf = item.getComponent(UITransform) this._itemSize = size(uitf.width, uitf.height); } //获取ListItem,如果没有就取消选择模式 let com = item.getComponent(ScrollviewListItem); let remove = false; if (!com) remove = true; com = item.getComponent(Widget); if (com && com.enabled) { this._needUpdateWidget = true; } switch (this._align) { case Layout.Type.HORIZONTAL: this._colLineNum = 1; this._sizeType = false; break; case Layout.Type.VERTICAL: this._colLineNum = 1; this._sizeType = true; break; case Layout.Type.GRID: switch (this._startAxis) { case Layout.AxisDirection.HORIZONTAL: //计算列数 let trimW: number = this.contentUITF.width - this._leftGap - this._rightGap; this._colLineNum = Math.floor((trimW + this._columnGap) / (this._itemSize.width + this._columnGap)); this._sizeType = true; break; case Layout.AxisDirection.VERTICAL: //计算行数 let trimH: number = this.contentUITF.height - this._topGap - this._bottomGap; this._colLineNum = Math.floor((trimH + this._lineGap) / (this._itemSize.height + this._lineGap)); this._sizeType = false; break; } break; } } /** * 检查是否初始化 * @param {Boolean} printLog 是否打印错误信息 * @returns */ checkInited(printLog: boolean = true) { if (!this._inited) { if (printLog) //hg_utils.hgLog('List initialization not completed!'); return false; } return true; } //禁用 Layout 组件,自行计算 Content Size _resizeContent() { let result: number; switch (this._align) { case Layout.Type.HORIZONTAL: { if (this._customSize) { let fixed: any = this._getFixedSize(null); result = this._leftGap + fixed.val + (this._itemSize.width * (this._numItems - fixed.count)) + (this._columnGap * (this._numItems - 1)) + this._rightGap; } else { result = this._leftGap + (this._itemSize.width * this._numItems) + (this._columnGap * (this._numItems - 1)) + this._rightGap; } break; } case Layout.Type.VERTICAL: { if (this._customSize) { let fixed: any = this._getFixedSize(null); result = this._topGap + fixed.val + (this._itemSize.height * (this._numItems - fixed.count)) + (this._lineGap * (this._numItems - 1)) + this._bottomGap; } else { result = this._topGap + (this._itemSize.height * this._numItems) + (this._lineGap * (this._numItems - 1)) + this._bottomGap; } break; } case Layout.Type.GRID: { switch (this._startAxis) { case Layout.AxisDirection.HORIZONTAL: let lineNum: number = Math.ceil(this._numItems / this._colLineNum); result = this._topGap + (this._itemSize.height * lineNum) + (this._lineGap * (lineNum - 1)) + this._bottomGap; break; case Layout.AxisDirection.VERTICAL: let colNum: number = Math.ceil(this._numItems / this._colLineNum); result = this._leftGap + (this._itemSize.width * colNum) + (this._columnGap * (colNum - 1)) + this._rightGap; break; } break; } } if (!this.content) { return; } let layout: Layout = this.content.getComponent(Layout); if (layout) layout.enabled = false; this._allItemSize = result; this._allItemSizeNoEdge = this._allItemSize - (this._sizeType ? (this._topGap + this._bottomGap) : (this._leftGap + this._rightGap)); this._lack = this._allItemSize < (this._sizeType ? this._uitf.height : this._uitf.width); let slideOffset: number = .1; let targetWH: number = this._lack ? ((this._sizeType ? this._uitf.height : this._uitf.width) - slideOffset) : (this._allItemSize); if (targetWH < 0) targetWH = 0; if (this._sizeType) { this.contentUITF.height = targetWH; } else { this.contentUITF.width = targetWH; } } //计算可视范围 _calcViewPos() { if (!this.content) return; let 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._uitf.width; this.elasticRight = this.viewRight > this.contentUITF.width ? Math.abs(this.viewRight - this.contentUITF.width) : 0; this.viewRight += this.elasticRight; // //hg_utils.hgLog(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._uitf.width; this.elasticLeft = this.viewLeft < -this.contentUITF.width ? Math.abs(this.viewLeft + this.contentUITF.width) : 0; this.viewLeft -= this.elasticLeft; // //hg_utils.hgLog(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._uitf.height; this.elasticBottom = this.viewBottom < -this.contentUITF.height ? Math.abs(this.viewBottom + this.contentUITF.height) : 0; this.viewBottom += this.elasticBottom; // //hg_utils.hgLog(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._uitf.height; this.elasticTop = this.viewTop > this.contentUITF.height ? Math.abs(this.viewTop - this.contentUITF.height) : 0; this.viewTop -= this.elasticTop; // //hg_utils.hgLog(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; if (!this._itemSize) return {}; switch (this._align) { case Layout.Type.HORIZONTAL: switch (this._horizontalDir) { case Layout.HorizontalDirection.LEFT_TO_RIGHT: { if (this._customSize) { let fixed: any = this._getFixedSize(id); left = this._leftGap + ((this._itemSize.width + this._columnGap) * (id - fixed.count)) + (fixed.val + (this._columnGap * fixed.count)); let 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; } right = left + width; return { id: id, left: left, right: right, x: left + (this._itemTmp.getComponent(UITransform).anchorX * width), y: this._itemTmp.position.y, }; } case Layout.HorizontalDirection.RIGHT_TO_LEFT: { if (this._customSize) { let fixed: any = this._getFixedSize(id); right = -this._rightGap - ((this._itemSize.width + this._columnGap) * (id - fixed.count)) - (fixed.val + (this._columnGap * fixed.count)); let 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; } left = right - width; return { id: id, right: right, left: left, x: left + (this._itemTmp.getComponent(UITransform).anchorX * width), y: this._itemTmp.position.y, }; } } break; case Layout.Type.VERTICAL: { switch (this._verticalDir) { case Layout.VerticalDirection.TOP_TO_BOTTOM: { if (this._customSize) { let fixed: any = this._getFixedSize(id); top = -this._topGap - ((this._itemSize.height + this._lineGap) * (id - fixed.count)) - (fixed.val + (this._lineGap * fixed.count)); let 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; } bottom = top - height; return { id: id, top: top, bottom: bottom, x: this._itemTmp.position.x, y: bottom + (this._itemTmp.getComponent(UITransform).anchorY * height), }; } case Layout.VerticalDirection.BOTTOM_TO_TOP: { if (this._customSize) { let fixed: any = this._getFixedSize(id); bottom = this._bottomGap + ((this._itemSize.height + this._lineGap) * (id - fixed.count)) + (fixed.val + (this._lineGap * fixed.count)); let 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; } top = bottom + height; return { id: id, top: top, bottom: bottom, x: this._itemTmp.position.x, y: bottom + (this._itemTmp.getComponent(UITransform).anchorY * height), }; break; } } } case Layout.Type.GRID: { let colLine: number = Math.floor(id / this._colLineNum); switch (this._startAxis) { case Layout.AxisDirection.HORIZONTAL: { switch (this._verticalDir) { case Layout.VerticalDirection.TOP_TO_BOTTOM: { top = -this._topGap - ((this._itemSize.height + this._lineGap) * colLine); bottom = top - this._itemSize.height; itemY = bottom + (this._itemTmp.getComponent(UITransform).anchorY * this._itemSize.height); break; } case Layout.VerticalDirection.BOTTOM_TO_TOP: { bottom = this._bottomGap + ((this._itemSize.height + this._lineGap) * colLine); top = bottom + this._itemSize.height; itemY = bottom + (this._itemTmp.getComponent(UITransform).anchorY * this._itemSize.height); break; } } itemX = this._leftGap + ((id % this._colLineNum) * (this._itemSize.width + this._columnGap)); switch (this._horizontalDir) { case Layout.HorizontalDirection.LEFT_TO_RIGHT: { itemX += (this._itemTmp.getComponent(UITransform).anchorX * this._itemSize.width); itemX -= (this.contentUITF.anchorX * this.contentUITF.width); break; } case Layout.HorizontalDirection.RIGHT_TO_LEFT: { itemX += ((1 - this._itemTmp.getComponent(UITransform).anchorX) * this._itemSize.width); itemX -= ((1 - this.contentUITF.anchorX) * this.contentUITF.width); itemX *= -1; break; } } return { id: id, top: top, bottom: bottom, x: itemX, y: itemY, }; } case Layout.AxisDirection.VERTICAL: { switch (this._horizontalDir) { case Layout.HorizontalDirection.LEFT_TO_RIGHT: { left = this._leftGap + ((this._itemSize.width + this._columnGap) * colLine); right = left + this._itemSize.width; itemX = left + (this._itemTmp.getComponent(UITransform).anchorX * this._itemSize.width); itemX -= (this.contentUITF.anchorX * this.contentUITF.width); break; } case Layout.HorizontalDirection.RIGHT_TO_LEFT: { right = -this._rightGap - ((this._itemSize.width + this._columnGap) * colLine); left = right - this._itemSize.width; itemX = left + (this._itemTmp.getComponent(UITransform).anchorX * this._itemSize.width); itemX += ((1 - this.contentUITF.anchorX) * this.contentUITF.width); break; } } itemY = -this._topGap - ((id % this._colLineNum) * (this._itemSize.height + this._lineGap)); switch (this._verticalDir) { case Layout.VerticalDirection.TOP_TO_BOTTOM: { itemY -= ((1 - this._itemTmp.getComponent(UITransform).anchorY) * this._itemSize.height); itemY += ((1 - this.contentUITF.anchorY) * this.contentUITF.height); break; } case Layout.VerticalDirection.BOTTOM_TO_TOP: { itemY -= ((this._itemTmp.getComponent(UITransform).anchorY) * this._itemSize.height); itemY += (this.contentUITF.anchorY * this.contentUITF.height); itemY *= -1; break; } } return { id: id, left: left, right: right, x: itemX, y: itemY, }; } } break; } } } //计算已存在的Item的位置 _calcExistItemPos(id: number) { let item: Node = this.getItemByListId(id); if (!item) return null; let data: any = { id: id, x: item.position.x, y: item.position.y, }; let uitf = item.getComponent(UITransform) if (this._sizeType) { data.top = item.position.y + (uitf.height * (1 - uitf.anchorY)); data.bottom = item.position.y - (uitf.height * uitf.anchorY); } else { data.left = item.position.x - (uitf.width * uitf.anchorX); data.right = item.position.x + (uitf.width * (1 - uitf.anchorX)); } return data; } //获取Item位置 getItemPos(id: number) { return this._calcItemPos(id); } //获取固定尺寸 _getFixedSize(listId: number) { if (!this._customSize) return null; if (listId == null) listId = this._numItems; let fixed: number = 0; let count: number = 0; for (let id in this._customSize) { if (parseInt(id) < listId) { fixed += this._customSize[id]; count++; } } return { val: fixed, count: count, }; } //滚动开始时.. private canRefresh: boolean = false; _onScrollBegan() { // Utils.Log("滚动容器 _onScrollBegan") this.canRefresh = false; this._beganPos = this._sizeType ? this.viewTop : this.viewLeft; } //滚动进行时... _onScrolling(ev: Event = null) { // Utils.Log("滚动容器 _onScrolling") if (!this._inited) return if (this.frameCount == null) this.frameCount = 0; if (!this._forceUpdate && (ev && ev.type != ScrollView.EventType.SCROLL_ENDED) && this.frameCount > 0) { this.frameCount--; return; } else{ this.frameCount = 0; } if (this._aniDelRuning) return; let scorv = this.node.getComponent(ScrollView) let maxPos = scorv.getMaxScrollOffset(); let curPos = scorv.getScrollOffset(); if (scorv.vertical) { if (curPos.y - maxPos.y > 100) { this.canRefresh = true; } } else if (scorv.horizontal) { if (curPos.x - maxPos.x > 100) { this.canRefresh = true; } } 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: number = 0; let endId: number = this._numItems - 1; if (this._customSize) { let breakFor: boolean = false; //如果该item的位置在可视区域内,就推入displayData for (; curId <= endId && !breakFor; curId++) { itemPos = this._calcItemPos(curId); switch (this._align) { case 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 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 Layout.Type.GRID: switch (this._startAxis) { case 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 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 { if (!this._itemSize) { return; } let ww: number = this._itemSize.width + this._columnGap; let 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._rightGap) / ww; break; case 2://单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT) curId = (-vRight - this._rightGap) / ww; endId = (-vLeft - this._leftGap) / ww; break; case 3://单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM) curId = (-vTop - this._topGap) / hh; endId = (-vBottom - this._bottomGap) / hh; break; case 4://单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP) curId = (vBottom + this._bottomGap) / hh; endId = (vTop + this._topGap) / 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; let len: number = this._lastDisplayData.length; let haveDataChange: boolean = this.displayItemNum != len; if (haveDataChange) { // 因List的显示数据是有序的,所以只需要判断数组长度是否相等,以及头、尾两个元素是否相等即可。 haveDataChange = this.firstListId != this._lastDisplayData[0] || this.displayData[this.displayItemNum - 1].id != this._lastDisplayData[len - 1]; } if (this._forceUpdate || haveDataChange) { //如果是强制更新 //直接渲染 this._lastDisplayData = []; // //hg_utils.hgLog('List Display Data II::', this.displayData); for (let c = 0; c < this.displayItemNum; c++) { this._createOrUpdateItem(this.displayData[c]); } this._forceUpdate = false; } this._calcNearestItem(); } } //滚动结束时.. _onScrollEnded() { // Utils.Log("滚动容器 _onScrollEnded") if (this.scrollToListId != null) { let item: any = this.getItemByListId(this.scrollToListId); this.scrollToListId = null; if (item) { // item.runAction(cc.sequence( // cc.scaleTo(.1, 1.06), // cc.scaleTo(.1, 1), // )); tween(item) .to(0.1, {scale: v3(1.06, 1.06, 1.06)}) .to(0.1, {scale: v3(1, 1, 1)}) .start() } } this._onScrolling(); if (this.canRefresh) { this.canRefresh = false; Component.EventHandler.emitEvents([this.renderEventUpData]); } } // 触摸时 _onTouchStart(ev, captureListeners) { // Utils.Log("滚动容器 _onTouchStart") this.canRefresh = false; let isMe = ev.eventPhase === 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() { // Utils.Log("滚动容器 _onTouchUp") this._scrollPos = null; this._scrollItem = null; } _onTouchCancelled(ev, captureListeners) { // Utils.Log("滚动容器 _onTouchCancelled") this._scrollPos = null; this._scrollItem = null; } //当尺寸改变 _onSizeChanged() { // Utils.Log("滚动容器 _onSizeChanged") if (this.checkInited(false)) this._onScrolling(); } //当Item自适应 _onItemAdaptive(item) { if (this.checkInited(false)) { let uitf = item.getComponent(UITransform) if ( (!this._sizeType && uitf.width != this._itemSize.width) || (this._sizeType && uitf.height != this._itemSize.height) ) { if (!this._customSize) this._customSize = {}; let val = this._sizeType ? uitf.height : uitf.width; if (this._customSize[item._listId] != val) { this._customSize[item._listId] = val; this._resizeContent(); this.updateAll(); // 如果当前正在运行 scrollTo,肯定会不准确,在这里做修正 if (!isNaN(this._scrollToListId)) { this._scrollPos = null; this.unschedule(this._scrollToSo); this.scrollTo(this._scrollToListId, Math.max(0, this._scrollToEndTime - ((new Date()).getTime() / 1000))); } } } } } //Update.. update() { } /** * 创建或更新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(); } else { item = instantiate(this._itemTmp); } if (item && item._listId != data.id) { item._listId = data.id; item.getComponent(UITransform).setContentSize(this._itemSize); //命名 if (!isNaN(data.id)){ item.name = `svCell_${data.id}` } } item.setPosition(v3(data.x, data.y)); this._resetItemSize(item); this.content.addChild(item); if (canGet && this._needUpdateWidget) { let widget: Widget = item.getComponent(Widget); if (widget) widget.updateAlignment(); } item.setSiblingIndex(this.content.children.length - 1); let listItem: ScrollviewListItem = item.getComponent(ScrollviewListItem); item['listItem'] = listItem; if (listItem) { listItem.listId = data.id; listItem.list = this; listItem._registerEvent(); } if (this.renderEvent && item) { EventHandler.emitEvents([this.renderEvent], item, data.id % this._actualNumItems); } } else if (this._forceUpdate && this.renderEvent) { //强制更新 item.setPosition(v3(data.x, data.y)); this._resetItemSize(item); if (this.renderEvent && item) { EventHandler.emitEvents([this.renderEvent], item, data.id % this._actualNumItems); } } this._resetItemSize(item); 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: ScrollviewListItem; if (!item) { //如果不存在 item = instantiate(this._itemTmp); item._listId = listId; this.content.addChild(item); listItem = item.getComponent(ScrollviewListItem); item['listItem'] = listItem; if (listItem) { listItem.listId = listId; listItem.list = this; listItem._registerEvent(); } if (this.renderEvent && item) { Component.EventHandler.emitEvents([this.renderEvent], item, listId); } } else if (this._forceUpdate && this.renderEvent) { //强制更新 item._listId = listId; if (listItem) listItem.listId = listId; if (this.renderEvent && item) { Component.EventHandler.emitEvents([this.renderEvent], item, listId); } } if (this._lastDisplayData.indexOf(listId) < 0) { this._lastDisplayData.push(listId); } } //仅虚拟列表用 _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.getComponent(UITransform).setContentSize(this._itemSize); else size = this._sizeType ? this._itemSize.height : this._itemSize.width; } if (size) { let uitf = item.getComponent(UITransform) if (this._sizeType) uitf.height = size; else uitf.width = size; } } /** * 更新Item位置 * @param {Number||Node} listIdOrItem */ _updateItemPos(listIdOrItem: any) { let item: any = isNaN(listIdOrItem) ? listIdOrItem : this.getItemByListId(listIdOrItem); let 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) { if (!this.checkInited()) return; if (!Array.isArray(args)) { args = [args]; } if (bool == null) { this.multSelected = args; } else { let listId: number, sub: number; if (bool) { for (let n: number = args.length - 1; n >= 0; n--) { listId = args[n]; sub = this.multSelected.indexOf(listId); if (sub < 0) { this.multSelected.push(listId); } } } else { for (let n: number = args.length - 1; n >= 0; n--) { listId = args[n]; sub = this.multSelected.indexOf(listId); if (sub >= 0) { this.multSelected.splice(sub, 1); } } } } this._forceUpdate = true; this._onScrolling(); } /** * 更新指定的Item * @param {Array} args 单个listId,或者数组 * @returns */ updateItem(args: any) { if (!this.checkInited()) return; if (!Array.isArray(args)) { args = [args]; } for (let n: number = 0, len: number = args.length; n < len; n++) { let listId: number = args[n]; let item: any = this.getItemByListId(listId); if (item) 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 == null) return null; for (let n: number = this.content.children.length - 1; n >= 0; n--) { let item: any = this.content.children[n]; if (item._listId == listId) return item; } } /** * 获取在显示区域外的Item * @returns */ _getOutsideItem() { let item: any; let result: any[] = []; for (let n: number = this.content.children.length - 1; n >= 0; n--) { item = this.content.children[n]; let isHas: boolean = false; for (let idx in this.displayData) { if (this.displayData[idx].id == item._listId) { isHas = true; continue; } } if (!isHas) { result.push(item); } } return result; } //删除显示区域以外的Item _delRedundantItem() { if (this._virtual) { let arr: any[] = this._getOutsideItem(); for (let n: number = arr.length - 1; n >= 0; n--) { let item: any = arr[n]; if (this._scrollItem && item._listId == this._scrollItem._listId) continue; 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; } } } } else { while (this.content.children.length > this._numItems) { this._delSingleItem(this.content.children[this.content.children.length - 1]); } } } //删除单个Item _delSingleItem(item: any) { item.removeFromParent(); if (item.destroy) item.destroy(); item = null; } /** * 动效删除Item(此方法只适用于虚拟列表,即_virtual=true) * 一定要在回调函数里重新设置新的numItems进行刷新,毕竟本List是靠数据驱动的。 */ aniDelItem(listId: number, callFunc: Function, aniType: number) { if (!this.checkInited() || !this._virtual) return //hg_utils.hgLog('This function is not allowed to be called!'); if (this._aniDelRuning) return //hg_utils.hgLog('Please wait for the current deletion to finish!'); let item: any = this.getItemByListId(listId); let listItem: ScrollviewListItem; if (!item) { callFunc(listId); return; } else { listItem = item.getComponent(ScrollviewListItem); } this._aniDelRuning = true; let curLastId: number = this.displayData[this.displayData.length - 1].id; let resetSelectedId: boolean = listItem.selected; listItem.showAni(aniType, () => { //判断有没有下一个,如果有的话,创建粗来 let newId: number; if (curLastId < this._numItems - 2) { newId = curLastId + 1; } if (newId != null) { let newData: any = this._calcItemPos(newId); this.displayData.push(newData); if (this._virtual) this._createOrUpdateItem(newData); else this._createOrUpdateItem2(newId); } else{ this._numItems--; } if (this._customSize) { if (this._customSize[listId]) delete this._customSize[listId]; let newCustomSize: any = {}; let size: number; for (let id in this._customSize) { size = this._customSize[id]; let idNumber: number = parseInt(id); newCustomSize[idNumber - (idNumber >= listId ? 1 : 0)] = size; } this._customSize = newCustomSize; } //后面的Item向前怼的动效 let sec: number = .2333; let acts: any[], haveCB: boolean; for (let n: number = newId != null ? newId : curLastId; n >= listId + 1; n--) { item = this.getItemByListId(n); if (item) { let posData: any = this._calcItemPos(n - 1); let a_num = 0 let act = tween(item); act.to(sec, {position: v3(posData.x, posData.y)}) a_num += 1 // acts = [ // cc.moveTo(sec, cc.v2(posData.x, posData.y)), // ]; if (n <= listId + 1) { haveCB = true; act.call(()=>{ this._aniDelRuning = false; callFunc(listId); }) a_num += 1 // acts.push(cc.callFunc(() => { // this._aniDelRuning = false; // callFunc(listId); // })); } if (a_num > 0) act.start() } } if (!haveCB) { this._aniDelRuning = false; callFunc(listId); } }, true); } /** * 滚动到.. * @param {Number} listId 索引(如果<0,则滚到首个Item位置,如果>=_numItems,则滚到最末Item位置) * @param {Number} timeInSecond 时间 * @param {Number} offset 索引目标位置偏移,0-1 * @param {Boolean} overStress 滚动后是否强调该Item(这只是个实验功能) */ scrollTo(listId: number, timeInSecond: number = .5, offset: number = null, overStress: boolean = false) { if (!this.checkInited(false)) return; if (timeInSecond == null) //默认0.5 timeInSecond = .5; else if (timeInSecond < 0) timeInSecond = 0; if (listId < 0) listId = 0; else if (listId >= this._numItems) listId = this._numItems - 1; // 以防设置了numItems之后layout的尺寸还未更新 if (!this._virtual && this._layout && this._layout.enabled) this._layout.updateLayout(); let pos: any = this.getItemPos(listId); let targetX: number, targetY: number; switch (this._alignCalcType) { case 1://单行HORIZONTAL(LEFT_TO_RIGHT)、网格VERTICAL(LEFT_TO_RIGHT) targetX = pos.left; if (offset != null) targetX -= this._uitf.width * offset; else targetX -= this._leftGap; pos = v2(targetX, 0); break; case 2://单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT) targetX = pos.right - this._uitf.width; if (offset != null) targetX += this._uitf.width * offset; else targetX += this._rightGap; pos = v2(targetX + this.contentUITF.width, 0); break; case 3://单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM) targetY = pos.top; if (offset != null) targetY += this._uitf.height * offset; else targetY += this._topGap; pos = v2(0, -targetY); break; case 4://单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP) targetY = pos.bottom + this._uitf.height; if (offset != null) targetY -= this._uitf.height * offset; else targetY -= this._bottomGap; pos = v2(0, -targetY + this.contentUITF.height); break; } let viewPos: any = this.content.getPosition(); viewPos = Math.abs(this._sizeType ? viewPos.y : viewPos.x); let comparePos = this._sizeType ? pos.y : pos.x; let runScroll = Math.abs((this._scrollPos != null ? this._scrollPos : viewPos) - comparePos) > .5; if (runScroll) { this._scrollView.scrollToOffset(pos, timeInSecond); this._scrollToListId = listId; this._scrollToEndTime = ((new Date()).getTime() / 1000) + timeInSecond; this._scrollToSo = this.scheduleOnce(() => { if (!this._adheringBarrier) { this.adhering = this._adheringBarrier = false; } this._scrollPos = this._scrollToListId = this._scrollToEndTime = this._scrollToSo = null; if (overStress) { let item = this.getItemByListId(listId); if (item) { tween(item) .to(0.1, {scale: v3(1.05, 1.05, 1.05)}) .to(0.1, {scale: v3(1, 1, 1)}) .start() } } }, timeInSecond + .1); if (timeInSecond <= 0) { this._onScrolling(); } } } /** * 计算当前滚动窗最近的Item */ _calcNearestItem() { this.nearestListId = null; let data: any, center: number; if (this._virtual) this._calcViewPos(); let vTop: number, vRight: number, vBottom: number, vLeft: number; vTop = this.viewTop; vRight = this.viewRight; vBottom = this.viewBottom; vLeft = this.viewLeft; let breakFor: boolean = false; for (let n = 0; n < this.content.children.length && !breakFor; n += this._colLineNum) { data = this._virtual ? this.displayData[n] : this._calcExistItemPos(n); if (!data) break; center = this._sizeType ? ((data.top + data.bottom) / 2) : (center = (data.left + data.right) / 2); switch (this._alignCalcType) { case 1://单行HORIZONTAL(LEFT_TO_RIGHT)、网格VERTICAL(LEFT_TO_RIGHT) if (data.right >= vLeft) { this.nearestListId = data.id; if (vLeft > center) this.nearestListId += this._colLineNum; breakFor = true; } break; case 2://单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT) if (data.left <= vRight) { this.nearestListId = data.id; if (vRight < center) this.nearestListId += this._colLineNum; breakFor = true; } break; case 3://单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM) if (data.bottom <= vTop) { this.nearestListId = data.id; if (vTop < center) this.nearestListId += this._colLineNum; breakFor = true; } break; case 4://单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP) if (data.top >= vBottom) { this.nearestListId = data.id; if (vBottom > center) this.nearestListId += this._colLineNum; breakFor = true; } break; } } //判断最后一个Item。。。(哎,这些判断真心恶心,判断了前面的还要判断最后一个。。。一开始呢,就只有一个布局(单列布局),那时候代码才三百行,后来就想着完善啊,艹..这坑真深,现在这行数都一千五了= =||) data = this._virtual ? this.displayData[this.displayItemNum - 1] : this._calcExistItemPos(this._numItems - 1); if (data && data.id == this._numItems - 1) { center = this._sizeType ? ((data.top + data.bottom) / 2) : (center = (data.left + data.right) / 2); switch (this._alignCalcType) { case 1://单行HORIZONTAL(LEFT_TO_RIGHT)、网格VERTICAL(LEFT_TO_RIGHT) if (vRight > center) this.nearestListId = data.id; break; case 2://单行HORIZONTAL(RIGHT_TO_LEFT)、网格VERTICAL(RIGHT_TO_LEFT) if (vLeft < center) this.nearestListId = data.id; break; case 3://单列VERTICAL(TOP_TO_BOTTOM)、网格HORIZONTAL(TOP_TO_BOTTOM) if (vBottom < center) this.nearestListId = data.id; break; case 4://单列VERTICAL(BOTTOM_TO_TOP)、网格HORIZONTAL(BOTTOM_TO_TOP) if (vTop > center) this.nearestListId = data.id; break; } } } //计算 CustomSize(这个函数还是保留吧,某些罕见的情况的确还是需要手动计算customSize的) calcCustomSize(numItems: number) { if (!this.checkInited()) return; if (!this._itemTmp) return //hg_utils.hgLog('Unset template item!'); if (!this.renderEvent) return //hg_utils.hgLog('Unset Render-Event!'); this._customSize = {}; let temp: any = instantiate(this._itemTmp); this.content.addChild(temp); let uitf = temp.getComponent(UITransform) for (let n: number = 0; n < numItems; n++) { Component.EventHandler.emitEvents([this.renderEvent], temp, n); if (uitf.height != this._itemSize.height || uitf.width != this._itemSize.width) { this._customSize[n] = this._sizeType ? uitf.height : uitf.width; } } if (!Object.keys(this._customSize).length) this._customSize = null; temp.removeFromParent(); if (temp.destroy) temp.destroy(); return this._customSize; } }