android游戏开发框架libgdx的使用(五)–舞台和常用UI类

16 11月

本文使用的libgdx是0.92版本,和现在的最新版可能有一些不一样的地方。全文内容仅供参考。

常用的UI类包括标签,按钮,勾选框,下拉框,图片,输入框,列表,滑动面板,滑条,分割面板。它们都在com.badlogic.gdx.scenes.scene2d.ui包中,都属于Actor,可以方便的纳入舞台的管理中。

其实仔细看看UI类的实现代码不难发现其实它们都是大部分继承自Widget或者Table,如果需要自定义UI可以继承以上两个类(它们继承自Actor),这里要说明一下libgdx的布局部分使用了TWL,有兴趣的朋友可以去看看。

在介绍每个控件之前我们先来看一下NinePatch,这是最近的一个比较重大的更新。

何为NinePatch?其实android原生即有NinePatch类,常在按钮中使用。

NinePatch

如图,将图片分成九份。中间部分可以根据需要扩大,使按钮的大小内容变动不受图片的限制。

而在libgdx的NinePatch其实就是九个TextureRegion对象。

常用的实例化方法有两个:

publicNinePatch (Texture texture,int left,int right,int top,int bottom)

publicNinePatch (TextureRegion region,int left,int right,int top,int bottom)

关于其中的四个int型参数如何取值我们可以参考一下源码:

publicNinePatch (TextureRegion region,int left,int right,int top,int bottom) {
int middleWidth = region.getRegionWidth() - left - right;
int middleHeight = region.getRegionHeight() - top - bottom;
this.patches =new TextureRegion[] {new TextureRegion(region,0,0, left, top),
new TextureRegion(region, left,0, middleWidth, top),new TextureRegion(region, left + middleWidth,0, right, top),
new TextureRegion(region,0, top, left, middleHeight),new TextureRegion(region, left, top, middleWidth, middleHeight),
new TextureRegion(region, left + middleWidth, top, right, middleHeight),
new TextureRegion(region,0, top + middleHeight, left, bottom),
new TextureRegion(region, left, top + middleHeight, middleWidth, bottom),
new TextureRegion(region, left + middleWidth, top + middleHeight, right, bottom)};
}

先计算中间部分的宽度和高度。然后开始切图,首先取顶部的最左边的那个,即图中编号1的那块,然后去它右边的,然后再右边的。

取完最上边的那行,然后取中间的那行,然后取最后一行的。

由上自下,由左自右。

而在绘制时又是如何处理的呢?看源码:

publicvoiddraw (SpriteBatch batch,float x,float y,float width,float height) {
float centerColumnX = x;
if (patches[BOTTOM_LEFT] !=null)
centerColumnX += patches[BOTTOM_LEFT].getRegionWidth();
elseif (patches[MIDDLE_LEFT] !=null)
centerColumnX += patches[MIDDLE_LEFT].getRegionWidth();
elseif (patches[TOP_LEFT] !=null)//
centerColumnX += patches[TOP_LEFT].getRegionWidth();

float rightColumnX = x + width;
if (patches[BOTTOM_RIGHT] !=null)
rightColumnX -= patches[BOTTOM_RIGHT].getRegionWidth();
elseif (patches[MIDDLE_RIGHT] !=null)
rightColumnX += patches[MIDDLE_RIGHT].getRegionWidth();
elseif (patches[TOP_RIGHT] !=null)//
rightColumnX += patches[TOP_RIGHT].getRegionWidth();

float middleRowY = y;
if (patches[TOP_LEFT] !=null)
middleRowY += patches[TOP_LEFT].getRegionHeight();
elseif (patches[TOP_CENTER] !=null)
middleRowY += patches[TOP_CENTER].getRegionHeight();
elseif (patches[TOP_RIGHT] !=null)//
middleRowY += patches[TOP_RIGHT].getRegionHeight();

float topRowY = y + height;
if (patches[TOP_LEFT] !=null)
topRowY -= patches[TOP_LEFT].getRegionHeight();
elseif (patches[TOP_CENTER] !=null)
topRowY -= patches[TOP_CENTER].getRegionHeight();
elseif (patches[TOP_RIGHT] !=null)//
topRowY -= patches[TOP_RIGHT].getRegionHeight();

// Bottom row
if (patches[BOTTOM_LEFT] !=null) batch.draw(patches[BOTTOM_LEFT], x, y, centerColumnX - x, middleRowY - y);
if (patches[BOTTOM_CENTER] !=null)
batch.draw(patches[BOTTOM_CENTER], centerColumnX, y, rightColumnX - centerColumnX, middleRowY - y);
if (patches[BOTTOM_RIGHT] !=null)
batch.draw(patches[BOTTOM_RIGHT], rightColumnX, y, x + width - rightColumnX, middleRowY - y);

// Middle row
if (patches[MIDDLE_LEFT] !=null) batch.draw(patches[MIDDLE_LEFT], x, middleRowY, centerColumnX - x, topRowY - middleRowY);
if (patches[MIDDLE_CENTER] !=null)
batch.draw(patches[MIDDLE_CENTER], centerColumnX, middleRowY, rightColumnX - centerColumnX, topRowY - middleRowY);
if (patches[MIDDLE_RIGHT] !=null)
batch.draw(patches[MIDDLE_RIGHT], rightColumnX, middleRowY, x + width - rightColumnX, topRowY - middleRowY);

// Top row
if (patches[TOP_LEFT] !=null) batch.draw(patches[TOP_LEFT], x, topRowY, centerColumnX - x, y + height - topRowY);
if (patches[TOP_CENTER] !=null)
batch.draw(patches[TOP_CENTER], centerColumnX, topRowY, rightColumnX - centerColumnX, y + height - topRowY);
if (patches[TOP_RIGHT] !=null)
batch.draw(patches[TOP_RIGHT], rightColumnX, topRowY, x + width - rightColumnX, y + height - topRowY);
}

先计算左右栏的宽度,在计算中间和顶部的高度。然后从下自上的绘制。说实话我觉得这段代码看着很好玩的。

现在来说说几个常用的控件的使用吧。先构建一个舞台。

先来试试Label吧,label是有缓存的,所以替换显示内容不是用setText方法,而是使用setWrappedText方法。

代码如下:

package com.cnblogs.htynkn.listener;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actors.Label;

publicclass FirstGame implements ApplicationListener {

private Stage stage;
Label label;

@Override
publicvoidcreate() {
stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
true);
label =new Label("fpsLabel",new BitmapFont(Gdx.files.internal("cf.fnt"),Gdx.files.internal("cf.png"),false),"label1");
label.x=5;
label.y=Gdx.graphics.getHeight()-label.height-5;
stage.addActor(label);
Gdx.input.setInputProcessor(stage);
}

@Override
publicvoiddispose() {
stage.dispose();
}

@Override
publicvoidpause() {
// TODO Auto-generated method stub

}

@Override
publicvoidrender() {
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
label.setWrappedText("FPS: "+Gdx.graphics.getFramesPerSecond(),
HAlignment.CENTER);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}

@Override
publicvoidresize(int width,int height) {
// TODO Auto-generated method stub

}

@Override
publicvoidresume() {
// TODO Auto-generated method stub

}
}

效果:

FPS

然后再看看Button吧,实例化需要一个ButtonStyle,定义了按钮三种状态对应的图片样式,按下和松开时的X,Y偏移还有Button中文字绘制所需的BitmapFont和Color。

按钮的三种状态的图片我就省了,只用一张图片。

06

修改代码如下:

package com.cnblogs.htynkn.listener;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.NinePatch;
import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actors.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.Button.ButtonStyle;

publicclass FirstGame implements ApplicationListener {

private Stage stage;
Label label;
Texture texture;
Button button;

@Override
publicvoidcreate() {
stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
true);
texture =new Texture(Gdx.files.internal("06.png"));
NinePatch n1 =new NinePatch(texture,7,7,9,9);
BitmapFont bitmapFont =new BitmapFont(Gdx.files.internal("cf.fnt"),
Gdx.files.internal("cf.png"),false);
label =new Label("fpsLabel", bitmapFont,"label1");
label.x =5;
label.y = Gdx.graphics.getHeight() - label.height -5;
stage.addActor(label);
button =new Button("button",new ButtonStyle(n1, n1, n1,0f,0f,0f,
0f, bitmapFont,new Color(1,1,0,0.5f)),"button");
button.x=10;
button.y=10;
button.width=100f;
button.height=32f;
stage.addActor(button);
Gdx.input.setInputProcessor(stage);
}

@Override
publicvoiddispose() {
stage.dispose();
}

@Override
publicvoidpause() {
// TODO Auto-generated method stub

}

@Override
publicvoidrender() {
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
label.setWrappedText("FPS: " + Gdx.graphics.getFramesPerSecond(),
HAlignment.CENTER);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}

@Override
publicvoidresize(int width,int height) {
// TODO Auto-generated method stub

}

@Override
publicvoidresume() {
// TODO Auto-generated method stub

}
}

效果:

libgdx按钮

按钮自然应该有点击事件,通过setClickListener来设置

button.setClickListener(new ClickListener() {
@Override
publicvoidclick(Actor actor) {
Gdx.app.log("Info","点击事件触发了");
}
});

然后再看看CheckBox。CheckBox的样式定义在CheckBoxStyle中,需要4个参数,两种状态的各一张图片,一个BitmapFont和Color。

这里我再添加一张图片

07

原理差不多,直接贴代码了。

package com.cnblogs.htynkn.listener;

import android.graphics.Paint.Align;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.NinePatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actors.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox;
import com.badlogic.gdx.scenes.scene2d.ui.ClickListener;
import com.badlogic.gdx.scenes.scene2d.ui.Button.ButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox.CheckBoxStyle;

publicclass FirstGame implements ApplicationListener {

private Stage stage;
Label label;
Texture texture1;
Texture texture2;
CheckBox checkBox;

@Override
publicvoidcreate() {
stage =new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(),
true);
texture1 =new Texture(Gdx.files.internal("06.png"));
texture2 =new Texture(Gdx.files.internal("07.png"));
NinePatch n1 =new NinePatch(texture1,7,7,9,9);
BitmapFont bitmapFont =new BitmapFont(Gdx.files.internal("cf.fnt"),
Gdx.files.internal("cf.png"),false);
label =new Label("fpsLabel", bitmapFont,"label1");
label.x =5;
label.y = Gdx.graphics.getHeight() - label.height -5;
CheckBoxStyle style =new CheckBoxStyle(new TextureRegion(texture1),
new TextureRegion(texture2), bitmapFont,new Color(1,1,1,
0.5f));

checkBox =new CheckBox("checkbox", style,"checkbox");
checkBox.x =100;
checkBox.y =100;
checkBox.width =158f;
checkBox.height =32f;
checkBox.setText("Yes");
checkBox.setClickListener(new ClickListener() {

@Override
publicvoidclick(Actor actor) {
if (checkBox.isChecked) {
checkBox.setText("Yes");
}else {
checkBox.setText("NO");
}
}
});
stage.addActor(checkBox);
stage.addActor(label);
Gdx.input.setInputProcessor(stage);
}

@Override
publicvoiddispose() {
stage.dispose();
}

@Override
publicvoidpause() {
// TODO Auto-generated method stub

}

@Override
publicvoidrender() {
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
label.setWrappedText("FPS: " + Gdx.graphics.getFramesPerSecond(),
HAlignment.CENTER);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}

@Override
publicvoidresize(int width,int height) {
// TODO Auto-generated method stub

}

@Override
publicvoidresume() {
// TODO Auto-generated method stub

}
}

效果:

noyes

其他的UI大致用法差不多,显示的样式在对应的Style或者Skin中定义。但是要注意有些UI类需要手动设置width和height,不然有些显示会很奇怪的。

最后说一下Slider的用法。

SliderStyle需要一个NinePath和Texture,我最初没有想通为什么不是两个NinePath,仔细看一下源码才了解到,NinePath是作为背景,而Texture那个是中间的那个滑动的方块。

关于用配置文件设置Style的问题,google code的wiki上似乎没有写,但是在libgdx的论坛里面有,比如

somePatch1:[ { height: 13, width: 9, x: 761, y: 78 }, { height: 13, width: 1, x: 770, y: 78 }, { height: 13, width: 9, x: 771, y: 78 }, { height: 1, width: 9, x: 761, y: 91 }, { height: 1, width: 1, x: 770, y: 91 }, { height: 1, width: 9, x: 771, y: 91 }, { height: 13, width: 9, x: 761, y: 92 }, { height: 13, width: 1, x: 770, y: 92 }, { height: 13, width: 9, x: 771, y: 92 }]
或者
somePatch2:[ { height: 13, width: 9, x: 761, y: 78 },]

发表评论

电子邮件地址不会被公开。