烦恼一般都是想太多了。

0%

FairyGUI对于列表和窗口的使用

这两者其实理解起来并不难,难的是在用起来的时候不熟悉背后的逻辑也有一脸懵逼的。因为 FairyGUI 很多地方都提供了直接编辑 UI 和使用代码来控制的方式,当然,直接编辑 UI 是最简单的,但是使用代码的话,是最灵活的。

在 一文中,我们已经大概知道了 GObject 是什么东西。

GList

Glist 继承自 GComponent,也是一个组件,我们可以利用所有 GComponent 的 API。不过这个时候我们可以更加在意 GList 本身提供的 API。

列表对应的类型是GList。在FairyGUI中,列表的本质就是一个组件,GList也是从GComponent派生来的,所以你可以用GComponent的API直接访问列表内的内容,例如可以用GetChild或者GetChildAt访问列表内的项目;也可以用AddChild添加一个item。这部分的API可以参考GComponent的显示列表管理。

官方文档上 进行了一些非代码的介绍。

当你对列表增删改后,列表是自动排列和刷新的,不需要调用任何API。自动排列时会根据列表的布局设置item的坐标、大小和深度,所以不要自行设置item的位置,也不要设置sortingOrder尝试去控制item的深度。除了一个例外,垂直布局的列表只会自动设置item的y坐标,如果你需要item有一个水平位移的效果,你仍然可以修改item的x值。水平布局的也是一样道理。

这个排列和刷新发生在本帧绘制之前,如果你希望立刻访问item的正确坐标,那么可以调用EnsureBoundsCorrect通知GList立刻重排。EnsureBoundsCorrect是一个友好的函数,你不用担心重复调用会有额外性能消耗。

GComponent 显示列表管理

  • numChildren 子元素数量
  • addChild、addChildAt 向容器内添加 元件
  • RemoveChild RemoveChildAt RemoveChildren 从容器内删除元件。当元件从显示对象中移出时,将不再占用显示资源。但元件从显示列表移出后,只是不显示,并没有销毁,如果你没有保存这个对象的引用留待后续使用,或者没有调用对象的Dispose方法销毁对象,那么会产生内存泄露。
  • GetChild GetChildAt 通过索引或名称获得元件引用。元件的名字是允许重复的,在这种情况下,GetChild返回第一个匹配名称的对象。
  • GetChildIndex 获得指定元件在显示列表中的索引。
  • SetChildIndex SwapChildren SwapChildrenAt 设置元件在显示列表中的索引。

成员及构造方法

export class GList extends GComponent {
public itemRenderer: (index: number, item: GObject) => void;
public itemProvider: (index: number) => string;
public constructor() {
super();

this._node.name = "GList";
this._trackBounds = true;
this._pool = new GObjectPool();
this._layout = ListLayoutType.SingleColumn;
this._autoResizeItem = true;
this._lastSelectedIndex = -1;
this._selectionMode = ListSelectionMode.Single;
this.opaque = true;
this._align = AlignType.Left;
this._verticalAlign = VertAlignType.Top;
}
  • itemRenderer 一个渲染 item 的函数,这个就类似于 Adapter 一样,用来操纵一个 Item。
  • itemProvider 官方文档没有介绍,我猜测这个东西应该指定的是一个元件的 URI。
  • _pool:一个对象池,为了在减少大量的删除和新建 Item 所造成的性能问题。

对象池

在实际应用中,列表的内容通常被频繁的更新。典型的用法就是当接收到后台数据时,将列表清空,然后再重新添加所有项目。如果每次都创建和销毁UI对象,将消耗很大的CPU和内存。因此,GList内建了对象池。

使用对象池后的显示列表管理方法:

  • addItem(string) 指定一个 GObject 的 url ,然后建立后添加到 GList 的节点下,调用的是 addChild() 方法。
  • addItemFromPool(string)。从对象池获取一个 URL 类型的 GObject,添加到 GList 节点下。
  • getFromPool(string)。从对象池获取一个 URL 类型的 GObject。如果对象池没有,那么会自动新建。
  • ReturnToPool(GObject) 将对象返回池里。
  • RemoveChildToPool(GObject) 删除一个item,并将对象返回池里。
  • RemoveChildToPoolAt(GObject) 删除一个指定位置的item,并将对象返回池里。
  • RemoveChildrenToPool(number,number) 删除一个范围内的item,或者全部删除,并将删除的对象都返回池里

回调修改列表

当添加大量item时,除了用循环方式AddChild或AddItemFromPool外,还可以使用另一种回调的方式。首先为列表定义一个回调函数,例如:

void RenderListItem(int index, GObject obj)
{
GButton button = obj.asButton;
button.title = ""+index;
}

然后设置其为渲染函数:

aList.itemRenderer = this.renderListItem.bind(this);

最后直接设置列表中的项目总数,这样列表就会调整当前列表容器的对象数量,然后调用回调函数渲染item。

//创建100个对象,注意这里不能使用numChildren,numChildren是只读的。

aList.numItems = 10;

如果新设置的项目数小于当前的项目数,那么多出来的item将放回池里。

使用这种方式生成的列表,如果你需要更新某个item,自行调用RenderListItem(索引,GetChildAt(索引))就可以了。

默认情况下,回调其实是空的:

public itemRenderer: (index: number, item: GObject) => void;
public itemProvider: (index: number) => string;

当我们设置 numItems 会执行很多逻辑:

  1. 如果是虚拟列表,同时设置了循环滚动,那么会将真实的元素数量乘以6,然后添加虚拟元素,进行渲染。
  2. 不是虚拟列表的情况,从对象池添加元素,然后调用 itemRenderer() 函数。
public set numItems(value: number) {
if (this._virtual) {
if (this.itemRenderer == null)
throw "Set itemRenderer first!";

this._numItems = value;
if (this._loop)
this._realNumItems = this._numItems * 6;//设置6倍数量,用于循环滚动
else
this._realNumItems = this._numItems;

//_virtualItems的设计是只增不减的
var oldCount: number = this._virtualItems.length;
if (this._realNumItems > oldCount) {
for (i = oldCount; i < this._realNumItems; i++) {
var ii: ItemInfo = {
width: this._itemSize.width,
height: this._itemSize.height,
updateFlag: 0
};

this._virtualItems.push(ii);
}
}
else {
for (i = this._realNumItems; i < oldCount; i++)
this._virtualItems[i].selected = false;
}

if (this._virtualListChanged != 0)
this._partner.unschedule(this._refreshVirtualList);

//立即刷新
this._refreshVirtualList();
}
else {
var cnt: number = this._children.length;
if (value > cnt) {
for (var i: number = cnt; i < value; i++) {
if (this.itemProvider == null)
this.addItemFromPool();
else
this.addItemFromPool(this.itemProvider(i));
}
}
else {
this.removeChildrenToPool(value, cnt);
}
if (this.itemRenderer != null) {
for (i = 0; i < value; i++)
this.itemRenderer(i, this.getChildAt(i));
}
}
}

回调函数的签名是:

(i:number, item:GObject) => {}

并不是通过数据驱动的哦。