Туториал по libGDX — создание пользовательського интерфейса. Часть 1

в 16:06, , рубрики: android, java, libgdx, Разработка под android, метки: , ,

Разбираясь дальше с библиотекой 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js