// lqr:自己写的一个listview.挂载就能用,懂的都懂。
export class ListViewParam {
data:Array<any> = [];
itemLength:number = 0;
itemRenderHandler:cc.Component.EventHandler = null ;
itemRenderFunction:Function = null ;
itemPref:cc.Node = null ;
clearPool:boolean = false ;
}
const { ccclass, property, disallowMultiple, menu, executionOrder, requireComponent } = cc._decorator;
@ccclass
@disallowMultiple()
@menu(‘自定义组件/ListView’)
@requireComponent(cc.ScrollView)
@executionOrder(-5000)
export default class ListView extends cc.Component {
/**
* @param itemRenderHandler 可以手动绑定回调事件,也可以在 refreshScrollView 里面传进来。
*/
@property({
type: cc.Component.EventHandler,
tooltip: CC_DEV && '渲染Item事件(渲染器)',
})
public itemRenderHandler: cc.Component.EventHandler = new cc.Component.EventHandler();
/**
* @param itemPref 复用的item,可以手动绑定,也可以在 refreshScrollView 里面传进来,都不传默认是content的第一个子节点。
*/
@property({
type:cc.Node,
tooltip: CC_DEV && ' 复用的item,可以手动绑定,也可以在 refreshScrollView 里面传进来,都不传默认是content的第一个子节点。',
})
public itemPref:cc.Node = null;
/**
* @param itemTotalNum item总共的长度,一般是数组的长度。
*/
@property({
type:cc.Integer,
tooltip: CC_DEV && 'item总共的长度,一般是数组的长度,refreshScrollView 里面传进来,如果是数组,则会自己计算。',
})
public itemTotalNum:number = 0 ;
/**
* @param showItemNum 显示item的数量,可手动设置。如果为-1,会根据遮罩动态计算。
*/
@property({
type:cc.Integer,
tooltip: CC_DEV && '显示item的数量,可手动设置。如果为-1,会根据遮罩动态计算。',
})
public showItemNum:number = -1 ;
/**
* @param scroll_com ScrollView组件,可调用原有的方法。比如,滚动到底部,顶部等。
*/
public scroll_com:cc.ScrollView = null ;
init()
{
if(this.isInit) return ;
this.isInit = true ;
this.scroll_com = this.node.getComponent(cc.ScrollView);
// 添加事件
let moveScroll = new cc.Component.EventHandler() ;
moveScroll.target = this.node ;
moveScroll.component = cc.js.getClassName(this) ;
moveScroll.handler = "onScroll" ;
this.node.getComponent(cc.ScrollView).scrollEvents.push( moveScroll );
if( this.itemPref == null ) this.itemPref = this.scroll_com.content.children[0];
this.itemPref.parent = null ;
this.content = this.scroll_com.content ;
this.nodePoolList = new cc.NodePool();
if( this.showItemNum == -1 ) this.showItemNum = Math.ceil( this.node.getChildByName("view").height / this.itemPref.height );
}
private dataList:Array<any> = null ;
private maxItemIndex:number = 0 ;
private nowItemIndex:number = 0 ;
private minItemIndex:number = 0 ;
private showItemIndexList:Array<number> = [] ;
private showItemNodeList:Array<cc.Node> = [];
private content:cc.Node = null ;
private isInit:boolean = false;
private nodePoolList:cc.NodePool;
private getPoolNode()
{
let node:cc.Node = null;
if (this.nodePoolList.size() > 0) { // 通过 size 接口判断对象池中是否有空闲的对象
node = this.nodePoolList.get();
} else { // 如果没有空闲对象,也就是对象池中备用对象不够时,我们就用 cc.instantiate 重新创建
node = cc.instantiate(this.itemPref);
}
node.active = true ;
return node
}
private delPoolNode(node:cc.Node)
{
node.parent = null ;
node.active = false ;
this.nodePoolList.put(node);
}
private clearPoolNode()
{
this.nodePoolList.clear();
}
private onScroll(){
let yy = ( this.content.y - this.itemPref.height ) / this.itemPref.height;
let index = Math.floor(yy);
if( index < 0 ) index = 0 ;
if(this.nowItemIndex == index) return ;
this.nowItemIndex = index ;
this.refreshItem();
}
private refreshItem()
{
let index;
if( this.nowItemIndex == this.minItemIndex) index = this.minItemIndex + 1 ;
else if( this.nowItemIndex == this.maxItemIndex) index = this.maxItemIndex - 1 ;
else index = this.nowItemIndex ;
let showItems = [] ;
for( let i = index -2 ; i < index + this.showItemNum + 2 ; i ++ )
{
showItems.push(i);
}
for( let i = 0 ; i < showItems.length ; i ++ )
{
let isReset = false ;
for( let j = 0 ; j < this.showItemIndexList.length ; j ++ )
{
if( showItems[i] == this.showItemIndexList[j] )
{
isReset = true ;
}
}
if( !isReset ) this.getItem(showItems[i]);
}
for( let i = 0 ; i < this.showItemIndexList.length ; i ++ )
{
let isReset = false ;
for( let j = 0 ; j < showItems.length ; j ++ )
{
if( this.showItemIndexList[i] == showItems[j] )
{
isReset = true ;
}
}
if( !isReset ) this.delItem(i);
}
}
private getItem(index)
{
let node = this.getPoolNode();
node.parent = this.content ;
// 默认锚点是0.5 也可以支持 1 或者 0 ;
if( node.anchorY == 1 ) node.y = - node.height * (index - 1) ;
else if( node.anchorY == 0 ) node.y = - node.height * (index - 1) - node.height ;
else node.y = - node.height * (index - 1) - node.height/2 ;
this.showItemNodeList.push(node);
this.showItemIndexList.push(index);
this.refreshData(node,index - 1 );
}
private delItem(index)
{
this.delPoolNode(this.showItemNodeList[index]);
this.showItemIndexList.splice(index, 1);
this.showItemNodeList.splice(index, 1);
}
private refreshData(node:cc.Node, index:number){
if( index < 0 || index >= this.itemTotalNum ) return node.active = false ;
let data = null ;
if( this.dataList ) data = this.dataList[index];
this.itemRenderHandler && this.itemRenderHandler.emit([node,data,index]);
this.itemRenderFunction && this.itemRenderFunction(node,data,index);
}
private itemRenderFunction:Function = null ;
/**
* 挂载之后,初始调用一次,每次刷新整个视图都需要调用这个接口。
* scroll_com 属性,可实现原有的scrollView 的接口。
* 刷新视图,默认滚动到最顶部。
* @param data 可以是长度,也可以是数组。
* @param itemRenderHandler 刷新item的异步回调,有三个参数(cc.node,object|null,number)。
* @param itemPref 复用的item , item有监听事件,可能需要注销,在监听。预制体可挂载脚本,另做处理。
* @param clearPool 复用不同的item,需要清理对象池。
*/
public refreshScrollView(data:Array<any>|number,itemRenderHandler:cc.Component.EventHandler|Function = null ,itemPref:cc.Node = null , clearPool:boolean = false )
{
if( itemPref ) this.itemPref = itemPref;
this.init();
if( typeof data == "number" )
{
this.dataList = null ;
this.itemTotalNum = data;
}
else
{
this.dataList = data ;
this.itemTotalNum = data.length;
}
if( typeof itemRenderHandler == "function" ) this.itemRenderFunction = itemRenderHandler;
else this.itemRenderHandler = itemRenderHandler ;
this.maxItemIndex = this.itemTotalNum ;
this.content.height = ( this.maxItemIndex ) * this.itemPref.height ;
// this.content.y = 0;
this.scroll_com.scrollToTop(0.1);
this.nowItemIndex = 0 ;
this.showItemIndexList = [];
for (let index = 0; index < this.showItemNodeList.length; index++) {
const element = this.showItemNodeList[index];
this.delPoolNode(element);
}
if( clearPool ) this.clearPoolNode();
this.showItemNodeList = [];
this.refreshItem();
}
/**
*
* @param listViewParam 参数的规范,指传需要的参数,别的默认。具体的看实现。
*/
public refreshListView(listViewParam:ListViewParam)
{
let data:any ;
if(listViewParam.data.length == 0) data = listViewParam.itemLength ;
else data = listViewParam.data ;
if( listViewParam.itemLength != 0 ) this.itemTotalNum = listViewParam.itemLength ;
let hndler:any ;
if(listViewParam.itemRenderHandler == null) hndler = listViewParam.itemRenderFunction ;
else hndler = listViewParam.itemRenderHandler ;
this.refreshScrollView(data,hndler,listViewParam.itemPref,listViewParam.clearPool);
}
/**
*
* @param data 可以是长度,也可以是数组。
* @param itemRenderHandler 刷新item的异步回调,有三个参数(cc.node,object|null,number)。
* @param self 是否需要绑定this指向
*/
public refreshHandler(data:Array<any>|number,itemRenderHandler:Function,self:cc.Component = null )
{
if(self) this.itemRenderFunction = itemRenderHandler.bind(self);
else this.itemRenderFunction = itemRenderHandler;
this.refreshScrollView(data);
}
}