Files
fc/dev/project/assets/Scripts/nx/cmp/flex/nx.fx.sv.expand.js
T
2026-05-23 22:10:14 +08:00

810 lines
15 KiB
JavaScript

/******************************************************************
* 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 );
},
} );