/** * @Author wutianhao * @ctime 2021-07-13 * @Version * ScrollBeta1.0 2021-07-13 ScrollFinal改版 支持带标题的滚动列表 具体用法可参照天赋技能预览界面 * @Tips * 复用滚动轴 用来减少drawcall * 与cc.ScrollView组件一同挂载在一个节点上 * item挂载的脚本必须添加setData方法,用来传递数据 * item锚点应该在中心 * 目前Grid类型只支持从左上到右下模式(垂直滚动),其他奇葩模式自己搞定 * 滚动轴的锚点必须放在滚动列表的起始位置(比如背包grid模式在左上角,成就列表在左上角) */ import AssetMgr from "./AssetMgr"; // 对象池类型 let PoolEnum = cc.Enum({ /**通用道具 */ ITEM_BASE: 0, /**背包道具 */ ITEM_BAG: 1, // /**合成道具 */ // ITEM_COMPOSE: 2, }) // 滚动类型 let ScrollDirEnum = cc.Enum({ /**垂直*/ VERTICAL: 0, /**水平*/ HORIZONTAL: 1, /**背包*/ GRID: 2 }) type ItemList = { index: number, node: cc.Node } const { ccclass, property, menu } = cc._decorator; @ccclass @menu('Scroll/ScrollBeta') export default class ScrollBeta extends cc.Component { // @property(cc.ScrollView) // scroll: cc.ScrollView | null = null @property({ tooltip: "使用对象池" }) useNodePool: boolean = false @property({ type: PoolEnum, visible: function () { return this.useNodePool }, tooltip: "对象池类型" }) private poolType = PoolEnum.ITEM_BASE @property({ type: ScrollDirEnum, visible: () => { return true }, tooltip: "滚动类型" }) private scrollDir = ScrollDirEnum.VERTICAL // 滚动类型 @property({ tooltip: "与滚动层的边界-左" }) private padingX: number = 10 @property({ tooltip: "与滚动层的边界-上" }) private padingY: number = 10 @property({ tooltip: "与滚动层的边界-下" }) private padingY2: number = 0 @property({ visible: function () { return this.scrollDir != ScrollDirEnum.VERTICAL }, tooltip: "item行间距" }) private spacingX: number = 20 @property({ visible: function () { return this.scrollDir != ScrollDirEnum.HORIZONTAL }, tooltip: "item列间距" }) private spacingY: number = 20 @property(cc.Prefab) itemPrefab: cc.Prefab = null // item资源加载地址 @property(cc.Prefab) itemPrefabTitle: cc.Prefab = null // item标题资源加载地址 @property itemScript: string = ""// item挂在的脚本名 @property itemScriptTitle: string = ""// item标题挂在的脚本名 @property itemScale: number = 1 // item缩放比例 @property({ tooltip: "是否开启滚动惯性" }) inertia: boolean = true @property({ tooltip: "开启惯性后,在用户停止触摸后滚动多块停止,0表示永不停止,1表示立即停止", visible: function () { return this.inertia == true }, }) brake: number = 0.75 @property({ tooltip: "是否允许滚动内容超过边界,并在停止触摸后回弹" }) elastic: boolean = true @property({ tooltip: "滚动行为是否会取消子节点上注册的触摸事件", }) cancelInnerEvents: boolean = true @property({ tooltip: "展示生产动画\n0->不展示\n1->缩放动画" }) showAnim: number = 1 @property({ tooltip: "创建item的延迟时间,设为0则为每帧生成。\n注意:在所有item刷新完之前,scroll组件的滚动功能将被关闭" }) ctime: number = 0 @property({ tooltip: "每帧生成item的个数" }) cnumber: number = 1 scrollView: cc.ScrollView mask: cc.Node content: cc.Node itemDataList: any[] = [] // 当前显示阵营的所有数据 itemDataListMain: any[] = [] // 当前显示主数据的所有数据 itemDataListTitle: any[] = [] // 当前显示标题的所有数据 private extraParams: any[] = [] // 额外数据 private itemListMain: ItemList[] = [] // 实例化的item列表 private itemListTitle: ItemList[] = [] // 实例化的item标题列表 private instantiateCount: number = 0 // item实例化数量 private instantiateCountTitle: number = 0 // item标题实例化数量 private hangCount: number = 0 // 行个数 private lieCount: number = 0 // 列个数 private itemDistanceX: number = 0 // item中心点之间的距离 private itemDistanceXTitle: number = 0 // item标题中心点之间的距离 private itemDistanceY: number = 0 // item中心点之间的距离 private itemDistanceYTitle: number = 0 // item标题中心点之间的距离 private scrollMaxOffsetX: number = 0 // 最大可滚动区域X private scrollMaxOffsetY: number = 0 // 最大可滚动区域Y private lastScrollPos: number = 0 //上一次的滚动位置 private curScrollPos: number = 0 // 当前滚动位置 private isScrollUp: boolean = false // 当前往哪个方向滚动 左和上是true private itemWidth: number = 10 // item宽度 private itemWidthTitle: number = 10 // item标题宽度 private itemHeight: number = 10 // item高度 private itemHeightTitle: number = 10 // item标题高度 private tagLang: number = 0 private tagIndex: number = -999 // 999 表示清0状态,此时无【插入标签】,-1表示标签置顶,其他即当前标签的下方(右侧)显示 private canCreateItem: boolean = false // 可以生成item private createIndex: number = 0 // 生成item的数据标签 private life: number = 0 // 生成item的时间 private baseIndex: number = 0 // 基础标签位置 private hasInit: boolean = false offset: number = 0 // 偏移量 onLoad() { if (this.node.getComponent(cc.ScrollView) != null) { console.error("滚动节点无需挂载scrollView组件了") return } /////////////// 构建滚动轴 /////////////// this.scrollView = this.addComponent(cc.ScrollView) this.scrollView.horizontal = this.scrollDir == ScrollDirEnum.HORIZONTAL this.scrollView.vertical = this.scrollDir != ScrollDirEnum.HORIZONTAL this.scrollView.inertia = this.inertia this.scrollView.brake = this.brake this.scrollView.elastic = this.elastic this.scrollView.cancelInnerEvents = this.cancelInnerEvents /////////////// 构建滚动遮罩 /////////////// this.mask = new cc.Node() this.mask.parent = this.node this.mask.setContentSize(this.node.getContentSize()) this.mask.addComponent(cc.Mask) this.mask.getComponent(cc.Mask).type = cc.Mask.Type.RECT this.mask.anchorX = this.node.anchorX this.mask.anchorY = this.node.anchorY this.mask.x = 0 this.mask.y = 0 // widget不需要加了 // let maskWidget = mask.addComponent(cc.Widget) // maskWidget.isAlignTop = true // maskWidget.isAlignBottom = true // maskWidget.isAlignLeft = true // maskWidget.isAlignRight = true // maskWidget.top = 0 // maskWidget.bottom = 0 // maskWidget.left = 0 // maskWidget.right = 0 /////////////// 构建滚动内容器 /////////////// this.content = new cc.Node() this.content.parent = this.mask this.scrollView.content = this.content this.content.setContentSize(this.node.getContentSize()) this.content.anchorX = this.node.anchorX this.content.anchorY = this.node.anchorY this.content.x = 0 this.content.y = 0 } resetSize() { if (this.mask) { this.mask.setContentSize(this.node.getContentSize()) } if (this.content) { this.content.setContentSize(this.node.getContentSize()) } } private init() { if (this.hasInit) { return } this.hasInit = true this.itemWidth = this.itemPrefab.data.getContentSize().width this.itemHeight = this.itemPrefab.data.getContentSize().height this.itemWidthTitle = this.itemPrefabTitle.data.getContentSize().width this.itemHeightTitle = this.itemPrefabTitle.data.getContentSize().height this.itemDistanceX = this.realItemWidth + this.spacingX this.itemDistanceY = this.realItemHeight + this.spacingY this.itemDistanceXTitle = this.itemWidthTitle + this.spacingX this.itemDistanceYTitle = this.itemHeightTitle + this.spacingY this.scrollView.node.on('scrolling', this.onScroll, this) //看下有没有不应该有的组件 // Layout if (this.scrollView.content.getComponent(cc.Layout)) { console.error("scrollFinal 与 layout 冲突,清删除 content 中的 layout 组件") } // Widget if (this.scrollView.node.getComponent(cc.Widget)) { if (this.scrollView.node.getComponent(cc.Widget).isAlignTop && this.scrollView.node.getComponent(cc.Widget).isAlignBottom) { console.error("不能用widget做长度适配(因为Widget的延迟),只可用作坐标适配") } } } private setInstantCount() { if (this.scrollView == null) { return } switch (this.scrollDir) { case ScrollDirEnum.VERTICAL: this.hangCount = 1 this.lieCount = Math.ceil(this.scrollView.node.height / (this.itemDistanceY)) + 1 this.instantiateCount = this.lieCount break case ScrollDirEnum.HORIZONTAL: this.hangCount = Math.ceil(this.scrollView.node.width / (this.itemDistanceX)) + 1 this.lieCount = 1 this.instantiateCount = this.hangCount break case ScrollDirEnum.GRID: this.hangCount = Math.floor(this.scrollView.node.width / (this.itemDistanceX)) this.lieCount = Math.ceil(this.scrollView.node.height / (this.itemDistanceY)) + 1 this.instantiateCount = this.hangCount * this.lieCount break } this.instantiateCountTitle = this.itemDataListTitle.length } indexFrom: { [index: number]: number } = {} initScrollView(itemDataList: any[] = [], ...args: any[]) { // this.scheduleOnce(() => { this.init() this.clear() this.clearTag() this.itemDataList = itemDataList this.refreshData() this.extraParams = args this.scrollView.stopAutoScroll() this.setInstantCount() this.showUI() // }, 0) } refreshData() { this.itemDataListMain = [] this.itemDataListTitle = [] this.indexFrom = {} let count = 0 this.itemDataList.forEach((element, index) => { element[0].forEach(_element => { this.itemDataListMain.push(_element) }); this.itemDataListTitle.push(element[1]) count += element[0].length this.indexFrom[index] = count }); } private getPositionInView(item: cc.Node): { x: number, y: number } { if (this.scrollView == null) { return cc.v2(0, 0) } let worldPos = item.parent.convertToWorldSpaceAR(item.position); let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos); return viewPos; } private onScroll() { this.curScrollPos = 0 if (this.scrollDir == ScrollDirEnum.HORIZONTAL) { this.curScrollPos = this.scrollView.getScrollOffset().x this.isScrollUp = this.curScrollPos < this.lastScrollPos } else { this.curScrollPos = this.scrollView.getScrollOffset().y this.isScrollUp = this.curScrollPos > this.lastScrollPos } this.lastScrollPos = this.curScrollPos if (this.scrollView == null) { return } this.itemListMain.forEach(ele => { this.tryResetItem(ele) }) // this.itemListTitle.forEach(ele => { // this.tryResetItemTitle(ele) // }) } private tryResetItem(ele: ItemList) { switch (this.scrollDir) { case ScrollDirEnum.HORIZONTAL: if (this.curScrollPos >= 0 || this.curScrollPos <= this.scrollMaxOffsetX) { return } break case ScrollDirEnum.VERTICAL: case ScrollDirEnum.GRID: if (this.curScrollPos <= -this.realItemHeight / 2 || this.curScrollPos >= this.scrollMaxOffsetY + this.realItemHeight / 2) { return } } let scrollWidth = this.scrollView.node.width let scrollHeight = this.scrollView.node.height let element = ele.node switch (this.scrollDir) { case ScrollDirEnum.HORIZONTAL: if (this.isScrollUp && this.getPositionInView(element).x < -(this.itemDistanceX / 2)) { // 超出左边界显示区域 let idx = ele.index + this.instantiateCount if (idx < this.itemDataListMain.length) { this.setItemData(element, this.itemDataListMain[idx], idx) // element.x = element.x + this.hangCount * this.itemDistanceX ele.index = idx this.setPosX(idx, element) } } else if (!this.isScrollUp && this.getPositionInView(element).x > (scrollWidth + this.itemDistanceX / 2)) { // 超出右边界显示区域 let idx = ele.index - this.instantiateCount if (idx >= 0) { this.setItemData(element, this.itemDataListMain[idx], idx) // element.x = element.x - this.hangCount * this.itemDistanceX ele.index = idx this.setPosX(idx, element) } } break case ScrollDirEnum.VERTICAL: case ScrollDirEnum.GRID: if (this.isScrollUp && this.getPositionInView(element).y > this.itemDistanceY / 2) { // 超出上边界显示区域 let idx = ele.index + this.instantiateCount if (idx < this.itemDataListMain.length && this.isScrollUp) { this.setItemData(element, this.itemDataListMain[idx], idx) ele.index = idx this.setPosX(idx, element) this.setPosY(idx, element) } } else if (!this.isScrollUp && this.curScrollPos > -this.itemDistanceY / 2 && this.getPositionInView(element).y < -(scrollHeight + this.itemDistanceY / 2)) { // 超出下边界显示区域 let idx = ele.index - this.instantiateCount if (idx >= 0 && this.isScrollUp == false) { this.setItemData(element, this.itemDataListMain[idx], idx) ele.index = idx this.setPosX(idx, element) this.setPosY(idx, element) } } break } } private tryResetItemTitle(ele: ItemList) { switch (this.scrollDir) { case ScrollDirEnum.HORIZONTAL: if (this.curScrollPos >= 0 || this.curScrollPos <= this.scrollMaxOffsetX) { return } break case ScrollDirEnum.VERTICAL: case ScrollDirEnum.GRID: if (this.curScrollPos <= -this.itemHeightTitle / 2 || this.curScrollPos >= this.scrollMaxOffsetY + this.itemHeightTitle / 2) { return } } let scrollWidth = this.scrollView.node.width let scrollHeight = this.scrollView.node.height let element = ele.node switch (this.scrollDir) { case ScrollDirEnum.HORIZONTAL: if (this.isScrollUp && this.getPositionInView(element).x < -(this.itemDistanceXTitle / 2)) { // 超出左边界显示区域 let idx = ele.index + this.instantiateCountTitle if (idx < this.itemDataListTitle.length) { this.addItemNodeTitle(idx, this.itemDataListTitle[idx]) } } else if (!this.isScrollUp && this.getPositionInView(element).x > (scrollWidth + this.itemDistanceX / 2)) { // 超出右边界显示区域 let idx = ele.index - this.instantiateCountTitle if (idx >= 0) { this.addItemNodeTitle(idx, this.itemDataListTitle[idx]) } } break case ScrollDirEnum.VERTICAL: case ScrollDirEnum.GRID: if (this.isScrollUp && this.getPositionInView(element).y > this.itemDistanceYTitle / 2) { // 超出上边界显示区域 let idx = ele.index + this.instantiateCountTitle if (idx < this.itemDataListTitle.length && this.isScrollUp) { this.addItemNodeTitle(idx, this.itemDataListTitle[idx]) } } else if (!this.isScrollUp && this.curScrollPos > -this.itemDistanceYTitle / 2 && this.getPositionInView(element).y < -(scrollHeight + this.itemDistanceYTitle / 2)) { // 超出下边界显示区域 let idx = ele.index - this.instantiateCountTitle if (idx >= 0 && this.isScrollUp == false) { this.addItemNodeTitle(idx, this.itemDataListTitle[idx]) } } break } } checkIndexFromItem(index: number): number { let _index = 0 for (let i = 0; i < index; i++) { const element = this.itemDataList[i][0] _index += element.length } return _index } checkIndexFromTitle(index: number): number { for (let i = 0; i < this.itemDataList.length; i++) { if (index < this.indexFrom[i]) { return i } } } checkAddTitle(index: number): boolean { let from = this.checkIndexFromTitle(index) if (from > 0) { return index == this.indexFrom[from - 1] } else { return index == 0 } } private setPosX(index: number, node?: cc.Node): number { let x = 0 switch (this.scrollDir) { case ScrollDirEnum.VERTICAL: x = this.scrollView.content.width / 2 + this.padingX//this.realItemWidth / 2 + this.padingX break case ScrollDirEnum.HORIZONTAL: x = index * this.itemDistanceX + this.realItemWidth / 2 + this.padingX + ((this.checkIndexFromTitle(index) + 1) * this.itemDistanceXTitle) if (this.tagIndex >= -1 && index > this.tagIndex) { x += this.tagLang } break case ScrollDirEnum.GRID: let _index = this.checkIndexFromTitle(index) let count = _index > 0 ? this.indexFrom[_index - 1] : 0 x = (index - count) % this.hangCount * this.itemDistanceX + this.realItemWidth / 2 + this.padingX break } if (node) { node.x = x } return x } private setPosY(index: number, node?: cc.Node): number { let y = 0 switch (this.scrollDir) { case ScrollDirEnum.VERTICAL: y = -index * this.itemDistanceY - this.realItemHeight / 2 - this.padingY - ((this.checkIndexFromTitle(index) + 1) * this.itemDistanceYTitle) + this.offset if (this.tagIndex >= -1 && index > this.tagIndex) { y -= this.tagLang } break case ScrollDirEnum.HORIZONTAL: y = -this.scrollView.content.height / 2 + this.padingY//-this.realItemHeight / 2 - this.padingY break case ScrollDirEnum.GRID: let _index = this.checkIndexFromTitle(index) let extra = 0 if (_index > 0) { for (let i = 0; i < _index; i++) { extra += Math.ceil(this.itemDataList[i][0].length / this.hangCount) } } let count = _index > 0 ? this.indexFrom[_index - 1] : 0 y = -(Math.floor((index - count) / this.hangCount) + extra) * this.itemDistanceY - this.realItemHeight / 2 - this.padingY - ((this.checkIndexFromTitle(index) + 1) * this.itemDistanceYTitle) + this.offset if (this.tagIndex >= -1 && (Math.floor((index - count) / this.hangCount) + extra) > this.tagIndex) { y -= this.tagLang } break } if (node) { node.y = y } return y } private setPosXTitle(index: number, node?: cc.Node): number { let x = 0 switch (this.scrollDir) { case ScrollDirEnum.VERTICAL: x = this.scrollView.content.width / 2 + this.padingX//this.realItemWidth / 2 + this.padingX break case ScrollDirEnum.HORIZONTAL: x = this.checkIndexFromItem(index) * this.itemDistanceX + this.realItemWidth / 2 + this.padingX + ((index + 1) * this.itemDistanceXTitle) - this.realItemWidth / 2 - this.spacingX - this.itemWidthTitle / 2 if (this.tagIndex >= -1 && index > this.tagIndex) { x += this.tagLang } break case ScrollDirEnum.GRID: x = this.scrollView.content.width / 2 break } if (node) { node.x = x } return x } private setPosYTitle(index: number, node?: cc.Node): number { let y = 0 switch (this.scrollDir) { case ScrollDirEnum.VERTICAL: y = -this.checkIndexFromItem(index) * this.itemDistanceY - this.realItemHeight / 2 - this.padingY - ((index + 1) * this.itemDistanceYTitle) + this.realItemHeight / 2 + this.spacingY + this.itemHeightTitle / 2 break case ScrollDirEnum.HORIZONTAL: y = -this.scrollView.content.height / 2 + this.padingY//-this.realItemHeight / 2 - this.padingY break case ScrollDirEnum.GRID: let extra = 0 if (index > 0) { for (let i = 0; i < index; i++) { extra += Math.ceil(this.itemDataList[i][0].length / this.hangCount) } } let count = index > 0 ? this.indexFrom[index - 1] : 0 y = -(Math.floor((this.checkIndexFromItem(index) - count) / this.hangCount) + extra) * this.itemDistanceY - this.realItemHeight / 2 - this.padingY - ((index + 1) * this.itemDistanceYTitle) + this.realItemHeight / 2 + this.spacingY + this.itemHeightTitle / 2 if (this.tagIndex >= -1 && (Math.floor((index - count) / this.hangCount) + extra) > this.tagIndex) { y -= this.tagLang } break } if (node) { node.y = y } return y } private setItemData(itemNode: cc.Node, data: any, index: number, isRefresh: boolean = false) { try { itemNode.getComponent(this.itemScript).setData(data, index, this.extraParams) if (isRefresh == false) { let addTitle = this.checkAddTitle(index) if (addTitle) { this.addItemNodeTitle(this.checkIndexFromTitle(index), this.itemDataListTitle[this.checkIndexFromTitle(index)]) } } } catch (error) { console.error("脚本中缺少setData方法,或者方法报错", error) } } private setItemDataTitle(itemNode: cc.Node, data: any, index: number) { try { itemNode.getComponent(this.itemScriptTitle).setData(data, index, this.extraParams) } catch (error) { console.error("脚本中缺少setData方法,或者方法报错", error) } } // refreshItems调用,在刷新时可能需要重置item的index标签 private resetIndex(index: number): number { if (this.itemDataListMain[index] != null) { return index } return this.resetIndex(index - this.instantiateCount) } // 刷新单独的item refreshItem(index: number, data) { if (this.itemDataListMain[index] == null) { return } this.itemDataListMain[index] = data if (this.getItem(index) == null) { return } this.setItemData(this.getItem(index), this.itemDataListMain[index], index) } // refreshItem(index: number) { // this.setItemData(this.getItem(index), this.itemDataListMain[index], index) // } refreshItems(itemDataList: any[], ...args: any[]) { this.itemDataList = itemDataList this.refreshData() if (args.length > 0) { this.extraParams = args } this.fixItemNodes() // 最终构造完整的 itemList 列表,刷新数据 this.itemListMain.forEach(element => { try { let newIndex = this.resetIndex(element.index) element.index = newIndex this.setItemData(element.node, this.itemDataListMain[element.index], element.index) this.setPosX(element.index, element.node) this.setPosY(element.index, element.node) // element.node.getComponent(this.itemScript).setData(this.itemDataListMain[element.index], element.index, this.extraParams) } catch (error) { console.warn("脚本中缺少refreshItem方法,或者方法报错", error) } }) } // 数据新增或减少时,增加或减少item fixItemNodes() { // 判断是否需要删除 itemList 里的数据 if (this.itemDataListMain.length < this.instantiateCount && this.itemListMain.length > this.itemDataListMain.length) { let needDeleteCount = this.itemListMain.length - this.itemDataListMain.length for (let index = this.itemDataListMain.length; index < this.itemDataListMain.length + needDeleteCount; index++) { this.itemListMain[index].node.destroy() } this.itemListMain.splice(this.itemDataListMain.length, needDeleteCount) // 判断是否需要增加 itemList 里的数据 } else if (this.itemListMain.length < this.instantiateCount && this.itemListMain.length < this.itemDataListMain.length) { let addCount = Math.min(this.instantiateCount - this.itemListMain.length, this.itemDataListMain.length - this.itemListMain.length) let startIndex = 0 this.itemListMain.forEach(element => { startIndex = Math.max(element.index + 1, startIndex) }); for (let addIndex = startIndex; addIndex < (startIndex + addCount); addIndex++) { this.addItemNode(addIndex, this.itemDataListMain[addIndex], true) } } // 判断是否需要删除 itemList 里的数据 if (this.itemDataListTitle.length < this.instantiateCountTitle && this.itemListTitle.length > this.itemDataListTitle.length) { let needDeleteCount = this.itemListTitle.length - this.itemDataListTitle.length for (let index = this.itemDataListTitle.length; index < this.itemDataListTitle.length + needDeleteCount; index++) { this.itemListTitle[index].node.destroy() } this.itemListTitle.splice(this.itemDataListTitle.length, needDeleteCount) } this.instantiateCountTitle = this.itemDataListTitle.length if (this.content) { this.setScrollContentSize() } } private get realItemWidth(): number { return this.itemWidth * this.itemScale } private get realItemHeight(): number { return this.itemHeight * this.itemScale } private initItemNode(): cc.Node { return AssetMgr.instantiate(this.node, this.itemPrefab, false) // @TODO PoolManager // if (this.useNodePool) { // if (this.poolType == PoolEnum.ITEM_BAG) { // return PoolManager.getItemBag(this.itemPrefab) // } else if (this.poolType == PoolEnum.ITEM_BASE) { // return PoolManager.getItemBase(this.itemPrefab) // } // } else { // return cc.instantiate(this.itemPrefab) // } } private _initItemNode(): cc.Node { return AssetMgr.instantiate(this.node, this.itemPrefabTitle, false) // @TODO PoolManager // if (this.useNodePool) { // if (this.poolType == PoolEnum.ITEM_BAG) { // return PoolManager.getItemBag(this.itemPrefab) // } else if (this.poolType == PoolEnum.ITEM_BASE) { // return PoolManager.getItemBase(this.itemPrefab) // } // } else { // return cc.instantiate(this.itemPrefab) // } } private showUI() { if (this.itemPrefab == null || this.itemPrefabTitle == null) { console.error("item预制体加载失败") return } if (this.scrollView == null) { console.error("没有绑定scroll") return } this.setCreateItems(true) this.scrollView.content.setAnchorPoint(0, 1) this.scrollView.content.setPosition(-this.scrollView.node.width / 2, this.scrollView.node.height / 2) this.setScrollContentSize() switch (this.scrollDir) { case ScrollDirEnum.VERTICAL: this.scrollView.scrollToTop() break case ScrollDirEnum.HORIZONTAL: this.scrollView.scrollToLeft() break case ScrollDirEnum.GRID: this.scrollView.scrollToTop() break } } private setCreateItems(bool: boolean) { if (bool) { this.scrollView.enabled = false this.canCreateItem = true this.createIndex = 0 this.baseIndex = 0 this.life = 0 // this._canCreateItem = true // this._createIndex = 0 // this._life = 0 } else { this.scrollView.enabled = true this.canCreateItem = false this.createIndex = 0 this.baseIndex = 0 this.life = 0 // this._canCreateItem = false // this._createIndex = 0 // this._life = 0 } } update(dt: number) { if (!this.canCreateItem) { return } for (let index = 0; index < this.cnumber; index++) { this.updateForCreateItem(dt) } // if (!this._canCreateItem) { // return // } // for (let index = 0; index < this.cnumber; index++) { // this._updateForCreateItem(dt) // } } private updateForCreateItem(dt: number) { if (!this.canCreateItem) { return } if (this.life == 0) { // 生 let _itemData = this.itemDataListMain[this.baseIndex] if (this.createIndex >= this.instantiateCount || _itemData == null) { this.setCreateItems(false) return } this.addItemNode(this.baseIndex, _itemData) this.createIndex += 1 this.baseIndex += 1 } this.life += dt if (this.life >= this.ctime) { this.life = 0 } } private setScrollContentSize() { let val = 0 switch (this.scrollDir) { case ScrollDirEnum.VERTICAL: for (let i = 0; i < this.itemDataList.length; i++) { val += this.itemDistanceYTitle for (let j = 0; j < this.itemDataList[i][0].length; j++) { val += this.itemDistanceY } } this.scrollView.content.setContentSize(this.scrollView.node.width, val + this.padingY + this.padingY2 + this.tagLang) break case ScrollDirEnum.HORIZONTAL: for (let i = 0; i < this.itemDataList.length; i++) { val += this.itemDistanceXTitle for (let j = 0; j < this.itemDataList[i][0].length; j++) { val += this.itemDistanceX } } this.scrollView.content.setContentSize(val + this.padingX + this.tagLang, this.scrollView.node.height) break case ScrollDirEnum.GRID: for (let i = 0; i < this.itemDataList.length; i++) { val += this.itemDistanceYTitle for (let j = 0; j < Math.ceil(this.itemDataList[i][0].length / this.hangCount); j++) { val += this.itemDistanceY } } this.scrollView.content.setContentSize(this.scrollView.node.width, val + this.padingY + this.padingY2 + this.tagLang) break } this.scrollMaxOffsetX = -this.scrollView.getMaxScrollOffset().x this.scrollMaxOffsetY = this.scrollView.getMaxScrollOffset().y } /** * 弹出详情标签,将会重设后续item的坐标。 * @param index item标签 * @param lang 坐标偏移量 */ setTag(index: number, lang: number) { this.tagLang = lang switch (this.scrollDir) { case ScrollDirEnum.VERTICAL: case ScrollDirEnum.HORIZONTAL: this.tagIndex = index break case ScrollDirEnum.GRID: this.tagIndex = Math.floor(index / this.hangCount) break } this.setScrollContentSize() this.itemListMain.forEach((element) => { this.setPosX(element.index, element.node) this.setPosY(element.index, element.node) }); } /**清除详情标签,恢复item默认坐标 */ clearTag() { this.setTag(-999, 0) // 修正index和坐标 let scrollHeight = this.scrollView.node.height this.itemListMain.forEach(ele => { // 判断是否超出边界 if (this.getPositionInView(ele.node).y > this.itemDistanceY / 2) { if (ele.index + this.instantiateCount < this.itemDataListMain.length) { ele.index += this.instantiateCount } this.setPosY(ele.index, ele.node) this.setItemData(ele.node, this.itemDataListMain[ele.index], ele.index) } else if (this.getPositionInView(ele.node).y < -(scrollHeight + this.itemDistanceY / 2)) { if (ele.index - this.instantiateCount >= 0) { ele.index -= this.instantiateCount } this.setPosY(ele.index, ele.node) this.setItemData(ele.node, this.itemDataListMain[ele.index], ele.index) } }) this.setScrollContentSize() } /**删除某个元素 */ del(index: number) { if (this.itemDataListMain.length < index) { return } this.itemDataListMain.splice(index, 1) // // 判断下是否需要删除节点 if (this.itemListMain.length > this.itemDataListMain.length) { this.itemListMain.pop().node.destroy() } this.setScrollContentSize() for (let index = 0; index < this.itemListMain.length; index++) { let element = this.itemListMain[index] if (this.itemDataListMain[element.index] == null) { element.index -= this.instantiateCount break } } this.itemListMain.forEach((element) => { this.setPosX(element.index, element.node) this.setPosY(element.index, element.node) this.setItemData(element.node, this.itemDataListMain[element.index], element.index) }); } /**在末尾添加一个元素 */ add(data: any) { this.itemDataListMain.push(data) // 判断是否需要增加item if (this.itemListMain.length >= this.instantiateCount) { // 不需要 // 判断下是否需要把最前面的放到最后面:1 let needUpdate = false for (let index = 0; index < this.itemListMain.length; index++) { if (this.itemListMain[index].index == this.itemDataListMain.length - 2) { needUpdate = true break } } if (needUpdate) { let minIndex = 1000 //这个是最小值 let minUpdateIndex = 0// 这个是最小值的标签 for (let i = 0; i < this.itemListMain.length; i++) { if (this.itemListMain[i].index < minIndex) { minIndex = this.itemListMain[i].index minUpdateIndex = i } } // 判断下是否需要把最前面的放到最后面:2 let _nodePos = 0 let _offset = 0 let needUpdate2 = false switch (this.scrollDir) { case ScrollDirEnum.HORIZONTAL: _nodePos = this.itemListMain[minUpdateIndex].node.x _offset = this.scrollView.getScrollOffset().x needUpdate2 = _nodePos + _offset > this.itemWidth / 2 break case ScrollDirEnum.VERTICAL: case ScrollDirEnum.GRID: _nodePos = this.itemListMain[minUpdateIndex].node.y _offset = this.scrollView.getScrollOffset().y needUpdate2 = _nodePos + _offset > this.itemHeight / 2 break } if (needUpdate2) { this.itemListMain[minUpdateIndex].index += this.instantiateCount this.setItemData(this.itemListMain[minUpdateIndex].node, data, this.itemListMain[minUpdateIndex].index) this.setPosX(this.itemListMain[minUpdateIndex].index, this.itemListMain[minUpdateIndex].node) this.setPosY(this.itemListMain[minUpdateIndex].index, this.itemListMain[minUpdateIndex].node) } } } else { // 需要 let index = this.itemDataListMain.length - 1 this.addItemNode(index, data) } this.setScrollContentSize() } private addItemNode(index: number, data: any, isRefresh: boolean = false) { let _node = this.initItemNode() this.scrollView.content.addChild(_node) this.setItemData(_node, data, index, isRefresh) this.itemListMain.push({ index: index, node: _node }) this.setPosX(index, _node) this.setPosY(index, _node) if (isRefresh || this.showAnim == 0) { _node.scale = this.itemScale } else if (this.showAnim == 1) { _node.scale = 0 cc.tween(_node). to(0.1, { scale: this.itemScale + 0.1 }). to(0.1, { scale: this.itemScale }). start() } else { _node.scale = this.itemScale } } private addItemNodeTitle(index: number, data: any) { if (this.itemListTitle.findIndex(n => n.index == index) == -1) { let _node = this._initItemNode() this.scrollView.content.addChild(_node) this.setItemDataTitle(_node, data, index) this.itemListTitle.push({ index: index, node: _node }) _node.zIndex = -999 this.setPosXTitle(index, _node) this.setPosYTitle(index, _node) } else { this.setItemDataTitle(this.itemListTitle[index].node, data, index) this.setPosXTitle(index, this.itemListTitle[index].node) this.setPosYTitle(index, this.itemListTitle[index].node) } } /** * @param val 立刻滚动到目标标签,目标标签将在顶部|左侧出现 * @param type 滚到哪里的类型(1.居中 2.顶部|左侧 3.底部|右侧) */ scrollToIndexNow(val: number, type: number = 1, ...args: any[]) { if (val >= this.itemDataListMain.length) { val = this.itemDataListMain.length - 1 } if (val < 0) { return } this.scrollView.stopAutoScroll() this.baseIndex = val let scrollPosIndex = this.baseIndex if (this.itemDataListMain.length - this.baseIndex < this.instantiateCount) { this.baseIndex -= this.instantiateCount - (this.itemDataListMain.length - this.baseIndex) } else { if (type == 1) { this.baseIndex -= Math.ceil(this.instantiateCount / 2) } else if (type == 3) { this.baseIndex -= (this.lieCount - 1) * this.hangCount } } if (this.scrollDir == ScrollDirEnum.GRID) { this.baseIndex -= this.baseIndex % this.hangCount } this.baseIndex = Math.max(this.baseIndex, 0) // 从第几个标签开始显示 switch (this.scrollDir) { case ScrollDirEnum.HORIZONTAL: let _x = -(this.setPosX(scrollPosIndex) - this.itemDistanceX / 2) // 判断是否超过边界 if (_x < this.scrollMaxOffsetX) { scrollPosIndex = this.itemDataListMain.length - this.instantiateCount this.scrollView.content.x = this.scrollMaxOffsetX } else { if (type == 1) { this.scrollView.content.x = Math.min(-(this.setPosX(scrollPosIndex) - this.scrollView.node.width / 2), 0) if (-(this.setPosX(scrollPosIndex) - this.scrollView.node.width / 2) >= 0) { this.baseIndex = 0 } } else if (type == 2) { this.scrollView.content.x = -(this.setPosX(scrollPosIndex) - this.itemDistanceX / 2) } else { scrollPosIndex++ this.scrollView.content.x = Math.min(-(this.setPosX(scrollPosIndex) - this.scrollView.node.width - this.itemWidth / 2 - this.spacingY), 0) if (-(this.setPosX(scrollPosIndex) - this.scrollView.node.width - this.itemWidth / 2 - this.spacingY) >= 0) { this.baseIndex = 0 } } } break case ScrollDirEnum.VERTICAL: case ScrollDirEnum.GRID: let _y = (Math.abs(this.setPosY(scrollPosIndex)) - this.itemDistanceY / 2) if (_y > this.scrollMaxOffsetY) { scrollPosIndex = this.itemDataListMain.length - this.instantiateCount this.scrollView.content.y = this.scrollMaxOffsetY } else { if (type == 1) { this.scrollView.content.y = Math.max(Math.abs(this.setPosY(scrollPosIndex)) - this.scrollView.node.height / 2, 0) if (Math.abs(this.setPosY(scrollPosIndex)) - this.scrollView.node.height / 2 <= 0) { this.baseIndex = 0 } } else if (type == 2) { this.scrollView.content.y = Math.abs(this.setPosY(scrollPosIndex)) - this.itemDistanceY / 2 } else { scrollPosIndex = this.scrollDir == ScrollDirEnum.VERTICAL ? (scrollPosIndex + 1) : scrollPosIndex + this.hangCount this.scrollView.content.y = Math.max(Math.abs(this.setPosY(scrollPosIndex)) - this.scrollView.node.height - this.itemHeight / 2 - this.spacingY, 0) if (Math.abs(this.setPosY(scrollPosIndex)) - this.scrollView.node.height - this.itemHeight / 2 - this.spacingY <= 0) { this.baseIndex = 0 } } } break } if (args.length > 0) { this.extraParams = args } this.fixItemNodes() // 如果content有子项目,则重制目标点位置 if (this.itemListMain.length > 0) { for (let index = 0; index < this.itemListMain.length; index++) { // let i = this.baseIndex + index let elemnet = this.itemListMain[index] elemnet.index = this.baseIndex + index this.setPosX(elemnet.index, elemnet.node) this.setPosY(elemnet.index, elemnet.node) this.setItemData(elemnet.node, this.itemDataListMain[elemnet.index], elemnet.index) } } } /** * 尝试滚动到滚动视图中心 * @param index 标签 * @param type 滚到哪里的类型(1.居中 2.顶部|左侧 3.底部|右侧) * @param time */ scrollToIndex(index: number, type: number = 1, time: number = 1, offset: number = 0) { if (this.itemDataListMain[index] == null) { console.error("不存在此标签") return } switch (this.scrollDir) { case ScrollDirEnum.HORIZONTAL: if (type == 1) { this.scrollView.scrollToOffset(cc.v2(this.setPosX(index) - this.scrollView.node.width / 2 + offset, this.scrollView.getScrollOffset().y), time) } else if (type == 2) { this.scrollView.scrollToOffset(cc.v2(this.setPosX(index) - this.itemDistanceX / 2 + offset, this.scrollView.getScrollOffset().y), time) } else { index++ this.scrollView.scrollToOffset(cc.v2(this.setPosX(index) - this.scrollView.node.width + offset, this.scrollView.getScrollOffset().y - this.itemWidth / 2 - this.spacingX), time) } break case ScrollDirEnum.VERTICAL: case ScrollDirEnum.GRID: if (type == 1) { this.scrollView.scrollToOffset(cc.v2(this.scrollView.getScrollOffset().x, Math.abs(this.setPosY(index)) - this.scrollView.node.height / 2 + offset), time) } else if (type == 2) { this.scrollView.scrollToOffset(cc.v2(this.scrollView.getScrollOffset().x, Math.abs(this.setPosY(index)) - this.itemDistanceY / 2 + offset), time) } else { index = this.scrollDir == ScrollDirEnum.VERTICAL ? (index + 1) : index + this.hangCount this.scrollView.scrollToOffset(cc.v2(this.scrollView.getScrollOffset().x, Math.abs(this.setPosY(index)) - this.scrollView.node.height - this.itemHeight / 2 - this.spacingY + offset), time) } break } // this.scheduleOnce(() => { // this.refreshItem(index) // }, time) } getItem(index: number): cc.Node | null { for (const key in this.itemListMain) { if (this.itemListMain[key].index == index) { return this.itemListMain[key].node } } return } // 使用对象池时,在切换界面时必须使用这个方法,将所有对象放到池中 // @TODO PoolManager clear() { if (this.scrollView && this.scrollView.content) { this.scrollView.content.removeAllChildren() } // if (this.useNodePool) { // this.itemListMain.forEach(element => { // if (this.poolType == PoolEnum.ITEM_BAG) { // PoolManager.putItemBag(element.node) // } else if (this.poolType == PoolEnum.ITEM_BASE) { // PoolManager.putItemBase(element.node) // } // }); // } else { // if (this.scrollView && this.scrollView.content) { // this.scrollView.content.removeAllChildren() // } // } this.itemDataList = [] this.itemListMain = [] this.itemListTitle = [] this.itemDataListMain = [] this.itemDataListTitle = [] this.indexFrom = {} } }