/****************************************************************** * Copyright(C) 2019 - 2020 Nx Studio * * 动态扩充列表(上下向) * * 2018.05.18 ******************************************************************/ cc.Class( { extends: cc.Component, properties: { svcKey: { default : "", displayName : "用途标签" }, fabItem: { default : null, type : cc.Prefab, displayName : "表项模板(二选一)" }, tmpItem: { default : null, type : cc.Node, displayName : "表项模板(二选一)" }, bindSCV: { default : null, type : cc.ScrollView, displayName : "操作列表" }, autoRow: { default : true, displayName : "单行宽度自适应", }, autoSpacePer: { default : cc.v2( 1.1, 1.1 ), displayName : "自适应间隙百分比", }, cntInRow: { default : 1, displayName : "单行个数(0为自动)" }, itemHeight: { default : 0, displayName : "单项高度(0为自动)" }, jumpBottom: { default : true, displayName : "重置是否到底", }, activeOnce: { default : true, displayName : "仅激活一次" }, selectHookers: { default : [], type : cc.Component.EventHandler, displayName : "选择回调" }, poolOpen: { default : false, displayName : "开启缓存池" }, poolName: { default : "", displayName : "自定义标记" }, poolCount: { default : 10, displayName : "缓存池大小" }, nodEmpty: { default : null, type : cc.Node, displayName : "空展示" }, blankPage: { default : false, displayName : "整页填空" }, }, // 编辑器特性 editor: { // 允许当前组件在编辑器模式下运行 executeInEditMode: false, // requireComponent 参数用来指定当前组件的依赖组件 requireComponent: null, // 当本组件添加到节点上后,禁止同类型(含子类)的组件再添加到同一个节点,防止逻辑发生冲突 disallowMultiple: true, // menu 用来将当前组件添加到组件菜单中,方便用户查找 menu: "Nx/组件|无限滚表", }, // 获取表项样本 getT: function() { if( this.tmpItem ) { this.tmpItem.active = false; return this.tmpItem; } if( this.fabItem ) { this.tmpItem = cc.instantiate( this.fabItem ); } // 表项模板合法性判断 if( !this.tmpItem || !this.tmpItem.getComponent( "nx.fx.sv.expand.item" ) ) { nx.error( "[FxExpandSV]无效的列表表项模板!" ); return null; } // 开启对象池 if( this.poolOpen && !this.pool ) { let key = this.fabItem ? this.fabItem.name : this.poolName; if( nx.dt.strEmpty( key ) ) { nx.warn( "[SVC]开启对象池失败,无效的标记名!" ); this.poolOpen = false; } else { this.pool = nx.pools.getPool( key, this.tmpItem, this.poolCount ); } } this.tmpItem.active = false; return this.tmpItem; }, // 加载 onLoad: function() { // 操作列表 if( !this.bindSCV ) { this.bindSCV = this.node.getComponent( cc.ScrollView ); if( !this.bindSCV ) { nx.error( "[FxExpandSV]无效的操作列表!" ); return; } } // 表项模板合法性判断 this.getT(); // 初始化占位节点 this.initPlaceholder(); // 滚动条件设定 this.dtSroll = 0; if( this.tmpNode ){ this.ckScroll = this.tmpNode.height * 0.25; } }, // 显示 onEnable: function() { // 监听列表事件 if( this.bindSCV ) { this.bindSCV.node.on( "scrolling", this.onScrolling, this ); } }, // 隐藏 onDisable: function() { // 监听列表事件 if( this.bindSCV ) { this.bindSCV.node.off( "scrolling", this.onScrolling, this ); } }, // 清理 clean: function() { // 清除聚焦 if( nx.dt.arrNEmpty( this.focusList ) ) { this.cleanFocus(); } this.focusList = []; // 监听撤销 let content = this.bindSCV.content; content.off( "size-changed", this.onListSizeChanged, this ); // 来源解除 this.data = null; // 当前激活 this.actIndexs = []; // 列表清理 let chd = content.children; for( let i in chd ) { let node = chd[i]; if( node.svItem ) { node.svItem.rebind( -1, null, this.svcKey ); } } // 节点回收 if( this.pool ) { for( let i in chd ) { let node = chd[i]; if( node.svItem ) { this.pool.put( node.svItem.node ); } } } content.removeAllChildren(); }, // 重建列表 rebuild: function( _data ) { if( !nx.dt.arrGood( _data ) ) { nx.error( "[FxExpandSV]无效的列表来源类型!" ); return; } // 表项模板合法性判断 this.getT(); // 初始化占位节点 this.initPlaceholder(); // 滚动条件设定 this.dtSroll = 0; this.ckScroll = this.tmpNode.height * 0.25; this.clean(); this.data = []; // 是否填空 if( this.blankPage ) { let row = Math.ceil( this.bindSCV.node.height / this.tmpNode.height ); let count = this.cntInRow * row; if( _data.length < count ) { for( let i = _data.length; i < count; ++i ) { _data.push( {} ); } } } // 批量追加 this.append( _data, true ); // 监听挂载 let content = this.bindSCV.content; content.on( "size-changed", this.onListSizeChanged, this ); // 空展示 if( this.nodEmpty ) { this.nodEmpty.active = nx.dt.arrEmpty( _data ); } }, // 追加 append: function( _data, _fixPos = false ) { if( !this.data ) { nx.error( "[FxExpandSV]先重建后追加!" ); return; } if( !nx.dt.arrGood( _data ) ) { nx.error( "[FxExpandSV]无效的列表来源类型!" ); return; } // 逐个创建 _data.forEach( _dt => { this._appendSingle( _dt ); } ); // 强刷布局 let content = this.bindSCV.content; let cmp = content.getComponent( cc.Layout ); if( cmp ) { cmp.updateLayout(); } // 位置修正 if( _fixPos ) { if( this.jumpBottom ) { this.bindSCV.scrollToBottom(); this.freshRange( _data.length - 1, false ); } else { this.bindSCV.scrollToTop(); this.freshRange( 0, true ); } } else { this.freshRange( this.actIndexs[0], true ); } }, // 删除 remove: function( _indexes ) { if( nx.dt.arrEmpty( _indexes ) ) { return; } // 逐个销毁 _indexes.forEach( _idx => { this._removeSingle( _idx ); } ); // 数组清理 nx.dt.arrDelete( this.data, ( _t ) => { return _t == undefined; }, false ); // 节点序列重排 this._reorderList(); // 强刷布局 let content = this.bindSCV.content; let cmp = content.getComponent( cc.Layout ); if( cmp ) { cmp.updateLayout(); } // 刷新 this.freshRange( 0, true ); }, // 是否聚焦 isFocus: function( _index ) { return nx.dt.arrMember( this.focusList, _index ); }, // 添加聚焦 addFocus: function( _index ) { if( !this.data ) { nx.warn( "[FxExpandSV]無法添加聚焦,暫無數據!" ); return; } // 越界 if( _index < 0 || _index >= this.data.length ) { return; } // 是否聚焦 if( this.isFocus( _index ) ) { return; } this.focusList.push( _index ); let chd = this.bindSCV.content.children; let cur = chd[_index]; if( cur && cur.svItem ) { cur.svItem.onFocus(); } }, // 移除聚焦 removeFocus: function( _index, _justArray = false ) { if( !_justArray ) { let chd = this.bindSCV.content.children; let item = chd[_index]; if( item && item.svItem ) { item.svItem.outFocus(); } } nx.dt.arrDelete( this.focusList, ( _t ) => { return _t == _index; } ); }, // 清除聚焦 cleanFocus: function( _justArray = false ) { if( !_justArray && nx.dt.arrNEmpty( this.focusList ) ) { let chd = this.bindSCV.content.children; for( let i = 0; i < this.focusList.length; ++i ) { let index = this.focusList[i]; let item = chd[index]; if( item && item.svItem ) { item.svItem.outFocus(); } } } this.focusList = []; }, // 初始化占位节点 initPlaceholder: function() { // 计算占位符尺寸 let tempN = this.getT(); let content = this.bindSCV.content; if( tempN ){ // 单行自动 if( this.autoRow || !nx.dt.numPositive( this.cntInRow, false ) ) { this.cntInRow = Math.floor( content.width / ( tempN.width * this.autoSpacePer.x ) ); if( this.cntInRow <= 0 ) { // nx.error( "无限列表宽度无效!" ); this.cntInRow = 1; } } // 高度自适应 if( !nx.dt.numPositive( this.itemHeight, false ) ) { this.itemHeight = tempN.height * this.autoSpacePer.y; } let width = Math.floor( content.width / this.cntInRow ); let height = this.itemHeight; let ph = new cc.Node( "tmp" ); ph.width = width; ph.height = height; // 保存 this.tmpNode = ph; } }, // 刷新可视区域 freshRange: function( _from, _toDn ) { // console.log( "刷新!" ); let sy = this.bindSCV.getScrollOffset().y; let ey = sy + this.bindSCV.node.height; let children = this.bindSCV.content.children; // 碰撞检测 let collision = function( _node ) { if( !_node || !_node.active ) { return false; } let y = Math.abs( _node.y ); let up = y - _node.height / 2; let dn = y + _node.height / 2; // 中间 if( up >= sy && dn <= ey ) { return true; } // 横跨 if( up <= sy && dn >= ey ) { return true; } // 上交||下交 if( ( up <= sy && dn >= sy ) || ( up <= ey && dn >= sy ) ) { return true; } return false; }; // 扫描 let actives = []; if( _toDn ) { // 向后扫描 let od = false; for( let i = _from; i < children.length; ++i ) { let nd = children[i]; if( !nd || !nd.active ) { continue; } if( collision( nd ) ) { od = true; actives.push( i ); nd.opacity = 255; } else { nd.opacity = 0; if( od ) break; } } } else { // 向前扫描 let od = false; for( let i = _from; i >= 0; --i ) { let nd = children[i]; if( !nd || !nd.active ) { continue; } if( collision( nd ) ) { od = true; actives.push( i ); nd.opacity = 255; } else { if( od ) break; nd.opacity = 0; } } } // 排序 actives.sort( ( a, b ) => { return a - b; } ); // 列表项激活 this.activeItems( actives ); }, // 列表项激活 activeItems: function( _indexs ) { // console.log( "列表项激活:" ); // console.log( "老:", this.actIndexs ); // console.log( "新:", _indexs ); if( nx.dt.arrEmpty( _indexs ) ) { return; } // 统计新增和过期 let adds = []; let outs = []; for( let i in _indexs ) { if( !nx.dt.arrMember( this.actIndexs, _indexs[i] ) ) { adds.push( _indexs[i] ); } } for( let i in this.actIndexs ) { if( !nx.dt.arrMember( _indexs, this.actIndexs[i] ) ) { outs.push( _indexs[i] ); } } // console.log( "增:", adds ); // console.log( "减:", outs ); // 更新当前激活列表 this.actIndexs = _indexs; let open = nx.dt.arrNEmpty( this.selectHookers ); // 逐个新增 const children = this.bindSCV.content.children; for( let i in adds ) { let idx = adds[i] let node = children[idx]; // 不需要新建 if( node.active && node.svItem && node.svItem.node.active ) { // 需要inShow if( !this.activeOnce && !node.svShow ) { node.svShow = true; node.svItem.inShow(); } } else { // 新建 if( !node.svItem ) { let tempN = this.getT(); let item = this.pool ? this.pool.get() : cc.instantiate( tempN ); item.name = "node"; item.active = true; item.parent = node; item.position = cc.Vec2.ZERO; item.svc = this; node.svItem = item.getComponent( "nx.fx.sv.expand.item" ); node.svItem.openTouch( open, this._onTouchItem.bind( this ) ); } node.active = true; node.svItem.node.active = true; node.svItem.rebind( idx, this.data[idx], this.svcKey ); } } // 需要outShow if( !this.activeOnce && outs.length > 0 ) { for( let i in adds ) { let idx = adds[i] let node = children[idx]; if( !node || !node.svItem || !node.svShow ) { continue; } node.svShow = false; node.svItem.outShow(); } } }, // 列表滚动 onScrolling: function() { // 无效越界屏蔽 let y = this.bindSCV.getScrollOffset().y; let max = this.bindSCV.getMaxScrollOffset().y; if( y < 0 || y > max ) { return; } // 不需要更新 let dt = y - this.dtSroll; if( Math.abs( dt ) < this.ckScroll || this.actIndexs.length == 0 ) { return; } // 刷新 let from = ( dt > 0 ) ? this.actIndexs[0] : this.actIndexs[this.actIndexs.length-1]; let toDn = ( dt > 0 ) ? true : false; this.freshRange( from, toDn ); this.dtSroll = y; }, // 列表动态长度改变 onListSizeChanged: function() { // console.log( "onListSizeChanged" ); if( this.actIndexs.length > 0 ) { this.freshRange( this.actIndexs[0], true ); } }, // 单个追加 _appendSingle: function( _dt ) { if( !_dt ) { nx.error( "[FxExpandSV]追加失败,空数据!" ); return; } // 追加数据 this.data.push( _dt ); // 列表项同步 let content = this.bindSCV.content; let index = this.data.length - 1; let node = content.children[index]; if( !node ) { // 创建节点 node = cc.instantiate( this.tmpNode ); node.parent = content; } // 这个时候svOrder绝对不会大于0,否则视为有BUG if( nx.dt.numPositive( node.svOrder, false ) ) { nx.error( "[FxExpandSV]追加出错,缓存节点报错:%d", index ); } node.active = true; node.svShow = false; node.svOrder = index; }, // 单个销毁 _removeSingle: function( _index ) { // 越界 if( _index < 0 || _index >= this.data.length ) { nx.error( "[FxExpandSV]单个销毁失败,无效位置:%d", _index ); return; } // 删除数据 delete this.data[_index]; // 聚焦移除 this.removeFocus( _index, true ); // 删除节点 let chd = this.bindSCV.content.children; for( let i in chd ) { let node = chd[i]; if( node && node.svOrder == _index ) { node.destroy(); return; } } // 失败 nx.error( "[FxExpandSV]单个销毁失败,节点销毁失败:%d", _index ); }, // 节点序列重排 _reorderList: function() { let idx = 0; let chd = this.bindSCV.content.children; for( let i in chd ) { let node = chd[i]; node.svOrder = node.active ? idx++ : -1; if( !node.active && node.svItem ) { if( node.svItem != -1 ) { node.svItem.rebind( -1, null, this.svcKey ); } node.svItem.node.active = false; } } // 聚焦清理 this.cleanFocus( true ); }, // 节点点击 _onTouchItem: function( _item ) { // 无效 if( !_item || !nx.dt.numPositive( _item.index, true ) ) { return; } // 有监听 if( nx.dt.arrNEmpty( this.selectHookers ) ) { this.selectHookers.forEach( _hk => { _hk.customEventData = _item; } ); nx.dt.trycatch( () => { cc.Component.EventHandler.emitEvents( this.selectHookers ); } ) } }, // 数据/节点同步检测 checkData: function() { let len = 0; let orders = []; let chd = this.bindSCV.content.children; for( let i in chd ) { let node = chd[i]; orders.push( node.svOrder ); if( node.active ) { ++len; } } nx.debug( "[FxExpandSV]同步检测:" ); nx.debug( "[FxExpandSV]\t数据:%d", this.data.length ); nx.debug( "[FxExpandSV]\t列表项: 总:%d 有效:%d", chd.length, len ); console.log( orders ); }, } );