NifytGUI——ListBox控件

2023-03-07,,

  ListBox控件的用法,创建一个xml,代码如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<nifty xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://nifty-gui.lessvoid.com/nifty-gui" xsi:schemaLocation="https://raw.githubusercontent.com/void256/nifty-gui/1.4/nifty-core/src/main/resources/nifty.xsd https://raw.githubusercontent.com/void256/nifty-gui/1.4/nifty-core/src/main/resources/nifty.xsd">
<useControls filename="nifty-default-controls.xml"/>
<useStyles filename="nifty-default-styles.xml"/>
<screen id="ListBoxScreen" controller="mygame.appState.ListBox01AppState">
<layer id="ListBoxLayer" childLayout="absolute">
<control name="listBox" id="myListBox" childLayout="overlay" horizontal="optional" width="612px" x="104" y="163" valign="center" vertical="optional" align="center" selectionMode="Single" displayItems="5" height="244px"/>
</layer>
</screen>
</nifty>

  Java控制器,代码如下:

/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mygame.appState; import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.BaseAppState;
import com.jme3.niftygui.NiftyJmeDisplay;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.NiftyEventSubscriber;
import de.lessvoid.nifty.controls.ListBox;
import de.lessvoid.nifty.controls.ListBoxSelectionChangedEvent;
import de.lessvoid.nifty.render.batch.BatchRenderConfiguration;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import java.util.List; /**
*
* @author JhonKkk
*/
public class ListBox01AppState extends BaseAppState implements ScreenController{
private Nifty nifty;
private NiftyJmeDisplay niftyDisplay;
public class JustAnExampleModelClass {
private String label; public JustAnExampleModelClass(final String label) {
this.label = label;
} public String toString() {
return label; // we could really use anything in here, the name of the sword or something ;-)
}
}
@Override
protected void initialize(Application app) {
SimpleApplication simpleApplication = (SimpleApplication) app;
BatchRenderConfiguration config = new BatchRenderConfiguration();
config.atlasWidth = 1024;
config.atlasHeight = 1024;
config.fillRemovedImagesInAtlas = false;
config.disposeImagesBetweenScreens = false;
config.useHighQualityTextures = true;
niftyDisplay = NiftyJmeDisplay.newNiftyJmeDisplay(
simpleApplication.getAssetManager(),
simpleApplication.getInputManager(),
simpleApplication.getAudioRenderer(),
simpleApplication.getGuiViewPort(),config);
nifty = niftyDisplay.getNifty();
nifty.enableAutoScaling(800, 600);
nifty.fromXml("Interface/ListBox01.xml", "ListBoxScreen", this);
simpleApplication.getGuiViewPort().addProcessor(niftyDisplay);
} @Override
protected void cleanup(Application app) {
} @Override
protected void onEnable() {
} @Override
protected void onDisable() {
} @Override
public void bind(Nifty nifty, Screen screen) {
// ListBox listBox = screen.findNiftyControl("myListBox", ListBox.class);
// listBox.addItem("a");
// listBox.addItem("b");
// listBox.addItem("c");
ListBox<JustAnExampleModelClass> listBox = (ListBox<JustAnExampleModelClass>) screen.findNiftyControl("myListBox", ListBox.class);
listBox.addItem(new JustAnExampleModelClass("You can add more lines to this ListBox."));
listBox.addItem(new JustAnExampleModelClass("Use the append button to do this."));
}
@NiftyEventSubscriber(id="myListBox")
public void onMyListBoxSelectionChanged(final String id, final ListBoxSelectionChangedEvent<JustAnExampleModelClass> event) {
List<JustAnExampleModelClass> selection = event.getSelection();
for (JustAnExampleModelClass selectedItem : selection) {
System.out.println("listbox selection [" + selectedItem.label + "]");
}
} @Override
public void onStartScreen() {
} @Override
public void onEndScreen() {
} }

  其中@NiftyEventSubscriber(id="myListBox")注解方法onMyListBoxSelectionChanged在每次listBox发生事件时回调,然后我们在方法里获取当前选择项并打印。

注意,该注解并非监听onSelect或onCheck事件,而是监听事件发生。

  如图:

  如果需要改变ListBox的字体,需要通过修改ListBox的Style来实现,通过研读默认的Style,可以发现ListBox使用默认的NiftyLabel作为Item,而默认的NiftyLabel又使用了默认的字体。所以我们只需要改变ListBox的Style,甚至可以通过Style修改用其他控件作为ListBox的Item。

  默认的Style文件:

<?xml version="1.0" encoding="UTF-8"?>
<nifty-styles xmlns="http://nifty-gui.lessvoid.com/nifty-gui"> <style id="nifty-listbox">
<attributes/>
</style>
<style id="nifty-listbox#scrollpanel">
<attributes focusable="true" padding="1px"/>
<effect overlay="true">
<onActive name="colorBar" color="#666f" post="false" neverStopRendering="true" timeType="infinite"/>
<onActive name="border" border="1px" color="#222f" inset="1px,0px,0px,1px"/>
<onFocus name="border" border="1px" color="#f006"/>
<onEnabled name="renderQuad" startColor="#2228" endColor="#2220" post="false" length="150"/>
<onDisabled name="renderQuad" startColor="#2220" endColor="#2228" post="false" length="150"/>
</effect>
</style>
<style id="nifty-listbox#bottom-right">
<attributes width="23px" height="23px"/>
</style>
<style id="nifty-listbox-item" base="nifty-label">
<attributes color="#000f" width="100%" align="left" textVAlign="center" textHAlign="left"/>
<interact onClick="listBoxItemClicked()"/>
<effect>
<onCustom customKey="focus" name="colorBar" post="false" color="#444f" neverStopRendering="true"
timeType="infinite"/>
<onCustom customKey="select" name="colorBar" post="false" color="#444f" neverStopRendering="true"
timeType="infinite"/>
<onCustom customKey="select" name="textColor" post="false" color="#fc0f" neverStopRendering="true"
timeType="infinite"/>
<onHover name="colorBar" color="#444f" post="false" neverStopRendering="true" timeType="infinite"
inset="1px"/>
<onClick name="focus" targetElement="#parent#parent"/>
</effect>
</style>
</nifty-styles>

  其中<style id="nifty-listbox-item" base="nifty-label">表明了ListBox默认使用自带的NiftyLabel作为Item,而默认的NiftyLabel使用默认的字体。

  自定义Item

  通过研读默认的nifty-listbox.xml,我们会发现默认的ListBox的item是一个NiftyLabel。而我们在游戏开发中往往不仅仅局限于显示文本列表。更多的是复杂的图文列表。那么,NiftyGUI是否可以像Android一样,提供自定义List Item的接口呢?答案是肯定的。

  具体而言,我们需要了解ListBox如何渲染一个Item,通过Wiki和源码可以简单了解到,ListBox渲染一个Item需要先提供模板控制器,然后每次渲染时通过模板控制器去获取Item并适配相应数据,这个过程其实和Android的RecylerView差不多,在Android里面,使用RecylerView渲染自定义Item,通常,我们可以用一个独立的布局文件来作为Item的模板,然后在java代码里实现适配器,加载对应的布局item,并填充数据。在NiftyGUI里,也是类似的。

  第一步,添加一个自定Item,在NiftyGUI中,UI控件分为两大类,一类是NiftyElement(如Layer,Panel等),另一类就是具体的控件类型(如Button,Label等),所以,在NiftyGUI中,添加一个自定义控件,就是一个自定义Control。在SDK中,在Interface目录下创建一个control目录,然后右键新建一个名为ImgLabelItem的Item控制器,如下:

<?xml version="1.0" encoding="UTF-8"?>
<nifty-controls xmlns="http://nifty-gui.lessvoid.com/nifty-gui">
<controlDefinition style="img-label-listbox" name="img-label-item">
<panel style="#panel" childLayout="horizontal" width="100%" align="center">
<image id="#icon" width="23px" height="23px" />
<control id="#text" name="label" align="left" style="img-label-listbox-label-item" textHAlign="left" height="23px" width="*" wrap="true" />
</panel>
</controlDefinition>
</nifty-controls>

  分析下这里的代码,首先第一行<nifty-controls>标签定义这是一个nifty控制器(所有具体的ui控件都是一个控制器或element),这表明我们正在创建一个自定义nifity控件;接下来的<controlDefinition>表明我们的控件定义从这里开始,在controlDefinition标签中,有一个style属性,这个非常重要,我们引用自己定义的img-label-listbox样式,这在接下来介绍,接着name属性指定了这个控件的名称是img-label-item(为何不叫id呢?其实可以这样理解,id是一个具体的实例的标识符,而name表明这是一个类的名称),以便我们在布局文件中使用这种控件。

  接着,我们通过组合基础控件类panel,image,control完成了我们这个自定义控件的内容(panle,image和control这些是基础类型控件,复杂的自定义控件都是通过组合基础类型得到,就像复合数据类型是通过组合各种基础数据类型实现的道理一样)。

  panel提供了一个容器,使用id为#panel的样式(这个样式定义在默认的nifty中,你可能会不解,没看到哪里引入了这个样式文件,没错,这里没有引入其他样式文件,因为我们在最后的屏幕布局中才引入默认的nifty style,同样,上面的img-label-listbox是一个自定义样式的id,但是在这个xml中我们并没有看到任何引入样式文件的代码,因为我们统一在最后的屏幕布局中才引入),childLayout指定子对象使用水平布局,width指定为填充100%宽度,而algin指定了panel对齐中心于父控件。

  image提供了一个图标控件,id为#icon,width为23像素,height为23像素。

  control的name属性表明这个control是一个label-control控件,有一点需要注意,image和panel都是element类型的控件,所以直接用image或panel作为标签名;而label是一个control类型的控件,所以通过control并指定name属性来描述这种控件。style使用id为img-label-listbox-label-item的样式,其他属性不用多说都能理解是什么意思。

  自定义控件定义好了,接下来,创建我们的自定义控件的样式文件,如果不创建自定义样式文件,则不存在上面id为img-label-listbox和img-label-listbox-label-item的样式,则只能使用默认的样式(样式必须显式指定,否则渲染会黑屏,但由于复杂控件由基础控件组成,基础控件默认就指定了样式,所以可以忽略style属性,但是controlDefinition必须显式指定样式)。我们在Interface下创建一个styles文件夹,然后添加一个名为img-label-list.xml的空样式文件,如下:

  打开样式文件,我们找到默认的nifty的listbox样式文件,具体位置在如下位置:

  把里面的内容复制到我们新建的style中,然后进行修改,如下:

 1 <?xml version="1.0" encoding="UTF-8"?>
2 <nifty-styles xmlns="http://nifty-gui.lessvoid.com/nifty-gui">
3
4 <style id="img-label-listbox">
5 <attributes/>
6 </style>
7 <style id="img-label-listbox#scrollpanel">
8 <attributes focusable="true" padding="1px"/>
9 <effect overlay="true">
10 <onActive name="colorBar" color="#666f" post="false" neverStopRendering="true" timeType="infinite"/>
11 <onActive name="border" border="1px" color="#222f" inset="1px,0px,0px,1px"/>
12 <onFocus name="border" border="1px" color="#f006"/>
13 <onEnabled name="renderQuad" startColor="#2228" endColor="#2220" post="false" length="150"/>
14 <onDisabled name="renderQuad" startColor="#2220" endColor="#2228" post="false" length="150"/>
15 </effect>
16 </style>
17 <style id="img-label-listbox#bottom-right">
18 <attributes width="23px" height="23px"/>
19 </style>
20 <style id="img-label-listbox-label-item" base="nifty-label">
21 <attributes color="#000f" width="100%" align="left" textVAlign="center" textHAlign="left"/>
22 <interact onClick="listBoxItemClicked()"/>
23 <effect>
24 <onCustom customKey="focus" name="colorBar" post="false" color="#444f" neverStopRendering="true"
25 timeType="infinite"/>
26 <onCustom customKey="select" name="colorBar" post="false" color="#444f" neverStopRendering="true"
27 timeType="infinite"/>
28 <onCustom customKey="select" name="textColor" post="false" color="#fc0f" neverStopRendering="true"
29 timeType="infinite"/>
30 <onHover name="colorBar" color="#444f" post="false" neverStopRendering="true" timeType="infinite"
31 inset="1px"/>
32 <onClick name="focus" targetElement="#parent#parent"/>
33 </effect>
34 </style>
35 </nifty-styles>

  在第4行定义了一个id为img-label-listbox的样式,但这是一个空样式;接着在第7行和第17行定义了img-label-listbox的伪类样式,这才是真正的img-label-listbox的各个项的样式;然后,在第20行定义了一个img-label-listbox#bottom-right样式,通过base属性表明该样式继承自nifty-label样式,说明这是一个为label控件类型定制的样式。我们看看这个样式定义,首先attributes定义了color,width,align等一些属性,然后通过interact标签定义了用户交互响应listBoxItemClicked()函数,这个是函数是默认的listbox控制器内部的函数,建议你直接用这个,因为你自己实现的话需要写适配器,绑定等一系列逻辑;接下来effect标签定义了控件的一些效果,比如鼠标悬浮时(onHover事件)的效果,或者点击时(onClick事件)的效果,另外,也可通过onCustom标签来描述事件,比如将onClick事件改为onCustom描述则是:<onCustom customKey="click"/>,所以,我们通过onCustom标签分别定义了focus事件,select事件发生时,哪些属性产生效果;如<onCustom customKey="focus" name="colorBar".../>定义了当focus事件发生时,属性colorBar产生特效效果,具体效果由后面的post,color,neverStopRendering等参数定义。

  现在,我们完成了自定义样式和自定义控件,可以实现我们的listBox的自定义item了,我们创建一个新的名为ImgLabelListBox01的gui文件,然后,代码如下:

 1 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2 <nifty xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://nifty-gui.lessvoid.com/nifty-gui" xsi:schemaLocation="https://raw.githubusercontent.com/void256/nifty-gui/1.4/nifty-core/src/main/resources/nifty.xsd https://raw.githubusercontent.com/void256/nifty-gui/1.4/nifty-core/src/main/resources/nifty.xsd">
3 <useControls filename="nifty-default-controls.xml"/>
4 <!--引入自定义控制器-->
5 <useControls filename="Interface/control/ImgLabelItem.xml"/>
6 <useStyles filename="nifty-default-styles.xml"/>
7 <!--引入自定义样式-->
8 <useStyles filename="Interface/styles/img-label-listbox.xml"/>
9 <screen id="ListBoxScreen" controller="mygame.appState.ImgLabelItemListBoxAppState">
10 <layer id="ListBoxLayer" childLayout="absolute">
11 <control name="listBox" id="myListBox" childLayout="overlay" horizontal="optional" width="612px" x="104" y="163" valign="center" vertical="optional" align="center" selectionMode="Single" displayItems="5" height="244px" viewConverterClass="mygame.gui.ImgLabelConverter">
12 <control name="img-label-item" controller="de.lessvoid.nifty.controls.listbox.ListBoxItemController" />
13 </control>
14 </layer>
15 </screen>
16 </nifty>

  这里可以看到,我们在自定义控件中使用的样式(如id为img-listbox的样式),是包含在img-label-listbox.xml这个文件中,我们在gui文件中引入了这个样式文件,然后引入了自定义控制器文件ImgLabelItem.xml。这样,我们的gui文件就可以访问到自定义控件,同时可以访问我们的自定义样式了。

  在第11行这里,我们定义了一个id为myListBox的listBox控件,然后添加了一个viewConverterClass="mygame.gui.ImgLabelConverter"属性,这个是item数据适配器对象,是一个我们自定义的item适配器java类,等会我们会介绍;然后,我们在内部定义了一个control标签,这个标签的name属性表明,这个control是一个img-label-item类型的控件,也就是我们自定义的类型为img-label-item的控件。同时指定controller属性,这个值可以写死,它使用默认的控制器类处理控件交互。

  接下来实现名称为mygame.gui.ImgLabelConverter的类,代码如下:

 1 /*
2 * To change this license header, choose License Headers in Project Properties.
3 * To change this template file, choose Tools | Templates
4 * and open the template in the editor.
5 */
6 package mygame.gui;
7
8 import de.lessvoid.nifty.controls.ListBox;
9 import de.lessvoid.nifty.elements.Element;
10 import de.lessvoid.nifty.elements.render.ImageRenderer;
11 import de.lessvoid.nifty.elements.render.TextRenderer;
12
13 /**
14 *
15 * @author JhonKkk
16 */
17 public class ImgLabelConverter implements ListBox.ListBoxViewConverter<ImgLabel>{
18 private static final String ICON = "#icon";
19 private static final String TEXT = "#text";
20
21 /**
22 * Default constructor.
23 */
24 public ImgLabelConverter() {
25 }
26
27 /**
28 * {@inheritDoc}
29 */
30 @Override
31 public final void display(final Element listBoxItem, final ImgLabel item) {
32 final Element text = listBoxItem.findElementById(TEXT);
33 final TextRenderer textRenderer = text.getRenderer(TextRenderer.class);
34 final Element icon = listBoxItem.findElementById(ICON);
35 final ImageRenderer iconRenderer = icon.getRenderer(ImageRenderer.class);
36 if (item != null) {
37 textRenderer.setText(item.getLabel());
38 iconRenderer.setImage(item.getIcon());
39 } else {
40 textRenderer.setText("");
41 iconRenderer.setImage(null);
42 }
43 }
44
45 /**
46 * {@inheritDoc}
47 */
48 @Override
49 public final int getWidth(final Element listBoxItem, final ImgLabel item) {
50 final Element text = listBoxItem.findElementById(TEXT);
51 final TextRenderer textRenderer = text.getRenderer(TextRenderer.class);
52 final Element icon = listBoxItem.findElementById(ICON);
53 final ImageRenderer iconRenderer = icon.getRenderer(ImageRenderer.class);
54 return ((textRenderer.getFont() == null) ? 0 : textRenderer.getFont().getWidth(item.getLabel()))
55 + ((item.getIcon() == null) ? 0 : item.getIcon().getWidth());
56 }
57
58 }

  其中该类继承自ListBoxViewConverter类,这是item适配器父类,实现了两个方法display和getWidth。其中display在每个item被渲染时回调,在这里填入item的数据(就跟Android里的RecerverView适配器一样);在getWidth返回当前渲染的item的宽度,返回0不可见。

  其中ImgLabel是一个javaBean类,代码如下:

 1 /*
2 * To change this license header, choose License Headers in Project Properties.
3 * To change this template file, choose Tools | Templates
4 * and open the template in the editor.
5 */
6 package mygame.gui;
7
8 import de.lessvoid.nifty.render.NiftyImage;
9
10 /**
11 *
12 * @author JhonKkk
13 */
14 public class ImgLabel {
15 private String label;
16 private NiftyImage icon;
17
18 public ImgLabel(String label, NiftyImage icon) {
19 this.label = label;
20 this.icon = icon;
21 }
22
23 public NiftyImage getIcon() {
24 return icon;
25 }
26
27 public String getLabel() {
28 return label;
29 }
30
31 }

  这没啥好说的,就是包含一个label和icon的数据结构。接着,我们创建名为mygame.appState.ImgLabelItemListBoxAppState的gui屏幕控制器类,用于实现最终整合gui的逻辑。如下:

 1 /*
2 * To change this license header, choose License Headers in Project Properties.
3 * To change this template file, choose Tools | Templates
4 * and open the template in the editor.
5 */
6 package mygame.appState;
7
8 import com.jme3.app.Application;
9 import com.jme3.app.SimpleApplication;
10 import com.jme3.app.state.BaseAppState;
11 import com.jme3.niftygui.NiftyJmeDisplay;
12 import de.lessvoid.nifty.Nifty;
13 import de.lessvoid.nifty.NiftyEventSubscriber;
14 import de.lessvoid.nifty.controls.ListBox;
15 import de.lessvoid.nifty.controls.ListBoxSelectionChangedEvent;
16 import de.lessvoid.nifty.render.batch.BatchRenderConfiguration;
17 import de.lessvoid.nifty.screen.Screen;
18 import de.lessvoid.nifty.screen.ScreenController;
19 import java.util.List;
20 import mygame.gui.ImgLabel;
21
22 /**
23 *
24 * @author JhonKkk
25 */
26 public class ImgLabelItemListBoxAppState extends BaseAppState implements ScreenController{
27 private Nifty nifty;
28 private NiftyJmeDisplay niftyDisplay;
29 public class JustAnExampleModelClass {
30 private String label;
31
32 public JustAnExampleModelClass(final String label) {
33 this.label = label;
34 }
35
36 public String toString() {
37 return label; // we could really use anything in here, the name of the sword or something ;-)
38 }
39 }
40 @Override
41 protected void initialize(Application app) {
42 SimpleApplication simpleApplication = (SimpleApplication) app;
43 BatchRenderConfiguration config = new BatchRenderConfiguration();
44 config.atlasWidth = 1024;
45 config.atlasHeight = 1024;
46 config.fillRemovedImagesInAtlas = false;
47 config.disposeImagesBetweenScreens = false;
48 config.useHighQualityTextures = true;
49 niftyDisplay = NiftyJmeDisplay.newNiftyJmeDisplay(
50 simpleApplication.getAssetManager(),
51 simpleApplication.getInputManager(),
52 simpleApplication.getAudioRenderer(),
53 simpleApplication.getGuiViewPort(),config);
54 nifty = niftyDisplay.getNifty();
55 nifty.enableAutoScaling(800, 600);
56 nifty.fromXml("Interface/ImgLabelListBox01.xml", "ListBoxScreen", this);
57 simpleApplication.getGuiViewPort().addProcessor(niftyDisplay);
58 }
59
60 @Override
61 protected void cleanup(Application app) {
62 }
63
64 @Override
65 protected void onEnable() {
66 }
67
68 @Override
69 protected void onDisable() {
70 }
71
72 @Override
73 public void bind(Nifty nifty, Screen screen) {
74 ListBox<ImgLabel> listBox = screen.findNiftyControl("myListBox", ListBox.class);
75 listBox.addItem(new ImgLabel("Help", nifty.getRenderEngine().createImage(screen, "Interface/help.png", false)));
76 listBox.addItem(new ImgLabel("Text", nifty.getRenderEngine().createImage(screen, "Interface/help.png", false)));
77 }
78 @NiftyEventSubscriber(id="myListBox")
79 public void onMyListBoxSelectionChanged(final String id, final ListBoxSelectionChangedEvent<ImgLabel> event) {
80 List<ImgLabel> selection = event.getSelection();
81 for (ImgLabel selectedItem : selection) {
82 System.out.println("listbox selection [" + selectedItem.getLabel() + "]");
83 }
84 }
85
86 @Override
87 public void onStartScreen() {
88 }
89
90 @Override
91 public void onEndScreen() {
92 }
93
94 }

  在bind()函数中初始化数据项目,在onMyListBoxSelectionChanged()方法中,获取选中的item并打印。最终效果如下:

  很显然,这就是我们的自定义Item。

  总结,一个自定义Item的实现需要以下几个步骤:

  1.实现自定义控件布局

  2.实现自定义控件样式

  3.实现自定义控件Item适配器

  需要注意的一点,如果你的自定义Item无法选中,即无法获取选中事件,那么极有可能是你没有为Item定义样式并定义Effect效果,要是一个item能够被选中,必须包含select效果,在这个例子中,是这样的:

1 <onCustom customKey="select" name="colorBar" post="false" color="#444f" neverStopRendering="true"
2 timeType="infinite"/>
3 <onCustom customKey="select" name="textColor" post="false" color="#fc0f" neverStopRendering="true"
4 timeType="infinite"/>

  还有一点需要注意,我们仅仅为id为#text控件定义了select效果,所以点击#text控件会有效,但是点击id为#icon的控件仍然无效。

NifytGUI——ListBox控件的相关教程结束。

《NifytGUI——ListBox控件.doc》

下载本文的Word格式文档,以方便收藏与打印。