Разбираясь дальше с библиотекой libGDX, я дошел до пакета com.badlogic.gdx.scenes.scene2d.ui. Этот пакет предназначен для создания пользовательського интерфейса. И тут меня ждало разочарование: статьи-туториала нет. Поэтому я решил самостоятельно разобраться с пакетом, используя исходники и Javadoc документацию. То есть, это будет туториал по scene2d.ui, но уже не перевод. Я не буду здесь детально описывать конструкторы, методы, приводить подробные сигнатуры. Я постараюсь взглянуть с высоты «птичьего полета», поскольку зная принципы, вы всегда сможете узнать больше из документации. Но даже при таком подходе материала слишком много, поэтому я разобью его на две (возможно больше) статей.
Вспомним предыдущую статью и то, что мы говорили про класс Stage. Класс Stage может содержать в себе актеров (Actor) и управлять ними. Также, есть класс Group, который является и актером, и контейнером для актером. Так вот, все визуальные компоненты из пакета scene2d.ua унаследованы или от Actor, или от Group. Соответсвенно, наследники Group могут содержать в себе другие компоненты. Эти компоненты мы рассмотрим позже, а сейчас взглянем на наследников Actor. Прямым наследником является класс Widget, а от него унаследуются следующие шесть компонентов: Label, TextField, Image, List, SelectBox, Slider.
Следует заметить, что все визуальные компоненты реализуют интерфейс Layout. Этот интерфейс нужен для правильного расположения компонентов в компонентах-контейнерах. Он предоставляет методы для перерисовки компонента, определения предпочтительного, минимального, максимального размера.
Еще одно важное понятие — стиль. Внутри у почти каждого графического компонента есть статический класс с названием <ИмяКомпонента>Style. Например, LabelStyle. В этом классе хранятся нужные для корректной работы этого класса ресурсы — например, шрифт, цвет шрифта и т.д. Дело в том, что OpenGL не может напрямую отрисовывать шрифты, поэтому нужны какие-то дополнительные механизмы. Очевидным вариантом является создание картинки с изображениями букв и текстового файла, который описывает, где какая буква расположена. Потом из этого описания можно сделать класс, который сможет «строить» строчки текста из отдельных букв-картинок. Вот пример класса LabelStyle из класса Label:
static public class LabelStyle {
public BitmapFont font;
/** Optional. */
public Color fontColor;
public LabelStyle () {
}
public LabelStyle (BitmapFont font, Color fontColor) {
this.font = font;
this.fontColor = fontColor;
}
}
Некоторые поля помечены комментарием как optional. Это значит, что их можно не описывать в описании стиля в файле настроек.
Для хранения стилей используется класс Skin. Класс Skin читает стили из файла из настройками и сохраняет их внутри себя. Затем при создании нового графического компонента мы передаем Skin ему в конструктор. Если есть подходящий стиль, он используется. Если нет — бросается исключение. Вот пример простого класса настроек, где определен стиль только для одного компонента — Label:
{
resources: {
com.badlogic.gdx.graphics.Color: {
black: { r: 0, g: 0, b: 0, a: 1 }
},
com.badlogic.gdx.graphics.g2d.BitmapFont: {
default-font: { file: default.fnt }
}
},
styles: {
com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle: {
default: {
font: default-font, fontColor: black
}
}
}
}
Некоторые комментарии. Этот файл написанный в формате JSON. Первая секция — это ресурсы. black — цвет, определенный в модели RGBA. default-font — название шрифта. Шрифт я взял из пакета com.badlogic.utils, где они назывались arial-15.fnt и arial-15.png. Вторая же секция — это непосредственно стили. Здесь мы определили один шрифт, которому указали шрифт и цвет шрифта. Заметьте, что названия полей в файле соответсвуют названиям публичных полей в классах стилей. Вы можете определить для каждого компонента несколько стилей с разными именами. Получить нужный стиль вы можете с помощью метода getStyle(Class type, String name) класса Skin. По умолчанию, вы передаете в конструктор компонента Skin и из него компонент берет стиль с названием default.
Для файла настроек нужно, чтобы в том же каталоге лежал одноименный файл-картинка, но с расширением png. Если, например, вы назвали файл настроек SimpleSkin, то графический файл будет называться SimpleSkin.png. Он необходим, если вы будете использовать ресурсы типа TextureRegion. Без этого файла программа выдаст исключение и не запустится.
Нужно заметить, что в данном файле шрифта нет русских букв. Я еще детально не разобрался в данном вопросе, поэтому русского текста в примерах не будет.
Перейдем теперь более подробно к отдельным компонентам, их стилям и использованию.
Label
Являет собой простую текстовую метку с возможностью переноса текста по словам. Имеет несколько конструкторов, самые важные, на мой взгляд, следующие два:
public Label (Skin skin)
— создает метку без текста.
public Label (String text, Skin skin)
— создает метку с текстом.
Имеются методы для установки и возвращения стиля — setStyle(), getStyle(), метод для установки текста setText() и метод для установки переноса по словам setWrapt(). По умолчанию, перенос по словам отключен. Чтобы использовать его, предварительно задайте предпочтительный размер метке (preferred size).
В остальном свойства компонента не представляют особого интереса, управление его расположением, вращением и т.д. осуществляется так же, как и классом Actor.
Класс стиля компонента. Как видно, ему требуется всего лишь шрифт и цвет шрифта.
...
static public class LabelStyle {
public BitmapFont font;
/** Optional. */
public Color fontColor;
public LabelStyle () {
}
public LabelStyle (BitmapFont font, Color fontColor) {
this.font = font;
this.fontColor = fontColor;
}
}
...
Соответсвующие этому классу ресурсы из файла настроек:
resources: {
com.badlogic.gdx.graphics.Color: {
black: { r: 0, g: 0, b: 0, a: 1 }
},
com.badlogic.gdx.graphics.g2d.BitmapFont: {
default-font: { file: default.fnt }
}
},
styles: {
com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle: {
default: {
font: default-font, fontColor: black
}
}
}
Пример использования:
...
Skin skin = new Skin(Gdx.files.internal("data/skins/SimpleSkin"));
Label label = new Label("I am label", skin);
label.x = 10;
label.y = 10;
stage.add(label);
...
TextField
Следующий класс — TextField. Являет собой поле для ввода текста. Этот класс уже любопытнее, поскольку ему можно назначить слушателя (listener). Библиотеку libGDX писали с использованием паттерна «подписчики-читатели», но ограниченным — вы можете установить только одного слушателя. Установка слушателя производится методом setTextFieldListener(), который принимает параметр типа TextFieldListener. Это интерфейс, вот его код:
...
static public interface TextFieldListener {
public void keyTyped (TextField textField, char key);
}
...
Как видно, обрабатывается одно событие — символ введен.
У класса есть несколько конструкторов. Опишем два основных, на мой взгляд:
TextField(String text, Skin skin)
— создает текстовое поле с текстом
TextField(Skin skin)
— создает пустое текстовое поле
Некоторые методы класса:
setText()
— устанавливает текст
setPasswordMode()
— вместо текста будут отображаться символы, которые вы выберете методом setPasswordCharacter()
setMessageText()
— устанавливает текст-подсказку, который будет отображаться, если вы ничего не ввели в текстовое поле.
TextField поддерживает копирование и вставку текста на ПК стандартными клавишами. На Android копирование и вставка не поддерживается из-за ограничений Android.
Класс стиля компонента. Как видите, обязательным параметром является лишь шрифт:
...
static public class TextFieldStyle {
/** Optional. */
public NinePatch background, cursor;
public BitmapFont font;
public Color fontColor;
/** Optional. */
public TextureRegion selection;
/** Optional. */
public BitmapFont messageFont;
/** Optional. */
public Color messageFontColor;
...
Ресурсы из файла настроек:
...
com.badlogic.gdx.scenes.scene2d.ui.TextField$TextFieldStyle: {
default: {
font: default-font, fontColor: black
}
}
...
И пример использования:
...
TextField textField = new TextField("I am text field", skin);
textField.y = 30;
stage.addActor(textField);
...
Image
Класс Image один из немногих, кто не требует стиля для своей работы. Поэтому его описание будет довольно кратко. Создать обьект этого класса можно несколькими конструкторами, один из наиболее простых — Image(TextureRegion region). Класс поддерживает слушателя типа ClickListener. Вот исходник этого интерфейса:
...
public interface ClickListener {
public void click (Actor actor, float x, float y);
}
...
То есть, мы можем обрабатывать события нажатия на наше изображение.
Полезным методом является setRegion(), который позволяет изменить изображение. Вы можете передать конструктору как текстуру, так и ее регион. Однако масштабирование, вращение будет работать лишь в случае региона текстуры.
Пример использования:
...
Image image = new Image(new Texture(Gdx.files.internal("data/skins/default.png")));
image.y = 100;
stage.addActor(image);
...
Здесь используется текстура в качестве параметра для конструктора. В реальных приложениях лучше использовать TextureRegion.
List
Класс List являет собой список текстовых полей с возможностью выбора определенного поля. Аналог List Swing, или ListBox из Delphi.
Традиционно он имеет несколько конструкторов, один из наиболее простых — List(Object[] items, Skin skin). Он создает обьект из списком из массива items. Узнать выделенный элемент вы можете методом getSelection(), а его индекс — getSelectedIndex(). Установить новый список можно методом setItems(), сделать какой-либо элемент выделенным можно или по имени или по индексу: setSelection(), setSelectionIndex(). List поддерживает слушателя SelectionListener. Исходник класса:
...
public interface SelectionListener {
public void selected (Actor actor, int index, String value);
}
...
Метод selected() вызывается при выборе элемента.
Вот класс стиля:
...
static public class ListStyle {
public BitmapFont font;
public Color fontColorSelected = new Color(1, 1, 1, 1);
public Color fontColorUnselected = new Color(1, 1, 1, 1);
public NinePatch selectedPatch;
...
Как мы видим, цвета уже проинициализированы, а вот шрифт и selectedPatch придется описывать. Маленькое отступление насчет NinePatch. Это обычное изображение (.png), в котором края размером в 1 пиксел содержат некую служебную информацию. Это изображение используется для выделенного элемента. Более подробно вы можете почитать здесь habrahabr.ru/post/113623/ Вот отрывки из файла настроек, что отвечают за наш List:
...
com.badlogic.gdx.graphics.g2d.NinePatch: {
default-nine :
[
{width: 100, height: 100, x: 0, y: 0}
]
}
...
com.badlogic.gdx.scenes.scene2d.ui.List$ListStyle: {
default: {
font: default-font, selectedPatch: default-nine
}
}
...
А вот и пример использования:
...
List list = new List(new String[] {"Item 1", "Item 2", "Item 3", "Item 4", "Item 5"}, skin);
list.x = 300;
stage.addActor(list);
...
SelectBox
Класс SelectBox являет собой выпадающий список из нескольких элементов. В неактивном состоянии это строчка, которая показывает выбранный элемент. Когда мы активируем его, нам показывается список элементов, которые можно выбрать нажатием. Имеет несколько конструкторов, самый простой — SelectBox(Object[] items, Skin skin) — создает выпадающий список элементов. Первый элемент считается выбранным. В остальном класс очень похож на List. Он поддерживает таких же слушателей как List — SelectionListener, имеет такие же методы для манипулирования элементами списка. Вот класс стиля:
...
static public class SelectBoxStyle {
public NinePatch background;
public NinePatch listBackground;
public NinePatch listSelection;
public BitmapFont font;
public Color fontColor = new Color(1, 1, 1, 1);
public float itemSpacing = 10;
...
Как мы видим, в обязательной инициализации нуждаются три NinePatch-a и шрифт.
Приведем отрывки из файла настроек:
...
com.badlogic.gdx.scenes.scene2d.ui.SelectBox$SelectBoxStyle: {
default: {
font: default-font, background: default-nine, listBackground: default-nine, listSelection: default-nine
}
}
...
Пример использования:
...
SelectBox selectBox = new SelectBox(new String[] {"Item 1", "Item 2", "Item 3"}, skin);
selectBox.x = 400;
stage.addActor(selectBox);
...
Slider
Последний класс-наследник Widget. Класс Slider являет собой ползунок с максимальным и минимальным значениями. Пользователь может изменять значение ползунка перетаскиванием. Класс имеет несколько конструкторов с заданием максимального, минимального значений, количества шагов. Наипростейшим является Slider(Skin skin). Он создаст ползунок с максимальным значением 100 и минимальным 0 с количеством градаций 100. Класс может обрабатывать события изменения значения с помощью класса типа ValueChangedListener:
...
static public interface ValueChangedListener {
public void changed (Slider slider, float value);
}
...
Когда вы изменяете значение ползунка, вызывается метод сhanged().
Класс стиля требует задания двух параметров:
...
static public class SliderStyle {
NinePatch slider; //Фон ползунка. Растягивается только по горизонтали.
TextureRegion knob; // Изображение части, которую мы передвигаем.
...
Вот куски кода из файла настроек:
...
com.badlogic.gdx.graphics.g2d.TextureRegion: {
default-region: {width: 10, height: 12, x: 0, y: 0}
}
...
com.badlogic.gdx.scenes.scene2d.ui.Slider$SliderStyle: {
default: {
slider: default-nine, knob: default-region
}
}
...
А вот пример использования:
...
Slider slider = new Slider(skin);
stage.addActor(slider);
...
Первая часть обзора пакета scene2d.ui завершена. Напомню, что мы рассмотрели только те классы, которые унаследованы от Widget. Остается еще большая половина класов-контейнеров — окна, списки, скроллируемые списки, панели и т. д. В их число входит и кнопка (Button). Как только появится время, я напишу продолжение, где рассмотрю остальные классы.
Приложение: проект с демонстрацией возможностей.
Приложение: диаграмма классов, составленная мной в программе ArgoUML.
Автор: 1nt3g3r