TableLayout is a lightweight, multi-platform UI positioning library that uses grid-based structures similar to HTML tables. It integrates seamlessly with LibGDX, Swing, Android, and TWL, offering both Java API and external configuration file workflows. A JNLP-based visual editor is available to simplify layout design.
Working with External Layout Files
Configuration files allow visual editing and separate design from logic, though older LibGDX iterations may have partial support. Start by designing a basic game menu with a start and settings button in the editor, which produces a debug-enabled layout file:
debug
*spacing:14 padding:6 align:center
---
[LaunchButton] width:220 height:65
---
[OptionsButton] width:220 height:65
The debug flag draws visible cell borders for troubleshooting. Widget namess end with their type convention, and the second line sets global rules that align closely with CSS box model principles. Save this file as menu.layout in your internal assets directory.
To use it in LibGDX, first initialize a Stage and a Table container with a Skin, then register UI widgets by their configuration names and parse the file:
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.Table;
import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.TableLayout;
import com.badlogic.gdx.utils.viewport.StretchViewport;
public class GameMenu implements ApplicationListener {
private Stage uiStage;
@Override
public void create() {
uiStage = new Stage(new StretchViewport(800, 480));
Gdx.input.setInputProcessor(uiStage);
Skin uiSkin = new Skin(Gdx.files.internal("assets/uiskin.json"));
Table rootTable = new Table(uiSkin);
rootTable.setFillParent(true);
uiStage.addActor(rootTable);
TableLayout layoutManager = rootTable.getTableLayout();
TextButton launchBtn = new TextButton("Start Game", uiSkin);
layoutManager.register("LaunchButton", launchBtn);
TextButton optionsBtn = new TextButton("Settings", uiSkin);
layoutManager.register("OptionsButton", optionsBtn);
layoutManager.parse(Gdx.files.internal("layout/menu.layout").readString());
}
@Override
public void render() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
uiStage.act(Math.min(Gdx.graphics.getDeltaTime(), 1/30f));
uiStage.draw();
}
@Override
public void resize(int width, int height) {
uiStage.getViewport().update(width, height, true);
}
@Override
public void pause() {}
@Override
public void resume() {}
@Override
public void dispose() {
uiStage.dispose();
uiSkin.dispose();
}
}
Building Layouts with Java API
The Java API workflow mirrors configuration files, often starting with a design from the visual editor to map styles and structure. Global defaults replace the configuration's second line, table.add() inserts widgets, and table.row() starts a new grid row instead of ---:
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.tablelayout.Table;
import com.badlogic.gdx.utils.viewport.FitViewport;
public class APILayoutMenu implements ApplicationListener {
private Stage gameUI;
private Skin baseSkin;
@Override
public void create() {
gameUI = new Stage(new FitViewport(720, 405));
Gdx.input.setInputProcessor(gameUI);
baseSkin = new Skin(Gdx.files.internal("assets/uiskin.json"));
Table menuGrid = new Table(baseSkin);
menuGrid.setFillParent(true);
menuGrid.defaults().space(14).align(Align.center).pad(6);
TextButton startBtn = new TextButton("Begin Adventure", baseSkin);
menuGrid.add(startBtn).width(220).height(65);
menuGrid.row();
TextButton configBtn = new TextButton("Preferences", baseSkin);
menuGrid.add(configBtn).width(220).height(65);
gameUI.addActor(menuGrid);
}
@Override
public void render() {
Gdx.gl.glClearColor(0.1f, 0.1f, 0.15f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
gameUI.act(Gdx.graphics.getDeltaTime());
gameUI.draw();
}
@Override
public void resize(int width, int height) {
gameUI.getViewport().update(width, height, true);
}
@Override
public void pause() {}
@Override
public void resume() {}
@Override
public void dispose() {
gameUI.dispose();
baseSkin.dispose();
}
}
Integrating Custom Fonts into Skin
LibGDX’s default Skin relies on Hiero-ganerated bitmap fonts, but TrueType fonts can be substituted. One approach is to wrap an existing base skin and override font resources:
FreeTypeFontGenerator fontGen = new FreeTypeFontGenerator(Gdx.files.internal("fonts/notosans_cn.ttf"));
FreeTypeFontParameter fontParams = new FreeTypeFontParameter();
fontParams.size = 24;
fontParams.characters = FreeTypeFontGenerator.DEFAULT_CHARS + "你好世界开始游戏设置退出";
BitmapFont customFont = fontGen.generateFont(fontParams);
fontGen.dispose();
Skin baseSkin = new Skin(Gdx.files.internal("assets/uiskin.json"));
Skin modifiedSkin = new Skin();
modifiedSkin.add("default-font", customFont);
TextButton.TextButtonStyle btnStyle = baseSkin.get("default", TextButton.TextButtonStyle.class);
btnStyle.font = customFont;
modifiedSkin.add("default", btnStyle);