Android游戲引擎libgdx使用教程5:常用UI類與舞臺(tái)
像按鈕、下拉框、列表、標(biāo)簽、圖片、復(fù)選框、編輯框、分割面板、滑動(dòng)面板、滑動(dòng)條等都是比較常用的UI類,它們都屬于Actor,可以很方便的納入到舞臺(tái)的管理中,而且都包含在com.badlogic.gdx.scenes.scene2d.ui包中,
其實(shí)仔細(xì)看看UI類的實(shí)現(xiàn)代碼不難發(fā)現(xiàn)其實(shí)它們都是大部分繼承自Widget或者Table,如果需要自定義UI可以繼承以上兩個(gè)類(它們繼承自Actor),這里要說(shuō)明一下libgdx的布局部分使用了TWL,有興趣的朋友可以去看看。
在介紹每個(gè)控件之前我們先來(lái)看一下NinePatch,這是最近的一個(gè)比較重大的更新。
何為NinePatch?其實(shí)android原生即有NinePatch類,常在按鈕中使用。
如圖,將圖片分成九份。中間部分可以根據(jù)需要擴(kuò)大,使按鈕的大小內(nèi)容變動(dòng)不受圖片的限制。
而在libgdx的NinePatch其實(shí)就是九個(gè)TextureRegion對(duì)象。
常用的實(shí)例化方法有兩個(gè):
public NinePatch (Texture texture, int left, int right, int top, int bottom)
public NinePatch (TextureRegion region, int left, int right, int top, int bottom)
關(guān)于其中的四個(gè)int型參數(shù)如何取值我們可以參考一下源碼:
- public NinePatch (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)};
- }
先計(jì)算中間部分的寬度和高度。然后開(kāi)始切圖,首先取頂部的最左邊的那個(gè),即圖中編號(hào)1的那塊,然后去它右邊的,然后再右邊的。
取完最上邊的那行,然后取中間的那行,然后取最后一行的。
由上自下,由左自右。
而在繪制時(shí)又是如何處理的呢?看源碼:
- public void draw (SpriteBatch batch, float x, float y, float width, float height) {
- float centerColumnX = x;
- if (patches[BOTTOM_LEFT] != null)
- centerColumnX += patches[BOTTOM_LEFT].getRegionWidth();
- else if (patches[MIDDLE_LEFT] != null)
- centerColumnX += patches[MIDDLE_LEFT].getRegionWidth();
- else if (patches[TOP_LEFT] != null) //
- centerColumnX += patches[TOP_LEFT].getRegionWidth();
- float rightColumnX = x + width;
- if (patches[BOTTOM_RIGHT] != null)
- rightColumnX -= patches[BOTTOM_RIGHT].getRegionWidth();
- else if (patches[MIDDLE_RIGHT] != null)
- rightColumnX += patches[MIDDLE_RIGHT].getRegionWidth();
- else if (patches[TOP_RIGHT] != null) //
- rightColumnX += patches[TOP_RIGHT].getRegionWidth();
- float middleRowY = y;
- if (patches[TOP_LEFT] != null)
- middleRowY += patches[TOP_LEFT].getRegionHeight();
- else if (patches[TOP_CENTER] != null)
- middleRowY += patches[TOP_CENTER].getRegionHeight();
- else if (patches[TOP_RIGHT] != null) //
- middleRowY += patches[TOP_RIGHT].getRegionHeight();
- float topRowY = y + height;
- if (patches[TOP_LEFT] != null)
- topRowY -= patches[TOP_LEFT].getRegionHeight();
- else if (patches[TOP_CENTER] != null)
- topRowY -= patches[TOP_CENTER].getRegionHeight();
- else if (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);
- }
先計(jì)算左右欄的寬度,在計(jì)算中間和頂部的高度。然后從下自上的繪制。說(shuō)實(shí)話我覺(jué)得這段代碼看著很好玩的。
現(xiàn)在來(lái)說(shuō)說(shuō)幾個(gè)常用的控件的使用吧。先構(gòu)建一個(gè)舞臺(tái)。
先來(lái)試試Label吧,label是有緩存的,所以替換顯示內(nèi)容不是用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;
- public class FirstGame implements ApplicationListener {
- private Stage stage;
- Label label;
- @Override
- public void create() {
- 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
- public void dispose() {
- stage.dispose();
- }
- @Override
- public void pause() {
- // TODO Auto-generated method stub
- }
- @Override
- public void render() {
- Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
- label.setWrappedText("FPS: "+Gdx.graphics.getFramesPerSecond(),
- HAlignment.CENTER);
- stage.act(Gdx.graphics.getDeltaTime());
- stage.draw();
- }
- @Override
- public void resize(int width, int height) {
- // TODO Auto-generated method stub
- }
- @Override
- public void resume() {
- // TODO Auto-generated method stub
- }
- }
效果:
然后再看看Button吧,實(shí)例化需要一個(gè)ButtonStyle,定義了按鈕三種狀態(tài)對(duì)應(yīng)的圖片樣式,按下和松開(kāi)時(shí)的X,Y偏移還有Button中文字繪制所需的BitmapFont和Color。
按鈕的三種狀態(tài)的圖片我就省了,只用一張圖片。
修改代碼如下:
- 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;
- public class FirstGame implements ApplicationListener {
- private Stage stage;
- Label label;
- Texture texture;
- Button button;
- @Override
- public void create() {
- 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
- public void dispose() {
- stage.dispose();
- }
- @Override
- public void pause() {
- // TODO Auto-generated method stub
- }
- @Override
- public void render() {
- Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
- label.setWrappedText("FPS: " + Gdx.graphics.getFramesPerSecond(),
- HAlignment.CENTER);
- stage.act(Gdx.graphics.getDeltaTime());
- stage.draw();
- }
- @Override
- public void resize(int width, int height) {
- // TODO Auto-generated method stub
- }
- @Override
- public void resume() {
- // TODO Auto-generated method stub
- }
- }
效果:
按鈕自然應(yīng)該有點(diǎn)擊事件,通過(guò)setClickListener來(lái)設(shè)置:
- button.setClickListener(new ClickListener() {
- @Override
- public void click(Actor actor) {
- Gdx.app.log("Info", "點(diǎn)擊事件觸發(fā)了");
- }
- });
然后再看看CheckBox。CheckBox的樣式定義在CheckBoxStyle中,需要4個(gè)參數(shù),兩種狀態(tài)的各一張圖片,一個(gè)BitmapFont和Color。
這里我再添加一張圖片:
原理差不多,直接貼代碼了。
- 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;
- public class FirstGame implements ApplicationListener {
- private Stage stage;
- Label label;
- Texture texture1;
- Texture texture2;
- CheckBox checkBox;
- @Override
- public void create() {
- 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
- public void click(Actor actor) {
- if (checkBox.isChecked) {
- checkBox.setText("Yes");
- } else {
- checkBox.setText("NO");
- }
- }
- });
- stage.addActor(checkBox);
- stage.addActor(label);
- Gdx.input.setInputProcessor(stage);
- }
- @Override
- public void dispose() {
- stage.dispose();
- }
- @Override
- public void pause() {
- // TODO Auto-generated method stub
- }
- @Override
- public void render() {
- Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
- label.setWrappedText("FPS: " + Gdx.graphics.getFramesPerSecond(),
- HAlignment.CENTER);
- stage.act(Gdx.graphics.getDeltaTime());
- stage.draw();
- }
- @Override
- public void resize(int width, int height) {
- // TODO Auto-generated method stub
- }
- @Override
- public void resume() {
- // TODO Auto-generated method stub
- }
- }
效果:
其他的UI大致用法差不多,顯示的樣式在對(duì)應(yīng)的Style或者Skin中定義。但是要注意有些UI類需要手動(dòng)設(shè)置width和height,不然有些顯示會(huì)很奇怪的。
最后說(shuō)一下Slider的用法
SliderStyle需要一個(gè)NinePath和Texture,我最初沒(méi)有想通為什么不是兩個(gè)NinePath,仔細(xì)看一下源碼才了解到,NinePath是作為背景,而Texture那個(gè)是中間的那個(gè)滑動(dòng)的方塊。
關(guān)于用配置文件設(shè)置Style的問(wèn)題,google code的wiki上似乎沒(méi)有寫(xiě),但是在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 },
- ]