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的控件仍然无效。