Почему все строки в layout указываются косвенно из файла с ресурсами
Понимание, что используемый в программе текст это отдельный ресурс, такой же как изображения и звук, приходит на сразу. Но стоит несколько раз поменять имя программы в паре десятков файлов или заняться исправлением однотипной синтаксической ошибки в пяти, шести разных местах и необходимость хранить строки отдельно от кода становиться очевидной.
В Android работа со строковыми ресурсами сделана очень удобна и не вызывает поначалу никаких сложностей. В официальной документации она описана в статье String Resources. В файле project\res\values\strings.xml задаем строку и ее имя после чего в Activity загружаем строку по этому имени.
strings.xml
ProjectActivity.java
Эту же строку можно использовать в xml файле разметки формы (layout resource). Как это реализовано можно посмотреть, создав новый проект и открыв файл main.xml.
Описание ресурсов
Загрузка строк
Если надо задать текст из ресурсов одному из элементов интерфейса, то нет необходимости заранее его загружать. Вторая и третья строка в приведенном примере работают совершенно одинаково.
Метод getString удобно использовать, когда в строку из ресурсов нужно внести дополнительные данные перед дальнейшим использованием. В этом случае в ресурсы помещается форматированная строка и дополнительные параметры для нее указываются прямо в getString, без дополнительного обращения к методу String.format. Примеры разделенные чертой приводят к одним и тем же результатам:
Для формата строки используется следующая конструкция: %X$F.
X$ — номер подставляемого параметра. В основном тексте они обычно идут по порядку 1$, 2$, но в локализованных ресурсах могут меняться местами. Также позволяется использовать один парметр в строке несколько раз.
F — обычный идентификатор формата, такой 's', 'd'. Их полный список, включая форматрирование даты, описан в документации класса Formatter
Если в строке используется только один параметр, то X$ можно опустить. Если считать параметры неудобно, а проблем с локализацией не предвидится, то можно вернуться к стандартной схеме формата строки — для этого в описание элемента нужно добавить атрибут formatted со значением false. Следующие две строки форматируют текст одинаковым образом:
Загрузить строку по ее имени можно также как и любой другой ресурс. Для этого сначала нужно с помощью метода getIdentifier по имени строки найти ее id, а с этим номером уже работать обычным сопособом. Следующий пример загружает строку с именем «score_correct».
Android позволяет хранить в ресурсах массивы строк. Для этого используется тег string-array, который содержит внутри элементы item с конкретными строками. Вот сокращенный пример из документации Android, который иллюстрирует задание массива.
strings.xml
Кроме очевидного применения — удобная загрузка данных, string-array часто используют для иницализации UI элементов с выпадающим списком значений: Spiner и ListPreference. В этом случае обычно требуется использовать одну из строк массива, как значение по умолчанию. Сослаться в ресурсах на конкретный элемент массива мы не можем, но из ситуации можно выйти задействовав псевдонимы для ресурсов.
Элементы массива инициализируются, как обычные строки, а элементы item содержат только ссылку на них. Такая инициализация на самом деле черезвычайно удобна и я использую ее даже в тех случаях, когда обращение к конкретной строке не планируется — это гарантирует, что при локазации все массивы будут одинакового размера, даже если часть строк в них не будет переведена. Само описание массива при этом удобно вынести в отдельный ресурсный файл.
Переписанный с использованием псевдонимов пример выглядит так:
strings.xml
Полезное замечание: до версии Android 2.3 в реализации загрузки string-array была ошибка, которая позволяла загружать максимум только 512 элементов.
Системные строки
Локализация строк
Базовый механизм локализации ресурсов в Android позволяет легко решить практически все задачи с локализацией строк. Полное описание в документации Android можно прочесть здесь. В ресурсах вам необходимо создать новую папку с именем values-xx и поместить туда файлы со строковыми ресурсами из базовой папки values. xx — это двухсимвольный идентификатор языка (список поддерживаемых значений приведен в конце текста). После этого необходимые строки нужно перевести, остальные удалить.
Для более тонкой локализации можно задействовать механизм задания региона. К примеру, у вас в программе есть соглашение пользователя, переведнное на французкий язык, но в нем есть отличия между версией для Франции и версией для Канады. Чтобы реализовать эти отличия надо к папке values-fr добавить название региона в формате rYY, где YY — это двухсимвольное название региона. В данном примере получатся папки values-fr-rFR и values-fr-rCA. В них следует поместить ресурсный файл с необходимой версией соглашения пользователя, а все остальные строки на французском языке оставить в папке values-fr.
Регионы и язык можно указывать без всякой связи друг с другом. Так в папке values-ru-rJP будут храниться русские тексты для жителей Японии.
values/strings.xml
values-no/strings.xml
values-nb/strings.xml
Вы можете создавать ресурсы с какими угодно кодами для языка и для региона. Если операционная система не найдет нужного региона, то она возмет значения для текущего языка. Если не сможет найти язык, то возмет значения по умолчанию из папки values.
Можно загрузить находящиеся в ресурсах строку для языка и региона отличных от установленных на устройстве. Для этого надо создать новый ресурс и задать ему необходимую локаль. Следующий пример загружает строку для французкого языка.
Вы также можете программно задать произвольный язык для всего приложения, но рассмотрение этой задачи выходит за рамки рассматриваемой темы.
Приложение
Поддерживаемые языки до Android 2.3
Английский (en), Голландский (nl), Испанский (es), Итальянский (it), Китайский (zh), Корейский (ko), Немецкий (de), Немецкий (de), Польский (pl), Русский (ru), Французский (fr), Чешский (cs), Японский (ja)
Поддерживаемые языки начиная с Android 2.3
Арабский (ar), Болгарский (bg), Венгерский (hu), Вьетнамский (vi), Греческий (el), Датский (da), Иврит (iw), Индонезийский (in), Каталонский (ca), Латышский (lv), Литовский (lt), Норвежский-Букмол (nb), Португальский (pt), Румынский (ro), Сербский (sr), Словацкий (sk), Словенский (sl), Тагальский (tl), Тайский (th), Турецкий (tr), Украинский (uk), Финский (fi), Хинди (hi), Хорватский (hr), Шведский (sv)
Поддерживаемые регионы до Android 2.3
Австралия (AU), Австрия (AT), Бельгия (BE), Британия (GB), Германия (DE), Испания (ES), Италия (IT), Канада (CA), КНР (CN), Корея (KR), Лихтенштейн (LI), Нидерланды (NL), Новая Зеландия (NZ), Польша (PL), Россия (RU), Сингапур (SG), США (US), Тайвань (TW), Франция (FR), Чешская республика (CZ), Швейцария (CH), Япония (JP)
Поддерживаемые регионы начиная с Android 2.3
Болгария (BG), Бразилия (BR), Венгрия (HU), Вьетнам (VN), Греция (GR), Дания (DK), Египет (EG), Зимбабве (ZA), Израиль (IL), Индия (IN), Индонезия (ID), Ирландия (IE), Латвия (LV), Литва (LT), Норвегия (NO), Португалия (PT), Румыния (RO), Сербия (RS), Словакия (SK), Словения (SI), Таиланд (TH), Турция (TR), Украина (UA), Филиппины (PH), Финляндия (FI)Болгария (BG), Бразилия (BR), Венгрия (HU), Вьетнам (VN), Греция (GR), Дания (DK), Египет (EG), Зимбабве (ZA), Израиль (IL), Индия (IN), Индонезия (ID), Ирландия (IE), Латвия (LV), Литва (LT), Норвегия (NO), Португалия (PT), Румыния (RO), Сербия (RS), Словакия (SK), Словения (SI), Таиланд (TH), Турция (TR), Украина (UA), Филиппины (PH), Финляндия (FI), Хорватия (HR), Швеция (SE)
Update: Добавлен раздел о массивах строк, локализации строк, системных строках. Убран пример с использованием ресурсов для передачи данных между различными Activity.
При разработке первых приложений мы уже встречали элемент LinearLayout, который позволяет группировать дочерние элементы в одну линию в автоматическом режиме. Существуют и другие типы контейнеров, позволяющие располагать элементы разными способами. Пришло время познакомиться с ними поближе.
Компоновка (также используются термины разметка или макет) хранится в виде XML-файла в папке /res/layout. Это сделано для того, чтобы отделить код от дизайна, как это принято во многих технологиях (HTML и CSS). Кроме основной компоновки для всего экрана, существуют дочерние элементы компоновки для группы элементов. По сути, компоновка – это некий визуальный шаблон для пользовательского интерфейса вашего приложения, который позволяет управлять элементами управления, их свойствами и расположением. В своей практике вам придется познакомиться со всеми способами размещения. Поэтому здесь мы рассмотрим только базовую часть теории, чтобы вы поняли саму сущность разметки. Кроме того, разметку можно создавать программным способом, который будет описан в конце статьи. Если вы будет обращаться к элементам управления через Java-код, то необходимо присваивать элементам уникальный идентификатор через атрибут android:id. Сам идентификатор назначается через выражение @+id/your_value. После этого вы можете обращаться к элементу через код при помощи метода findViewById(R.id.your_value).
Android Studio включает в себя специальный редактор для создания разметки двумя способами. Редактор имеет две вкладки: одна позволяет увидеть, как будут отображаться элементы управления, а вторая – создавать XML-разметку вручную.
Создавая пользовательский интерфейс в XML-файле, вы можете отделить представление приложения от программного кода. Вы можете изменять пользовательский интерфейс в файле разметки без необходимости изменения вашего программного кода. Например, вы можете создавать XML-разметки для различных ориентаций экрана мобильного устройства (portrait, landscape), размеров экрана и языков интерфейса.
Каждый файл разметки должен содержать только один корневой элемент компоновки, который должен быть объектом View или ViewGroup. Внутри корневого элемента вы можете добавлять дополнительные объекты разметки или виджеты как дочерние элементы, чтобы постепенно формировать иерархию элементов, которую определяет создаваемая разметка.
Виды разметок
Существует несколько стандартных типов разметок:
Все описываемые разметки являются подклассами ViewGroup и наследуют свойства, определённые в классе View.
Layout-файл в виде XML
Открыв файл main.xml, вы видите его визуальное представление. Т.е. некий предпросмотр, как это будет выглядеть на экране. Снизу вы можете видеть две вкладки – Graphical Layout и main.xml. Откройте main.xml
Мы видим достаточно читабельное xml-описание всех View нашего layout-файла. Названия xml-элементов - это классы View-элементов, xml-атрибуты - это параметры View-элементов, т.е. все те параметры, что мы меняем через вкладку Properties. Также вы можете вносить изменения прямо сюда и изменения будут отображаться в Graphical Layout. Например изменим текст у TextView. Вместо ссылки на константу, вставим свой текст «Какой-то текст»
Сохраняем. Открываем Graphical Layout и наблюдаем изменения.
Обычно авторы учебников дают содержание layout-файлов именно в xml виде. Это удобно – вы можете просто скопировать фрагмент и использовать, и не надо вручную добавлять View-элементы, бегать по Properties и настраивать все руками. Я буду делать в своих проектах также.
Layout-файл при смене ориентации экрана
По умолчанию мы настраиваем layout-файл под вертикальную ориентацию экрана. Но что будет если мы повернем смартфон и включится горизонтальная ориентация? Давайте смотреть.
Изменим myscreen.xml. Добавим вертикальный ряд кнопок и изменим надпись.
xml-код (вы можете скопировать его и вставить в ваш файл):
android:orientation = "vertical"
android:layout_width = "match_parent"
android:layout_height = "match_parent" >
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "Вертикальная ориентация экрана" >
android:layout_width = "match_parent"
android:id = "@+id/linearLayout1"
android:orientation = "vertical" >
android:text = "Button1"
android:id = "@+id/button1"
android:layout_height = "100dp"
android:layout_width = "100dp" >
android:text = "Button2"
android:id = "@+id/button2"
android:layout_height = "100dp"
android:layout_width = "100dp" >
android:text = "Button3"
android:id = "@+id/button3"
android:layout_height = "100dp"
android:layout_width = "100dp" >
android:text = "Button4"
android:id = "@+id/button4"
android:layout_height = "100dp"
android:layout_width = "100dp" >
Обратите внимание - я добавил вертикальный LinearLayout и поместил в него 4 кнопки. Подробнее обсудим это на следующем уроке.
Запустим приложение. В вертикальной ориентации все ок.
Нажмем в эмуляторе CTRL+F12, ориентация сменилась на горизонтальную и наши кнопки уже не влезают в экран (эмулятор версии 2.3.3 глючит и смена ориентации срабатывает не всегда)
Т.е. нам необходим еще один layout-файл, который был бы заточен под горизонтальную ориентацию и в нашем случае вывел бы кнопки горизонтально.
Но как дать знать Activity, что она в вертикальной ориентации должна использовать один layout-файл, а в горизонтальной – другой? Об этом за нас уже подумали создатели Андроид. Необходимо создать папку res/layout-land, а в ней создать layout файл с тем же именем, что и основной. Этот файл будет использован в горизонтальной ориентации.
Создаем папку: правой кнопкой на res, New > Folder, Folder name = layout-land, жмем Finish.
Далее создадим файл res/layout-land/myscreen.xml и настроим его под горизонтальную ориентацию. Аналогично, как и в первый раз, жмем кнопку создания файла. Откроется визард.
Вводим имя файла: myscreen.xml
Визард может ругнуться, что есть уже такой файл - The destination file already exists. Это он углядел ранее созданный файл res/layout/myscreen.xml. Нам надо ему сообщить, что новый файл предназначен для папки res/layout-land, а не res/layout. Жмем Next
А здесь уже руками внизу допишите приставку -land, чтобы получилось res/layout-land
Как вариант, можно было руками не дописывать, а добавить из левого столбца в правый атрибут Orientation и указать ему значение Landscape. Визард все понял бы и сам дописал к пути приставку -land.
А мы сами дописали путь и визард сам добавил атрибут направо.
Жмем Finish и получаем готовый файл.
Поместите этот xml-код в файл и сохраните файл, чтобы получить такую же картинку, как выше:
android:orientation = "vertical"
android:layout_width = "match_parent"
android:layout_height = "match_parent" >
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "Горизонтальная ориентация экрана" >
android:layout_width = "match_parent"
android:id = "@+id/linearLayout1"
android:orientation = "horizontal" >
android:text = "Button1"
android:id = "@+id/button1"
android:layout_height = "100dp"
android:layout_width = "100dp" >
android:text = "Button2"
android:id = "@+id/button2"
android:layout_height = "100dp"
android:layout_width = "100dp" >
android:text = "Button3"
android:id = "@+id/button3"
android:layout_height = "100dp"
android:layout_width = "100dp" >
android:text = "Button4"
android:id = "@+id/button4"
android:layout_height = "100dp"
android:layout_width = "100dp" >
Запустим приложение. Activity читает layout-файл, который мы указывали в методе setContentView, т.е. myscreen.xml и отображает его содержимое. Переключим ориентацию CTRL+F12. Activity понимает, что находится в горизонтальной ориентации, ищет в папке layout-land файл myscreen.xml и использует уже его.
В этом уроке мы:
разобрали, откуда Activity знает, какой layout-файл надо читать и настроили его на чтение другого файла
рассмотрели layout-файл с другого ракурса – XML
узнали, какой layout-файл используется при смене ориентации экрана (горизонтальная/вертикальная)
В следующем уроке:
изучим основные виды layout: LinearLayout, TableLayout, RelativeLayout, AbsoluteLayout
( пользоваться не рекомендуется, deprecated )
AbsoluteLayout — означает что каждый элемент верстки будет иметь абсолютную позицию относительно верхнего левого угла экрана задаваемую с помощью координат x и y. Т.е. верхнийлевый угол экрана при AbsoluteLayout имеет координаты x = 0, y = 0.
Позиция указывается в атрибутах элемента android:layout_x и android:layout_y.
Пример кода:
FrameLayout — тип верстки внутри которого может отображаться только один элемент в строке. Т.е. если внутри FrameLayout вы поместите несколько элементов, то следующий будет отображаться поверх предыдущего.
Пример кода:
LinearLayout — тип верстки при котором область верстки делится на строки и в каждую строку помещается один элемент. Разбиение может быть вертикальное или горизонтальное, тип разбиения указывается в атрибуте LinearLayout android:orientation. Внутри верстки возможно комбинировать вертикальную и горизонтальную разбивки, а кроме того, возможна комбинация нескольких разных типов верстки например использование LinearLayout внутри FrameLayout.
Пример вертикальной разбивки LinearLayout:
Пример горизонтальной разбивки LinearLayout:
Комбинация нескольких LinearLayout:
- android:layout_alignParentBottom – Низ элемента находится внизу контейнера
- android:layout_alignParentLeft – Левая часть элемента прилегает к левой части контейнера
- android:layout_alignParentRight – Правая часть элемента прилегает к правой части контейнера
- android:layout_alignParentTop – Элемент находится в верхней части контейнера
- android:layout_centerHorizontal – Элемент позиционируется по центру относительно горизонтального размера контейнера
- android:layout_centerInParent – Элемент позиционируется по центру относительно горизонтального и вертикального размеров размера контейнера
- android:layout_centerVertical – Элемент позиционируется по центру относительно вертикального размера контейнера
android:layout_above – Распологает элемент над указанным
android:layout_below – Распологает элемент под указанным
android:layout_toLeftOf – Распологает элемент слева от указанного
android:layout_toRightOf – Распологает элемент справа от указанного
Выравнивание относительно других элементов.
android:layout_alignBaseline – Выравнивает baseline элемента с baseline указаннго элемента
android:layout_alignBottom – Выравнивает низ элемента по низу указанного элемента
android:layout_alignLeft – Выравнивает левый край элемента с левым краем указанного элемента
android:layout_alignRight – Выравнивает правый край элемента с правым краем указанного элемента
android:layout_alignTop – Выравнивает верхнюю часть элемента в соответствие с верхней частью указанного элемента
TableLayout — табличная верстка.
Организует элементы в строки и столбцы таблицы.
Для организации строк служит таг
Alternate Layouts - альтернативная верстка. Позволяет использовать различную верстку для различных ориентаций экрана.
XML для альтернативной верстки помещается в папки проекта:
res/layout-land – альтернативная верстка для landscape UI
res/layout-port –альтернативная верстка для portrait UI
res/lauout-square – альтернативная верстка для square UI
и перед тем как получить макет из res/lauout система проверяет наличие файлов в этих папках.
И в завершении немного о стилях.
Во первых стили элемента могут быть описаны в атрибутах самого элемента.
Например:
Кроме того стили можно вынести в отдельный xml файл и сохранить его в папке res/values/
Напимер:
Если мы вынесем стили в отдельный файл, то для описания стилей элемента будем использовать атрибут style.
Заворачиваем в фрагменты
Фрагмент, как и активность, состоит из разметки и класса. Сначала займёмся разметкой.
Логически экран можно разделить на две части - верхняя неизменяемая часть с кнопками и нижняя часть с текстовым блоком и контейнером для картинки, которая изменяет свой вид в зависимости от нажатой кнопки.
Создадим две отдельные разметки и скопируем нужные части из общей разметки в разметки для фрагментов. Делаем щелчок правой кнопкой мыши на папке res/layout и выбираем New | Layout Resource File.
Создаём новый файл fragment1.xml и размещаем верхнюю часть кода:
Также поступаем со вторым фрагментом - создаём новый файл fragment2.xml и в него копируем код из нижней части кода.
В обоих случаях мы вставляли код в корневой контейнер LinearLayout. Но если у вас была бы более сложная разметка с использованием контейнерных элементов, то вы могли бы копировать сразу готовый кусок кода без необходимости оборачивать его корневым элементом, как в нашем случае.
Пока мы создали разметки для будущих фрагментов. Теперь нужно создать отдельные классы для двух фрагментов. Для начала укажем, что наш класс должен наследоваться от класса Fragment. Не копируйте, а пишите код самостоятельно. Я создаю классы вручную с нуля, можно также воспользоваться готовым шаблоном Fragment (Blank), которым пользовались в первой части.
Следите, чтобы импортировался класс androidx.fragment.app.Fragment, а не устаревшие классы.
Самостоятельно создайте класс для второго фрагмента Fragment2 по такому же принципу.
Настало время подключить разметки к фрагментам. В активностях мы подключали разметку в методе onCreate() через метод setContentView(). В фрагментах метод onCreate() служит для других задач. А для подключения разметки используется отдельный метод onCreateView().
Чтобы долго не искать нужный нам метод, просто вводите на клавиатуре первую и заглавные буквы метода - ocv . Такой комбинации соответствует только один метод, который нам и нужен. Нажимаем кнопку OK и в код фрагмента будет вставлен следующий шаблон:
У метода используются три параметра. В первом параметре используется объект класса LayoutInflater, который позволяет построить нужный макет, считывая информацию из указанного XML-файла. Удалим строчку, которая возвращает результат и напишем свой вариант.
В Java-варианте код разбит на две части. Сначала мы получаем объект View, а затем уже его возвращаем в методе (не обязательно).
Скопируйте код метода onCreateView() и вставьте его в код класса Fragment2, не забыв указать разметку R.layout.fragment2.
Остальные два параметра container, false используются в связке и указывают на возможность подключения фрагментов в активность через контейнер динамически. Мы обойдёмся без динамики, а создадим собственные блоки для фрагментов, поэтому у нас используется значение false.
Возвращаемся к главной разметке активности. Смело удаляем все элементы с экрана, чтобы остался только корневой элемент LinearLayout.
В старых версиях студии на панели инструментов был готовый компонент .
Сейчас компонент убрали, поэтому напишем код вручную в режиме Code.
Обратите внимание на атрибут tools:layout="@layout/fragmentX" у тегов fragment, они помогут отобразить содержимое фрагментов в режиме дизайна.
Если сейчас запустим приложение, то тоже никаких изменений не увидим. Зачем тогда потратили столько времени на создание фрагментов? Непонятно.
А, я понял. Можно теперь писать в резюме про свои умения: использую фрагменты.
Однако, продолжим. Если повернуть устройство в альбомную ориентацию, то программа будет выглядеть не слишком красиво.
Мы знаем, что можно создать отдельную папку res/layout-land (перечитайте урок Ориентация) и разместить там разметку для такого случая.
Небольшой лайфхак: в режиме Design у файла activity_main.xml щёлкните на значке Orientation for Preview и в выпадающем списке выберите пункт Create Tablet variation - файл res/layout-land/activity_main.xml будет создан автоматически.
Скопируем файл activity_main.xml и вставим его в новую папку.
Скорее всего вы не видите созданную папку, но она есть! Переключитесь в режим Project и заново откройте структуру проекта, найдя нужную папку. В дальнейшем оставайтесь в этом режиме для данного урока.
Изменим разметку фрагмента для альбомной ориентации.
Не забывайте в имени фрагментов использовать свои названия пакетов. Совсем другое дело. Теперь в альбомной ориентации приложение выглядит намного лучше.
Но это мы могли сделать и без фрагментов. Зачем же они нужны? Пока версия с лишней строчкой в резюме остаётся основной - чтобы работодатель уважал за прогрессивный стиль.
Хотя небольшое удобство есть. Благодаря модульности, мы поменяли разметку только у фрагментов, а то, что было внутри фрагментов (кнопки, текстовые блоки и т.д.), мы не трогали.
Как уже говорилось, фрагменты были придуманы для того, чтобы обеспечить быстрое написание приложения под разные типы экранов - для смартфонов и планшетов. Часто бывает так, что на смартфоне на первом экране находится список, а когда пользователь нажимает на отдельный элемент списка, то запускается отдельная активность. А на планшете можно уместить список и дополнительные данные на одном экране, как можно увидеть на нашем последнем примере с альбомной ориентацией.
Давайте подключим поддержку планшетов. Создадим новую папку layout-sw600dp и скопируем в него файл из папки layout-land. Идентификатор sw600 говорит о минимальной ширине 600dp, что соответствует 7-дюймовым планшетам в альбомной ориентации. Существуют и другие варианты для планшетов с большими размерами.
Тут возникает небольшая проблема - если нам понадобится что-то изменить в разметке для альбомной ориентации, то придётся редактировать файлы во всех папках. Но есть выход из этой ситуации - использовать псевдонимы.
Мы можем создать одну копию разметки и указать, чтобы её использовали все нужные размеры устройств.
Делаем следующее. В папке layout-land переименовываем файл activity_main.xml в activity_main_wide.xml (Refactor | Rename) и перемещаем файл в папку layout. Пустую папку layout-land можно удалить.
Теперь создайте новую папку res/values-land. В созданной папке создаём новый файл refs.xml (имя не имеет значения, но так принято).
Этот файл говорит, что в альбомной ориентации вместо ресурса activity_main следует подключать ресурс layout/activity_main_wide. Можете запустить приложение и убедиться, что ничего не изменилось.
Если у вас будет поддержка альбомных ориентаций для разных размеров планшетов, то просто копируйте файл refs.xml в папки типа values-720dp_land и др.
Теперь вы можете вносить изменения в одном файле activity_main_wide.xml, а не по отдельности в каждом файле.
В первой части мы узнали, что для создания фрагмента необходимо создать разметку, затем новый класс и в методе onCreateView() указать разметку. Затем в разметке активности указать тег fragment и присвоить ему имя класса фрагмента.
Поговорим о важном моменте. Вы можете установить связь между двумя фрагментами напрямую, чтобы при нажатии кнопки в первом фрагменте менялось содержимое во втором фрагменте. Но это неправильный подход, так как теряется смысл модульности фрагментов. Фрагменты ничего не должны знать о существовании друг друга. Любой фрагмент существует только в активности и только активность через свой специальный менеджер фрагментов должна управлять ими. А сами фрагменты должны реализовать необходимые интерфейсы, которые активность будет использовать в своих целях.
В первом фрагменте имеются кнопки. Добавим обработчик нажатий кнопок (такой же код вы могли использовать в активности, всё знакомо):
Подключаем кнопки в методе onViewCreated. Код будет похож на код, который мы обычно используем в методе onCreate() у активности, только метод findViewById() будет относиться уже не к классу Activity (обычно, мы опускали это), а к корневому элементу разметки фрагмента, в нашем случае view/rootView. В Java-варианте используется старый пример до появления метода onViewCreated(). Раньше приходилось писать код в onCreateView().
Запустите пример и проверьте. Но у нас три кнопки. Надо написать код, который бы получал информацию о нажатой кнопке, чтобы активность могла использовать эту информацию и использовать её для управления вторым фрагментом. Для удобства создадим в классе Fragment1 отдельный метод, который на основании идентификатора кнопки создаст нужный индекс:
Каждой кнопке соответствует свой индекс от 1 до 3.
Фрагмент всегда может узнать, в какой активности он находится, через метод getActivity(). В методе makeText() мы уже воспользовались данным методом, так как в фрагментах нет метода getApplicationContext().
Перепишем код для щелчка кнопки, чтобы узнать индекс нажатой кнопки.
Теперь мы умеем определять индекс нажатой кнопки. Но пока эта информация доступна только самому фрагменту. Наша задача - передать эту информацию активности, которая затем передаст её другой активности.
Для этой цели используются интерфейсы.
Открываем код первого фрагмента Fragment1 и объявляем интерфейс с единственным методом до объявления самого класса Fragment1:
Интерфейс не определяет работу метода, а только даёт ему имя. Класс, который будет использовать данный интерфейс, должен придумать, что делать в методе с данным именем.
У нас интерфейс будет использовать класс активности.
Переходим в класс активности и добавляем интерфейс OnSelectedButtonListener, который следует реализовать.
Среда разработки поможет создать заготовку для необходимого метода:
В этом методе надо написать такой код, чтобы активность получила индекс нажатой кнопки и передала информацию другому фрагменту, которая должна выполнить свою работу.
Но сначала подготовим второй фрагмент к работе. Объявим ссылки на компоненты, которые есть в разметке второго фрагмента. А также загрузим массив строк из ресурсов, который будем использовать для описания котов. Не забывайте, что в Java-варианте используется устаревший код.
Массив задаём в ресурсах (файл res/values/strings.xml). Так как первый элемент массива идёт под индексом 0, то добавим нейтральный текст:
Подготовим метод, который будет менять содержимое фрагмента в зависимости от индекса нажатой кнопки:
Осталось только получить информацию от активности (не от фрагмента) об индексе.
Опять возвращаемся в активность и напишем код для пустого метода onButtonSelected(), который будет получать от первого фрагмента индекс нажатой кнопки и передавать его второму фрагменту:
Активность получает доступ к своим фрагментам через специальный менеджер фрагментов (коты называют его манагером). Менеджер есть у любой активности, поэтому мы его не создаём через конструкцию new FragmentManager, а получаем через метод getSupportFragmentManager().
Менеджер фрагментов держит в руках все нити управления над своими фрагментами. Найти нужный фрагмент можно по идентификатору через метод FragmentManager.findFragmentById(), который похож на метод findViewById() для получения идентификатора кнопки, метки и т.д. У менеджера есть ещё один метод для поиска фрагмента по тегу findFragmentByTag().
В созданной заготовке вызываем менеджер фрагментов, получаем ссылку на второй фрагмент через его идентификатор и вызываем его метод setDescription().
Во втором фрагменте у нас нет надобности создавать интерфейс, так как фрагменту не нужно ничего сообщать активности. Он исполняет пассивную роль и ему нужно только получить данные для работы.
Но теперь в методе onClick() мы можем получить доступ к слушателю активности.
По цепочке мы передаём информацию от первого фрагмента в активность, а затем активность передаёт информацию во второй фрагмент.
Если посмотреть на код двух фрагментов, то увидим, что они полностью независимы и не обращаются ни конкретно к друг другу, ни к определённой активности. Принцип модульности соблюдён. Вы можете добавить любой из этих фрагментов в любую новую активность и при этом вам не придётся менять код в самих фрагментах. Весь необходимый функционал в фрагментах уже прописан.
Запустите проект и проверьте на работоспособность. Для данного случая мы пока не получили никаких преимуществ в использовании фрагментов. Но сейчас главное для вас - понять основные принципы создания и взаимодействия фрагментов.
Спустя несколько лет возможности фрагментов расширились и теперь передавать данные можно проще.
На прошлом уроке мы выяснили, что Activity читает layout-файл и отображает то, что в нем сконфигурировано. Теперь выясним, откуда Activity знает, какой именно layout-файл читать. А еще в этом уроке находится подсказка, как зарегиться на нашем форуме ;)
В этом уроке мы будем поворачивать экран. Эмулятор версии 2.3.3, который мы успешно применяли и будем применять для тестирования наших приложений, имеет глюк. Он не хочет корректно поворачивать экран. Поэтому вспомните, плз, урок 3 и создайте еще один эмулятор версии 2.2, например. Соответственно при создании проекта, тоже указываем версию 2.2. Скрины сделаны на версии 2.3.3 - не обращайте внимания на это.
Создадим новый проект:
Project name: P0051_LayoutFiles
Build Target: Android 2.2
Application name: LayoutFiles
Package name: ru.startandroid.develop.LayoutFiles
Create Activity: MainActivity
При разработке каждому Activity сопоставляется одноименный java-класс (наследник класса android.app.Activity). При запуске приложения, когда система должна показать Activity и в дальнейшем работать с ним, она будет вызывать методы этого класса. И от того, что мы в этих методах накодим, зависит поведение Activity.
При создании проекта мы указывали, что надо создать Activity с именем MainActivity
При использовании старого визарда это выглядело так:
В новом визарде - чуть по другому. На одном экране галка, на другом - имя:
Мы попросили создать Activity и Eclipse создал нам соответствующий класс (в дальнейшем мы научимся их создавать самостоятельно).
Давайте посмотрим этот класс: откроем двойным кликом файл src/ru.startandroid.develop.LayoutFiles/MainActivity.java
В нашем классе мы видим, что реализован метод onCreate – он вызывается, когда приложение создает и отображает Activity (на onCreateOptionsMenu пока не обращаем внимания). Посмотрим код реализации.
Первая строка:
super.onCreate(savedInstanceState); – конструктор родительского класса, выполняющий необходимые процедуры, его мы не трогаем.
Нас интересует этот код:
setContentView(R.layout.main);
Метод setContentView(int) – устанавливает содержимое Activity из layout-файла. Но в качестве аргумента мы указываем не путь к нашему layout-файлу (res/layout/main.xml), а константу, которая является ID файла. Это константа генерируется автоматически здесь gen/ru.startandroid.develop.LayoutFiles/R.java.
Этот файл можно открыть и посмотреть, но менять его не стоит. В R классе будут храниться сгенерированные ID для всех ресурсов проекта (из папки res/*), чтобы мы могли к ним обращаться. Имена этих ID-констант совпадают с именами файлов ресурсов (без расширений).
Откроем res/layout/main.xml, посмотрим, что там
Запустим приложение и посмотрим что оно нам покажет
Все верно - Activity отобразил то, что прописано в main.xml.
Попробуем отобразить содержимое другого файла. Создадим еще один layout-файл, например myscreen.xml. Для этого выделим папку res/layout в нашем проекте и нажмем кнопку создания нового файла
В поле File вводим имя файла: myscreen.xml и жмем Finish.
Новый layout-файл должен сразу открыться. Добавим TextView и через Properties изменим его текст на: «Этот экран описан не в main.xml, а в myscreen.xml».
При этом Eclipse будет подчеркивать этот текст желтым цветом и ругаться примерно так: [I18N] Hardcoded string ". ", should use @string resource. Это он возмущен хардкодом, который мы тут устроили. Он хочет, чтобы использовали файлы ресурсов для всех надписей. Но пока что мы это не умеем, так что игнорируем эти предупреждения.
Обязательно сохраняем. Чтобы в R.java появилась новая константа для этого файла - R.layout.myscreen.
Теперь настроим так, чтобы Activity использовало новый файл myscreen.xml, а не main.xml. Откроем MainActivity.java и поменяем аргумент метода setContentView. Замените «R.layout.main», на «R.layout.myscreen» (ID нового layout-файла). Должно получиться так:
Сохраняем, запускаем приложение. Видим, что теперь оно отображает содержимое из myscreen.xml, т.к. мы явно ему это указали в методе setContentView, который выполняется при создании (onCreate) Activity
Программный способ создания разметки
Для подключения созданной разметки используется код в методе onCreate():
Естественно, вы можете придумать и свое имя для файла, а также в приложениях с несколькими экранами у вас будет несколько файлов разметки: game.xml, activity_settings.xml, fragment_about.xml и т.д.
В большинстве случаев вы будете использовать XML-способ задания разметки и подключать его способом, указанным выше. Но, иногда бывают ситуации, когда вам понадобится программный способ (или придётся разбираться с чужим кодом). Вам доступны для работы классы android.widget.LinearLayout, LinearLayout.LayoutParams, а также Android.view.ViewGroup.LayoutParams, ViewGroup.MarginLayoutParams. Вместо стандартного подключения ресурса разметки через метод setContentView(), вы строите содержимое разметки в Java, а затем уже в самом конце передаёте методу setContentView() родительский объект макета:
Число макетов постоянно меняется. Например, недавно появились новые виды CoordinatorLayout и ConstraintLayout. Кроме стандартных элементов разметки существуют и сторонние разработки.
В предыдущем примере два фрагмента были полностью независимы друг от друга. Но в реальности такое не встречается. Фрагменты должны как-то общаться между собой. Поэтому пора переходить к следующей части - как взаимодействовать с фрагментами.
Чтобы было легче перестроиться на новую технологию, начнём издалека и создадим сначала следующую программу. Набросаем на экран несколько кнопок и других компонентов. Я по возможности оставляю старый вариант, чтобы не переписывать всю статью. Вы можете некоторые части кода менять на более современные аналоги, например, использовать ConstraintLayout.
Думаю, вам уже не составит труда написать код для кнопок, чтобы в нижней части экрана менялась картинка и текстовое содержание про каждого кота. Но я этого делать пока не буду.
Комбинирование
Компоновка ведёт себя как элемент управления и их можно группировать. Расположение элементов управления может быть вложенным. Например, вы можете использовать RelativeLayout в LinearLayout и так далее. Но будьте осторожны: слишком большая вложенность элементов управления вызывает проблемы с производительностью.
Можно внедрить готовый файл компоновки в существующую разметку при помощи тега :
Подробнее в отдельной статье Include Other Layout
Читайте также: