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

    Разбираясь дальше с библиотекой 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.
    • +9
    • 40,7k
    • 2
    Поделиться публикацией

    Комментарии 2

      0
      Спасибо. Думаю, статья поможет. Пишите ещё.
        0
        либа классная, но без слез ей пользоваться нельзя — вот сейчас что то автор поменял и больше image.y = 100; не работает. Примеров новых нет — опять все методом тыка.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое