列表(GList)
游戲開發(fā)過程中經(jīng)常會用到列表組件,比如我們做排行榜,服務(wù)器列表等。FGUI的列表能實現(xiàn)很多種復(fù)雜效果,相比較會比Laya原生的列表要強大。FGUI的虛擬列表還可以很好的處理數(shù)據(jù)量比較大的列表。
列表的基本用法很簡單,還是一樣點擊左邊工具欄上的列表按鈕創(chuàng)建一個列表,列表很多時候里面顯示的Iiem是差不多的,就是里面的圖標換一下或者文本內(nèi)容換一下啦,前面說GButton的時候也又提到過,列表的item使用的是GButton。這里設(shè)置列表的Item組件。

-
渲染順序(列表是特殊容器,Item是列表的子節(jié)點,多個Item就有渲染順序的問題,誰顯示再前面誰顯示再后面):
- 升序 按照Item索引升序排列
- 降序 按照Item索引降序排列
-
拱形 自定義個顯示再最前面的item,如果指定的是5,那么列表中第6個就顯示在最前面。
GList_3.png
邊緣 設(shè)置Item在列表中的位置
觸摸滾動效果 可以關(guān)閉列表滾動功能
為什么會是使用GButton呢,比如我們再做一個背包界面,每個物品都是一個背景,Icon,文本組成,這不是和GButton正好一樣么,背包里面的物品需要點擊GButton也支持。當然了,你一定不想用GButton,一定要用其他的也是可以的。
關(guān)于列表的屬性有很多,我就不一一介紹了還是看官網(wǎng)。
列表的使用
- 添加列表Item
const listItem = fairygui.UIPackage.createObject("Package1",'listItem2').asButton;
listItem.title = "第6個";
testList.addChild(listItem);
- 通過FGUI URL添加列表Item
const listItem2 = testList.addItem("ui://Package1/listItem2").asButton;
listItem2.title = "第7個";
- 刪除列表Item
testList.removeChild(listItem2);
testList.removeChildAt(0);
當你對列表進行增加刪除或者修改操作后,列表的排列和刷新是自動的,不需要調(diào)用任何API。
- 獲取列表中Item個數(shù)
// 獲取列表中Item個數(shù)
console.log('numChildren = ' + testList.numChildren); //numChildren = 7
//獲取列表中數(shù)據(jù)個數(shù)
console.log('numItems =' + testList.numItems); // numItems =7
在列表中Glist.numChildren永遠是等于Glist.numItems的,后面說到的虛擬列表就不一定了。
- 點擊列表內(nèi)的某一個item觸發(fā)事件
testList.on(fairygui.Events.CLICK_ITEM,this,this.onClickItem)
private onClickItem(item:fairygui.GComponent,event:any){
const itemButton = item.asButton;
console.log('點擊了' + itemButton.title + 'Button');
}
我們在開發(fā)排行榜時列表的內(nèi)容會頻繁的的更新,一般時從后臺接受到數(shù)據(jù)后先清空列表,然后重新添加新的項目。但是會對象頻繁的創(chuàng)建銷毀會消耗很多的內(nèi)存和CPU。因此列表內(nèi)建了對象池解決這個問題。
使用對象池管理item的相關(guān)方法:
//從對象池中添加一個item到列表中,這里我們不用管對象池中有沒有item,如果對象池時空的會fgui會自動幫我們創(chuàng)建
const listItem0 = testList.addItemFromPool("ui://Package1/listItem2").asButton;
listItem0.title = '第1個';
const listItem1 = testList.addItemFromPool("ui://Package1/listItem2").asButton;
listItem1.title = '第2個';
const listItem2 = testList.addItemFromPool("ui://Package1/listItem2").asButton;
listItem2.title = '第3個';
const listItem3 = testList.addItemFromPool("ui://Package1/listItem2").asButton;
listItem3.title = '第4個';
// 將Item從列表中移除并放回對象池
testList.removeChildToPool(listItem1);
// 將指定位置的Item從列表中移除并放回對象池
testList.removeChildToPoolAt(0);
// 將指定范圍內(nèi)的Item從列表中移除并放回對象池,也可以移除全部(如果結(jié)束位置的索引小于0或者大于Item數(shù)量則移除全部)
testList.removeChildrenToPool(0,3);
虛擬列表
前面說到了FGUI可以很好的處理數(shù)據(jù)量很大的情況,試想一下玩家在第一次打開排行榜,這時候排行榜中有100條排名信息,如果我們不用循環(huán)的方式怎么將數(shù)據(jù)顯示到item上呢,列表會有一個item渲染的回調(diào)函數(shù),每一個item再被創(chuàng)建時都會將item對象傳到回調(diào)函數(shù)中,我們可以再回調(diào)函數(shù)中進行數(shù)據(jù)和UI的綁定。
//設(shè)置列表的渲染回調(diào)
testList.itemRenderer = Laya.Handler.create(this,this.renderItem,null,false)
//在設(shè)置列表要顯示的item數(shù)量(注意,這里一定要先設(shè)置渲染回調(diào)再設(shè)置item數(shù)量)
testList.numItems= 100;
private renderItem(index:number,item:fairygui.GComponent){
//可以再這里將排行榜數(shù)據(jù)顯示到item中
const itemButton = item.asButton;
itemButton.title = '第' + index + '個';
console.log('item個數(shù):' + item.parent._children.length);
}

通過打印看到創(chuàng)建了100個item。
那么就算使用對象池也需要創(chuàng)建100個Item對象,這個開銷也是很可怕的,關(guān)鍵是這100個item并不是每一個會都被玩家看到,可能玩家只會關(guān)心排名靠前的幾個,那么后面的很多創(chuàng)建出來就是浪費的。
虛擬列表的處理方法是不管有多少個數(shù)據(jù),始終只會創(chuàng)建出能在列表中顯示出來的數(shù)量,在玩家滾動列表時動態(tài)設(shè)置數(shù)據(jù)。
說了這么多,其實我們只要再上面代碼中設(shè)置渲染回調(diào)函數(shù)設(shè)置之前加上一句話就行了。
//開啟虛擬列表
testList.setVirtual();

再看下打印只創(chuàng)建了6個Item對象。只有6個Item是怎么顯示100條排行榜信息呢。剛才的回調(diào)函數(shù)里面還有一個參數(shù)index這個時候就要用到了。
如果我們100條數(shù)據(jù)保存再一個數(shù)組里面。這6條item也是保存再數(shù)組里面,這里就有兩個索引了
- ItemIndex 對應(yīng)著100條排行信息的索引
- ChildIndex 對應(yīng)著6個item對象的索引
這兩個索引有著一個轉(zhuǎn)換接口:
//根據(jù)itemIndex獲取ChildIndex
const childIndex = testList.itemIndexToChildIndex(index);
//根據(jù)ChildIndex獲取itemIndex
const itemIndex = testList.childIndexToItemIndex(childIndex);
上面說到的回調(diào)函數(shù)中的index就是ItemIndex。然后根據(jù)這個index就可以從100條數(shù)據(jù)中取出應(yīng)該現(xiàn)在再這個item上的數(shù)據(jù)了。
有些時候可能會有這個需求,玩家打開排行榜之后列表需要直接定位到自己的排名信息。
//假設(shè)玩家自得排名再第80名
const itemindex = 79
//滾動到指定的item,這里的參數(shù)應(yīng)是itemIndex
testList.scrollToView(itemindex);
const childIndex = testList.itemIndexToChildIndex(itemindex);
// 這就是玩家自己排名信息得item
const item = testList.getChildAt(childIndex);
某些情況下可能不想讓整個item接受點擊事件,會再item內(nèi)部放一個按鈕讓這個按鈕響應(yīng)點擊事件,那么上面說到得按鈕點擊事件可能就不行了,這時候可以在渲染函數(shù)中設(shè)置點擊事件。但是覺得不可以用匿名函數(shù)或者lamba表達式。否則會造成點擊事件多次觸發(fā)。
//這里因為onClick事件是從Laya中觸發(fā),
//在點擊的回調(diào)函數(shù)中不能取到GButton對象,只能夠取到GButton._displayObject。
//所以這里將GButton對象作為參數(shù)傳遞到回調(diào)函數(shù)中
itemButton.onClick(this,this.onClickItem,[itemButton]);
//這樣設(shè)置回調(diào)可能會導致同一個item下會存在多個匿名函數(shù)的點擊響應(yīng)函數(shù),會觸發(fā)多次回調(diào)函數(shù)
itemButton.onClick(this,(item:fairygui.GButton,event:any)=>{
},[itemButton]);
private onClickItem(item:fairygui.GButton,event:any){
const itemButton = item.asButton;
console.log('點擊了' + itemButton.title + 'Button');
}
注:
- 虛擬列表一旦開啟就不能關(guān)閉。
- 設(shè)置列表或者虛擬列表的numItems之前需要先設(shè)置渲染函數(shù)
- 虛擬列表一定需要設(shè)置渲染函數(shù)
-
虛擬列表的溢出處理需要設(shè)置成滾動,否則不能開啟虛擬列表GList_6.png
- 虛擬列表只能渲染設(shè)置好的item組件,可以在編輯器中設(shè)置,也可以通過GList.defaultItem設(shè)置。
- 虛擬列表不可以自己管理item,即不可以調(diào)用AddChild或者RemoveChild。只能通過numItems改變item的數(shù)量
- 如果想讓虛擬列表的首尾相連可以使用GList.setVirtualAndLoop開啟
- 如果需要動態(tài)修改item的大小,可以在渲染函數(shù)中設(shè)置item的大小或者在設(shè)置item大小后調(diào)用GList.RefreshVirtualList刷新虛擬列表。
- 獲取距離最近的錨點的item的錨點的坐標
console.log(testList.getSnappingPosition(Laya.stage.mouseX,Laya.stage.mouseY));
- 獲取當前處于顯示區(qū)域中的第一個item的索引
console.log(testList.getFirstChildInView());
- 調(diào)整列表的大小到能夠顯示全部item的尺寸
testList.resizeToFit();

