1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174 |
- /**
- * @Author huangxin
- * @ctime 2020-06-10
- * @Version
- * ScrollFinal1.0 2020-06-10 beta升级,使用挂载预制体
- * ScrollFinal1.1 2020-08-11 新增对象池类大法
- * ScrollFinal1.2 2020-08-17 1.新增延迟刷新item,防止在某一帧生成过量item导致卡顿的问题
- * 2.初始的控件属性设置(init方法)改为由initScrollView触发(原本的onLoad触发在适配模式下会不准确)
- * ScrollFinal1.3 2021-05-26 现在的滚动节点不需要手动添加scrollView组件了
- * ScrollFinal1.4 2022-04-14 现在可以在初始化或refresh时,立刻滚动到某个位置了
- * ScrollFinal1.5 2023-06-22 新增 adapterItem , 可在index首次出现时设定它的高度(后续把动态修改补上)
- * @Tips
- * 复用滚动轴 用来减少drawcall
- * 与cc.ScrollView组件一同挂载在一个节点上
- * item挂载的脚本必须添加setData方法,用来传递数据
- * item锚点应该在中心
- * 目前Grid类型只支持从左上到右下模式(垂直滚动),其他奇葩模式自己搞定
- * 滚动轴的锚点必须放在滚动列表的起始位置(比如背包grid模式在左上角,成就列表在左上角)
- *
- * @adapterItem item如有不定高度时,编辑器中设定的item需要为最小高度,确保instance的个数最大值是正确的
- * 支持不规则高度item 谨用,有缺陷:
- * 1.同一个item再次设置不同高度时会出现问题(若要修改,需要做一个链表来关联前后item)
- * 2.仅支持垂直和水平模式,背包模式不支持(水平模式未测试)
- */
- import { gameMethod } from "../common/gameMethod";
- import { CC_NODE_EVENT } from "../data/const/TypeConst";
- import ScrollInner from "./ScrollInner";
- import ScrollOut from "./ScrollOut";
- // 对象池类型
- let PoolEnum = cc.Enum({
- /**通用道具 */
- ITEM_BASE: 0,
- /**背包道具 */
- ITEM_BAG: 1,
- // /**合成道具 */
- // ITEM_COMPOSE: 2,
- })
- // 滚动类型
- let ScrollDirEnum = cc.Enum({
- /**垂直*/
- VERTICAL: 0,
- /**水平*/
- HORIZONTAL: 1,
- /**背包*/
- GRID: 2
- })
- // 滚动类型
- let ScrollOutInner = cc.Enum({
- /**外层scroll*/
- DEFAULT: 0,
- /**外层scroll*/
- OUT: 1,
- /**内层scroll*/
- INNER: 2,
- })
- interface AdapterList {
- x: number,
- y: number,
- width: number,
- height: number
- }
- type ItemList = { index: number, node: cc.Node }
- const { ccclass, property, menu } = cc._decorator;
- @ccclass
- @menu('Scroll/ScrollFinal')
- export default class ScrollFinal 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({
- type: ScrollOutInner,
- tooltip: "0=>常规scroll\n1=>外层scroll\n2=>内层scroll"
- })
- private outInner = ScrollOutInner.DEFAULT
- @property({ tooltip: "与滚动层的边界-左" })
- padingX: number = 10
- @property({ tooltip: "与滚动层的边界-上" })
- padingY: number = 10
- @property({ tooltip: "与滚动层的边界-下" })
- padingY2: number = 0
- @property({
- visible: function () { return this.scrollDir != ScrollDirEnum.VERTICAL }, tooltip: "item行间距"
- })
- spacingX: number = 20
- @property({
- visible: function () { return this.scrollDir != ScrollDirEnum.HORIZONTAL }, tooltip: "item列间距"
- })
- spacingY: number = 20
- @property(cc.Prefab)
- itemPrefab: cc.Prefab = null // item资源加载地址
- @property
- itemScript: 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: "不支持背包,默认不激活", // 激活后,无法在初始化时直接滚动到未展示过的index标签位置,即 scrollToIndexNow会受到限制
- displayName: "不固定item尺寸"
- })
- adapterItem: boolean = false
- adapterList: { [idx: string]: AdapterList } = {} // 记录适配的坐标列表
- adapterContentLength: number = 0 // 激活适配后的content高度
- // @property({
- // tooltip: "启动widget模式(注意激活后不可与原生cc.widget同时使用)"
- // })
- // useWidget: boolean = false
- // @property({ tooltip: "适配顶部", visible: function () { return this.useWidget == true } })
- // useAlignTop: boolean = false
- // @property({ tooltip: "距离父节点顶部", visible: function () { return this.useAlignTop == true } })
- // widgetTop: number = 0
- // @property({ tooltip: "适配底部", visible: function () { return this.useWidget == true } })
- // useAlignBottom: boolean = false
- // @property({ tooltip: "距离父节点底部", visible: function () { return this.useAlignBottom == true } })
- // widgetBottom: number = 0
- // @property({ tooltip: "适配左侧", visible: function () { return this.useWidget == true } })
- // useAlignLeft: boolean = false
- // @property({ tooltip: "距离父节点左侧", visible: function () { return this.useAlignLeft == true } })
- // widgetLeft: number = 0
- // @property({ tooltip: "适配右侧", visible: function () { return this.useWidget == true } })
- // useAlignRight: boolean = false
- // @property({ tooltip: "距离父节点右侧", visible: function () { return this.useAlignRight == true } })
- // widgetRight: number = 0
- @property({
- tooltip: "展示生产动画\n0->不展示\n1->缩放动画\n2->x方向压扁拉伸\n3->y方向压扁拉伸\n4->慢缩放"
- })
- showAnim: number = 0
- @property({
- tooltip: "单个动画播放速度\n最佳播放速度参考:\n缩放动画->0.1\nx方向压扁拉伸->0.1\ny方向压扁拉伸->0.25\n慢缩放->0.3"
- })
- animSpeed: number = 0.15
- @property({
- tooltip: "创建item的延迟时间,设为0则为每帧生成。\n注意:在所有item刷新完之前,scroll组件的滚动功能将被关闭"
- })
- ctime: number = 0
- @property({
- tooltip: "每帧生成item的个数"
- })
- cnumber: number = 1
- scrollView: cc.ScrollView | ScrollOut | ScrollInner
- mask: cc.Node
- content: cc.Node
- isScrollUp: boolean = false // 当前往哪个方向滚动 左和上是true
- private _itemDataList: any[] = [] // 当前显示阵营的所有数据
- private extraParams: any[] = [] // 额外数据
- private itemList: ItemList[] = [] // 实例化的item列表
- private instantiateCount: number = 0 // item实例化数量
- private hangCount: number = 0 // 行个数
- private lieCount: number = 0 // 列个数
- private itemDistanceX: number = 0 // item中心点之间的距离
- private itemDistanceY: number = 0 // item中心点之间的距离
- private scrollMaxOffsetX: number = 0 // 最大可滚动区域X
- private scrollMaxOffsetY: number = 0 // 最大可滚动区域Y
- private scrollIndex: number = -1 // scroll参数
- private lastScrollPos: number = 0 //上一次的滚动位置
- private curScrollPos: number = 0 // 当前滚动位置
- private itemWidth: number = 10 // item宽度
- private itemHeight: 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
- onLoad() {
- // if (this.node.getComponent(cc.ScrollView) != null) {
- // console.error("滚动节点无需挂载scrollView组件了")
- // return
- // }
- this.init()
- }
- resetSize() {
- if (this.mask) {
- this.mask.setContentSize(this.node.getContentSize())
- }
- if (this.content) {
- // this.content.setContentSize(this.node.getContentSize())
- this.setScrollContentSize()
- }
- this.setInstantCount()
- }
- private init() {
- if (this.hasInit) { return }
- /////////////// 构建滚动轴 ///////////////
- this.scrollView = this.outInner == ScrollOutInner.DEFAULT ? this.addComponent(cc.ScrollView) :
- this.outInner == ScrollOutInner.OUT ? this.addComponent(ScrollOut) : this.addComponent(ScrollInner)
- // 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
- /////////////// 检测是否需要重新适配 ///////////////
- if (this.node.getComponent(cc.Widget)) {
- this.node.getComponent(cc.Widget).updateAlignment()
- }
- /////////////// 构建滚动遮罩 ///////////////
- this.mask = new cc.Node()
- this.mask.parent = this.node
- this.mask.name = "scrollMask"
- this.mask.setContentSize(this.node.getContentSize())
- this.mask.addComponent(cc.Widget)
- this.mask.getComponent(cc.Widget).isAlignTop = true
- this.mask.getComponent(cc.Widget).isAlignBottom = true
- this.mask.getComponent(cc.Widget).top = 0
- this.mask.getComponent(cc.Widget).bottom = 0
- 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.content.name = "scrollContent"
- 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
- this.hasInit = true
- this.itemWidth = this.itemPrefab.data.getContentSize().width
- this.itemHeight = this.itemPrefab.data.getContentSize().height
- // SCROLL_BOUNCE_BOTTOM = 'bounce-bottom',//滚动视图滚动到顶部边界并且开始回弹时发出的事件
- // SCROLL_BOUNCE_LEFT = 'bounce-left',//滚动视图滚动到底部边界并且开始回弹时发出的事件
- // SCROLL_BOUNCE_RIGHT = 'bounce-right',//滚动视图滚动到左边界并且开始回弹时发出的事件
- // SCROLL_BOUNCE_TOP = 'bounce-top',//滚动视图滚动到右边界并且开始回弹时发出的事件
- this.scrollView.node.on(CC_NODE_EVENT.SCROLLING, this.onScroll, this)
- this.scrollView.node.on(CC_NODE_EVENT.SCROLL_BEGAN, this.onScrollBegan, this)
- this.scrollView.node.on(CC_NODE_EVENT.SCROLL_BOUNCE_TOP, this.onBounceTop, this)
- this.scrollView.node.on(CC_NODE_EVENT.SCROLL_BOUNCE_BOTTOM, () => { }, this)
- this.scrollView.node.on(CC_NODE_EVENT.SCROLL_BOUNCE_LEFT, () => { }, this)
- this.scrollView.node.on(CC_NODE_EVENT.SCROLL_BOUNCE_RIGHT, () => { }, this)
- this.scrollView.node.on(CC_NODE_EVENT.SCROLL_BOUNCE_TOP, () => { }, this)
- this.itemDistanceX = this.realItemWidth + this.spacingX
- this.itemDistanceY = this.realItemHeight + this.spacingY
- this.setInstantCount()
- // //看下有没有不应该有的组件
- // // 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
- }
- }
- initScrollView(list: any[] = [], ...args: any[]) {
- // this.scheduleOnce(() => {
- this.init()
- this.clear()
- this.clearTag()
- this.adapterList = {}
- this.itemDataList = list
- this.extraParams = args
- if (this.outInner == ScrollOutInner.OUT) {
- this.extraParams.push(this.scrollView)
- }
- this.scrollView.stopAutoScroll()
- this.showUI()
- // }, 0)
- }
- set itemDataList(list: any[]) {
- this._itemDataList = list
- if (!this.adapterItem) {
- return
- }
- // 适配模式,需要去掉记录的信息
- let newAdapterList: { [idx: string]: AdapterList } = {}
- for (let index = 0; index < list.length; index++) {
- if (this.adapterList[index]) {
- newAdapterList[index] = this.adapterList[index]
- }
- }
- this.adapterList = newAdapterList
- switch (this.scrollDir) {
- case ScrollDirEnum.VERTICAL:
- this.adapterContentLength = 0
- for (const idx in this.adapterList) {
- this.adapterContentLength = Math.max(Math.abs(this.adapterList[idx].y) + this.adapterList[idx].height / 2)
- }
- break
- case ScrollDirEnum.HORIZONTAL:
- this.adapterContentLength = 0
- for (const idx in this.adapterList) {
- this.adapterContentLength = Math.max(this.adapterList[idx].x + this.adapterList[idx].width / 2)
- }
- break
- }
- }
- get itemDataList(): any[] {
- return this._itemDataList
- }
- 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 onBounceTop() {
- switch (this.scrollDir) {
- case ScrollDirEnum.HORIZONTAL:
- break
- case ScrollDirEnum.VERTICAL:
- case ScrollDirEnum.GRID:
- this.checkScrollState()
- if (this.adapterItem) {
- // @TODO
- }
- break
- }
- this.scrollIndex = -1
- }
- 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.itemList.forEach(ele => {
- this.tryResetItem(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 < -(scrollWidth * this.node.anchorX + ele.node.width / 2)) {
- // 超出左边界显示区域
- let idx = ele.index + this.instantiateCount
- if (idx < this.itemDataList.length) {
- this.setItemData(element, this.itemDataList[idx], idx)
- // element.x = element.x + this.hangCount * ele.node.width
- ele.index = idx
- this.setPosX(idx, element)
- }
- } else if (!this.isScrollUp && this.getPositionInView(element).x > scrollWidth * (1 - this.node.anchorX) + ele.node.width / 2) {
- // 超出右边界显示区域
- let idx = ele.index - this.instantiateCount
- if (idx >= 0) {
- this.setItemData(element, this.itemDataList[idx], idx)
- // element.x = element.x - this.hangCount * ele.node.width
- ele.index = idx
- this.setPosX(idx, element)
- }
- }
- break
- case ScrollDirEnum.VERTICAL:
- case ScrollDirEnum.GRID:
- if (this.isScrollUp && this.getPositionInView(element).y > scrollHeight * (1 - this.node.anchorY) + ele.node.height / 2) {
- // 超出上边界显示区域
- let idx = ele.index + this.instantiateCount
- if (idx < this.itemDataList.length && this.isScrollUp) {
- this.setItemData(element, this.itemDataList[idx], idx)
- ele.index = idx
- this.setPosY(idx, element)
- }
- } else if (!this.isScrollUp && this.curScrollPos > -ele.node.height / 2 && this.getPositionInView(element).y < -(scrollHeight * this.node.anchorY + ele.node.height / 2)) {
- // 超出下边界显示区域
- let idx = ele.index - this.instantiateCount
- if (idx >= 0 && this.isScrollUp == false) {
- this.setItemData(element, this.itemDataList[idx], idx)
- ele.index = idx
- this.setPosY(idx, element)
- }
- }
- break
- }
- }
- private setPosX(index: number, node?: cc.Node): number {
- let x = 0
- if (this.adapterItem) {
- if (this.adapterList[index] && this.adapterList[index].x != null) {
- if (node) {
- node.x = this.adapterList[index].x
- }
- return this.adapterList[index].x
- } else {
- let lastX = index == 0 ? 0 : this.adapterList[index - 1].x
- let lastWidth = index == 0 ? 0 : this.adapterList[index - 1].width
- // 上一个坐标 + 当前node偏移量
- switch (this.scrollDir) {
- case ScrollDirEnum.VERTICAL:
- x = this.scrollView.content.width / 2 + this.padingX//this.realItemWidth / 2 + this.padingX
- break
- case ScrollDirEnum.HORIZONTAL:
- x += node.width / 2 + this.padingX + lastX + lastWidth / 2 + this.spacingX
- this.adapterContentLength = Math.max(x + node.width / 2)
- this.setScrollContentSize()
- break
- case ScrollDirEnum.GRID:
- x = index % this.hangCount * this.itemDistanceX + this.realItemWidth / 2 + this.padingX
- break
- }
- if (this.adapterList[index] == null) { this.adapterList[index] = { x: null, y: null, width: null, height: null } }
- this.adapterList[index].x = x
- this.adapterList[index].width = node.width
- if (node) { node.x = x }
- return x
- }
- } else {
- 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
- if (this.tagIndex >= -1 && index > this.tagIndex) {
- x += this.tagLang
- }
- break
- case ScrollDirEnum.GRID:
- x = index % 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
- if (this.adapterItem) {
- if (this.adapterList[index] && this.adapterList[index].y != null) {
- if (node) {
- node.y = this.adapterList[index].y
- }
- return this.adapterList[index].y
- } else {
- let lastY = index == 0 ? 0 : this.adapterList[index - 1].y
- let lastHeight = index == 0 ? 0 : this.adapterList[index - 1].height
- switch (this.scrollDir) {
- case ScrollDirEnum.VERTICAL:
- y = lastY - this.padingY - node.height / 2 - lastHeight / 2 - this.spacingY
- this.adapterContentLength = Math.max(Math.abs(y) + node.height / 2)
- this.setScrollContentSize()
- // y = -index * this.itemDistanceY - this.realItemHeight / 2 - this.padingY
- // 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:
- y = -Math.floor((index) / this.hangCount) * this.itemDistanceY - this.realItemHeight / 2 - this.padingY
- if (this.tagIndex >= -1 && (Math.floor(index / this.hangCount)) > this.tagIndex) {
- y -= this.tagLang
- }
- break
- }
- if (this.adapterList[index] == null) { this.adapterList[index] = { x: null, y: null, width: null, height: null } }
- this.adapterList[index].y = y
- this.adapterList[index].height = node.height
- if (node) { node.y = y }
- return y
- }
- } else {
- switch (this.scrollDir) {
- case ScrollDirEnum.VERTICAL:
- y = -index * this.itemDistanceY - this.realItemHeight / 2 - this.padingY
- 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:
- y = -Math.floor((index) / this.hangCount) * this.itemDistanceY - this.realItemHeight / 2 - this.padingY
- if (this.tagIndex >= -1 && (Math.floor(index / this.hangCount)) > this.tagIndex) {
- y -= this.tagLang
- }
- break
- }
- if (node) {
- node.y = y
- }
- return y
- }
- }
- private setItemData(itemNode: cc.Node, data: any, index: number) {
- try {
- itemNode.getComponent(this.itemScript).setData(data, index, this.extraParams)
- } catch (error) {
- console.error("脚本中缺少setData方法,或者方法报错", error)
- }
- }
- // refreshItems调用,在刷新时可能需要重置item的index标签
- private resetIndex(index: number): number {
- if (this.itemDataList[index] != null) {
- return index
- }
- return this.resetIndex(index - this.instantiateCount)
- }
- // 刷新单独的item
- refreshItem(index: number, data) {
- if (this.itemDataList[index] == null) {
- return
- }
- this.itemDataList[index] = data
- if (this.getItem(index) == null) {
- return
- }
- this.setItemData(this.getItem(index), this.itemDataList[index], index)
- }
- // refreshItem(index: number) {
- // this.setItemData(this.getItem(index), this.itemDataList[index], index)
- // }
- refreshItems(itemDataList: any[], ...args: any[]) {
- this.itemDataList = itemDataList
- if (args.length > 0) {
- this.extraParams = args
- }
- if (this.outInner == ScrollOutInner.OUT) {
- this.extraParams.push(this.scrollView)
- }
- this.fixItemNodes()
- // 最终构造完整的 itemList 列表,刷新数据
- this.itemList.forEach(element => {
- try {
- let newIndex = this.resetIndex(element.index)
- element.index = newIndex
- this.setItemData(element.node, this.itemDataList[element.index], element.index)
- this.setPosX(element.index, element.node)
- this.setPosY(element.index, element.node)
- // element.node.getComponent(this.itemScript).setData(this.itemDataList[element.index], element.index, this.extraParams)
- } catch (error) {
- console.warn("脚本中缺少refreshItem方法,或者方法报错", error)
- }
- })
- }
- // 数据新增或减少时,增加或减少item
- fixItemNodes() {
- // 判断是否需要删除 itemList 里的数据
- if (this.itemDataList.length < this.instantiateCount && this.itemList.length > this.itemDataList.length) {
- let needDeleteCount = this.itemList.length - this.itemDataList.length
- for (let index = this.itemDataList.length; index < this.itemDataList.length + needDeleteCount; index++) {
- this.itemList[index].node.destroy()
- }
- this.itemList.splice(this.itemDataList.length, needDeleteCount)
- // 判断是否需要增加 itemList 里的数据
- } else if (this.itemList.length < this.instantiateCount && this.itemList.length < this.itemDataList.length) {
- let addCount = Math.min(this.instantiateCount - this.itemList.length, this.itemDataList.length - this.itemList.length)
- let startIndex = 0
- this.itemList.forEach(element => {
- startIndex = Math.max(element.index + 1, startIndex)
- });
- for (let addIndex = startIndex; addIndex < (startIndex + addCount); addIndex++) {
- this.addItemNode(addIndex, this.itemDataList[addIndex], true)
- }
- }
- 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 {
- if (this.useNodePool) {
- // @TODO PoolManager
- return cc.instantiate(this.itemPrefab)
- // 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)
- }
- }
- // back
- private onScrollBegan() {
- this.scrollIndex = 0
- }
- private showUI() {
- if (this.itemPrefab == null) {
- console.error("item预制体加载失败")
- return
- }
- if (this.scrollView == null) {
- console.error("没有绑定scroll")
- return
- }
- this.setCreateItems(true)
- this.scrollIndex = -1
- 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
- } else {
- this.scrollView.enabled = true
- this.canCreateItem = false
- this.createIndex = 0
- this.baseIndex = 0
- this.life = 0
- }
- }
- update(dt: number) {
- if (this.scrollIndex >= 0) { this.scrollIndex += 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.itemDataList[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
- }
- }
- setScrollContentSize() {
- if (this.adapterItem) {
- switch (this.scrollDir) {
- case ScrollDirEnum.VERTICAL:
- this.scrollView.content.setContentSize(this.scrollView.node.width, this.adapterContentLength)
- break
- case ScrollDirEnum.HORIZONTAL:
- this.scrollView.content.setContentSize(this.adapterContentLength, this.scrollView.node.height)
- break
- case ScrollDirEnum.GRID:
- this.scrollView.content.setContentSize(this.scrollView.node.width, this.itemDistanceY * Math.ceil(this.itemDataList.length / this.hangCount) + this.padingY + this.tagLang + this.padingY2)
- break
- }
- } else {
- switch (this.scrollDir) {
- case ScrollDirEnum.VERTICAL:
- this.scrollView.content.setContentSize(this.scrollView.node.width, this.itemDistanceY * this.itemDataList.length + this.padingY + this.tagLang + this.padingY2 - this.spacingY)
- break
- case ScrollDirEnum.HORIZONTAL:
- this.scrollView.content.setContentSize(this.itemDistanceX * this.itemDataList.length + this.padingX + this.tagLang - this.spacingX, this.scrollView.node.height)
- break
- case ScrollDirEnum.GRID:
- this.scrollView.content.setContentSize(this.scrollView.node.width, this.itemDistanceY * Math.ceil(this.itemDataList.length / this.hangCount) + this.padingY + this.tagLang + this.padingY2 - this.spacingY)
- break
- }
- }
- this.scrollMaxOffsetX = -this.scrollView.getMaxScrollOffset().x
- this.scrollMaxOffsetY = this.scrollView.getMaxScrollOffset().y
- // console.log("---重新设置了滚动区域", this.scrollView.content.height)
- }
- /**
- * 弹出详情标签,将会重设后续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.itemList.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.itemList.forEach(ele => {
- // 判断是否超出边界
- if (this.getPositionInView(ele.node).y > scrollHeight * (1 - this.node.anchorY) + this.itemDistanceY / 2) {
- if (ele.index + this.instantiateCount < this.itemDataList.length) {
- ele.index += this.instantiateCount
- }
- this.setPosY(ele.index, ele.node)
- this.setItemData(ele.node, this.itemDataList[ele.index], ele.index)
- } else if (this.getPositionInView(ele.node).y < -(scrollHeight * this.node.anchorY + this.itemDistanceY / 2)) {
- if (ele.index - this.instantiateCount >= 0) {
- ele.index -= this.instantiateCount
- }
- this.setPosY(ele.index, ele.node)
- this.setItemData(ele.node, this.itemDataList[ele.index], ele.index)
- }
- })
- this.setScrollContentSize()
- }
- /**删除某个元素 */
- del(index: number) {
- if (this.itemDataList.length < index) { return }
- this.itemDataList.splice(index, 1)
- // // 判断下是否需要删除节点
- if (this.itemList.length > this.itemDataList.length) {
- this.itemList.pop().node.destroy()
- }
- this.setScrollContentSize()
- for (let index = 0; index < this.itemList.length; index++) {
- let element = this.itemList[index]
- if (this.itemDataList[element.index] == null) {
- element.index -= this.instantiateCount
- break
- }
- }
- this.itemList.forEach((element) => {
- this.setPosX(element.index, element.node)
- this.setPosY(element.index, element.node)
- this.setItemData(element.node, this.itemDataList[element.index], element.index)
- });
- }
- /**在末尾添加一个元素 */
- add(data: any) {
- this.itemDataList.push(data)
- // 判断是否需要增加item
- if (this.itemList.length >= this.instantiateCount) {
- // 不需要
- // 判断下是否需要把最前面的放到最后面:1
- let needUpdate = false
- for (let index = 0; index < this.itemList.length; index++) {
- if (this.itemList[index].index == this.itemDataList.length - 2) {
- needUpdate = true
- break
- }
- }
- if (needUpdate) {
- let minIndex = 1000 //这个是最小值
- let minUpdateIndex = 0// 这个是最小值的标签
- for (let i = 0; i < this.itemList.length; i++) {
- if (this.itemList[i].index < minIndex) {
- minIndex = this.itemList[i].index
- minUpdateIndex = i
- }
- }
- // 判断下是否需要把最前面的放到最后面:2
- let _nodePos = 0
- let _offset = 0
- let needUpdate2 = false
- switch (this.scrollDir) {
- case ScrollDirEnum.HORIZONTAL:
- _nodePos = this.itemList[minUpdateIndex].node.x
- _offset = this.scrollView.getScrollOffset().x
- needUpdate2 = _nodePos + _offset > this.itemWidth / 2
- break
- case ScrollDirEnum.VERTICAL:
- case ScrollDirEnum.GRID:
- _nodePos = this.itemList[minUpdateIndex].node.y
- _offset = this.scrollView.getScrollOffset().y
- needUpdate2 = _nodePos + _offset > this.itemHeight / 2
- break
- }
- if (needUpdate2) {
- this.itemList[minUpdateIndex].index += this.instantiateCount
- this.setItemData(this.itemList[minUpdateIndex].node, data, this.itemList[minUpdateIndex].index)
- this.setPosX(this.itemList[minUpdateIndex].index, this.itemList[minUpdateIndex].node)
- this.setPosY(this.itemList[minUpdateIndex].index, this.itemList[minUpdateIndex].node)
- }
- }
- } else {
- // 需要
- let index = this.itemDataList.length - 1
- this.addItemNode(index, data)
- }
- this.setScrollContentSize()
- }
- addItemNode(index: number, data: any, isRefresh: boolean = false) {
- let _node = this.initItemNode()
- // add放在前面.先激活onLoad方法,再走setData
- this.scrollView.content.addChild(_node)
- this.setItemData(_node, data, index)
- this.itemList.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(this.animSpeed, { scale: this.itemScale + 0.1 }).
- to(this.animSpeed, { scale: this.itemScale }).
- start()
- } else if (this.showAnim == 2) {
- _node.scale = this.itemScale
- let delayTime = index % this.hangCount / 20 + 0.1
- _node.x -= _node.width / 2
- _node.scaleX = 0
- cc.tween(_node).delay(delayTime).to(this.animSpeed, { x: _node.x + _node.width / 2, scaleX: this.itemScale }).start()
- } else if (this.showAnim == 3) {
- //@TODO
- _node.scale = this.itemScale
- let delayTime = index % this.hangCount / 20 + 0.1
- _node.y += _node.height / 2
- _node.scaleY = 0
- cc.tween(_node).delay(delayTime).to(this.animSpeed, { y: _node.y - _node.height / 2, scaleY: this.itemScale }).start()
- } else if (this.showAnim == 4) {
- _node.scale = 0
- cc.tween(_node).
- to(this.animSpeed, { scale: this.itemScale }).
- start()
- } else {
- _node.scale = this.itemScale
- }
- }
- /**
- * @param val 立刻滚动到目标标签,目标标签将在顶部|左侧出现
- * @param type 滚到哪里的类型(1.居中 2.顶部|左侧 3.底部|右侧)
- */
- scrollToIndexNow(val: number, type: number = 1, ...args: any[]) {
- if (this.adapterItem && this.adapterList[val] == null) {
- console.warn("未展示过,无法移动")
- return
- }
- if (val >= this.itemDataList.length) {
- val = this.itemDataList.length - 1
- }
- if (val < 0) {
- this.refreshItems(this.itemDataList)
- return
- }
- this.scrollView.stopAutoScroll()
- this.baseIndex = val
- let scrollPosIndex = this.baseIndex
- if (this.itemDataList.length - this.baseIndex < this.instantiateCount) {
- this.baseIndex -= this.instantiateCount - (this.itemDataList.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)
- let _itemWidth = this.adapterItem ? this.adapterList[val].width : this.itemWidth
- let _itemHeight = this.adapterItem ? this.adapterList[val].height : this.itemHeight
- // 从第几个标签开始显示
- switch (this.scrollDir) {
- case ScrollDirEnum.HORIZONTAL:
- let _x = -(this.setPosX(scrollPosIndex) - (_itemWidth * this.itemScale + this.spacingX) / 2)
- // 判断是否超过边界
- if (_x < this.scrollMaxOffsetX) {
- scrollPosIndex = this.itemDataList.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) - (_itemWidth * this.itemScale + this.spacingX) / 2)
- } else {
- scrollPosIndex++
- this.scrollView.content.x = Math.min(-(this.setPosX(scrollPosIndex) - this.scrollView.node.width - _itemWidth / 2 - this.spacingY), 0)
- if (-(this.setPosX(scrollPosIndex) - this.scrollView.node.width - _itemWidth / 2 - this.spacingY) >= 0) {
- this.baseIndex = 0
- }
- }
- }
- break
- case ScrollDirEnum.VERTICAL:
- case ScrollDirEnum.GRID:
- let _y = (Math.abs(this.setPosY(scrollPosIndex)) - (_itemHeight * this.itemScale + this.spacingY) / 2)
- if (_y > this.scrollMaxOffsetY) {
- scrollPosIndex = this.itemDataList.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)) - (_itemHeight * this.itemScale + this.spacingY) / 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 - _itemHeight / 2 - this.spacingY, 0)
- if (Math.abs(this.setPosY(scrollPosIndex)) - this.scrollView.node.height - _itemHeight / 2 - this.spacingY <= 0) {
- this.baseIndex = 0
- }
- }
- }
- break
- }
- if (args.length > 0) {
- this.extraParams = args
- }
- this.fixItemNodes()
- // 如果content有子项目,则重制目标点位置
- if (this.itemList.length > 0) {
- for (let index = 0; index < this.itemList.length; index++) {
- // let i = this.baseIndex + index
- let elemnet = this.itemList[index]
- elemnet.index = this.baseIndex + index
- this.setPosX(elemnet.index, elemnet.node)
- this.setPosY(elemnet.index, elemnet.node)
- if (gameMethod.isEmpty(this.itemDataList[elemnet.index])) {
- continue
- }
- this.setItemData(elemnet.node, this.itemDataList[elemnet.index], elemnet.index)
- }
- }
- }
- /**
- * 尝试滚动到滚动视图中心
- * @param index 标签
- * @param type 滚到哪里的类型(1.居中 2.顶部|左侧 3.底部|右侧)
- * @param time
- */
- scrollToIndex(index: number, type: number = 1, time: number = 1, offsetY: number = 0) {
- if (this.itemDataList[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, this.scrollView.getScrollOffset().y), time)
- } else if (type == 2) {
- this.scrollView.scrollToOffset(cc.v2(this.setPosX(index) - this.itemDistanceX / 2, this.scrollView.getScrollOffset().y), time)
- } else {
- index++
- this.scrollView.scrollToOffset(cc.v2(this.setPosX(index) - this.scrollView.node.width, 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 - offsetY), time)
- } else if (type == 2) {
- this.scrollView.scrollToOffset(cc.v2(this.scrollView.getScrollOffset().x, Math.abs(this.setPosY(index)) - this.itemDistanceY / 2 - offsetY), 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 - offsetY), time)
- }
- break
- }
- // this.scheduleOnce(() => {
- // this.refreshItem(index)
- // }, time)
- }
- getItem(index: number): cc.Node | null {
- for (const key in this.itemList) {
- if (this.itemList[key].index == index) {
- return this.itemList[key].node
- }
- }
- return
- }
- checkScrollState() {
- if (this.scrollIndex > this.itemDataList.length) {
- this.scrollView.stopAutoScroll()
- this.scrollView.scrollToOffset(cc.v2(0, this.scrollMaxOffsetY / 2))
- }
- }
- // 使用对象池时,在切换界面时必须使用这个方法,将所有对象放到池中
- clear() {
- // @TODO PoolManager
- if (this.scrollView && this.scrollView.content) {
- this.scrollView.content.removeAllChildren()
- }
- // if (this.useNodePool) {
- // this.itemList.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.itemList = []
- }
- }
|