Макеты в представлениях

Попробуйте способ «Композиции»
Jetpack Compose — рекомендуемый набор инструментов для разработки пользовательского интерфейса для Android. Узнайте, как работать с макетами в Compose.

Макет определяет структуру пользовательского интерфейса вашего приложения, например, в Activity . Все элементы макета построены с использованием иерархии объектов View и ViewGroup . View обычно отображает то, что пользователь может видеть и с чем может взаимодействовать. ViewGroup — это невидимый контейнер, определяющий структуру макета для View и других объектов ViewGroup , как показано на рисунке 1.

Рисунок 1. Иллюстрация иерархии представлений, определяющей макет пользовательского интерфейса.

Объекты View часто называются виджетами и могут быть одним из множества подклассов, например, Button или TextView . Объекты ViewGroup обычно называются макетами и могут быть одним из множества типов, предоставляющих другую структуру макета, например, LinearLayout или ConstraintLayout .

Вы можете объявить макет двумя способами:

  • Объявляйте элементы пользовательского интерфейса в XML. Android предоставляет простой словарь XML, соответствующий классам и подклассам View , например, для виджетов и макетов. Вы также можете использовать редактор макетов Android Studio для создания XML-макета с помощью интерфейса перетаскивания.

  • Создавайте элементы макета во время выполнения. Ваше приложение может создавать объекты View и ViewGroup и программно управлять их свойствами.

Объявление пользовательского интерфейса в формате XML позволяет отделить представление приложения от кода, управляющего его поведением. Использование XML-файлов также упрощает создание различных макетов для разных размеров и ориентаций экранов. Подробнее об этом см. в разделе «Поддержка различных размеров экранов» .

Фреймворк Android предоставляет вам возможность использовать любой из этих методов или оба сразу для создания пользовательского интерфейса вашего приложения. Например, вы можете объявить макеты приложения по умолчанию в XML, а затем изменять их во время выполнения.

Напишите XML

Используя XML-словарь Android, вы можете быстро проектировать макеты пользовательского интерфейса и содержащиеся в них элементы экрана, так же, как вы создаете веб-страницы в HTML с рядом вложенных элементов.

Каждый файл макета должен содержать ровно один корневой элемент, который должен быть объектом View или ViewGroup . После определения корневого элемента вы можете добавлять дополнительные объекты макета или виджеты в качестве дочерних элементов, постепенно выстраивая иерархию View , определяющую ваш макет. Например, вот XML-макет, использующий вертикальный LinearLayout для размещения TextView и Button :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Hello, I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>

После объявления макета в XML сохраните файл с расширением .xml в каталоге res/layout/ вашего проекта Android, чтобы он корректно скомпилировался.

Дополнительную информацию о синтаксисе XML-файла макета см. в разделе Ресурс макета .

Загрузить XML-ресурс

При компиляции приложения каждый XML-файл макета компилируется в ресурс View . Загрузите ресурс макета в реализации обратного вызова Activity.onCreate() вашего приложения. Для этого вызовите setContentView() , передав ему ссылку на ресурс макета в формате: R.layout. layout_file_name . Например, если ваш XML-макет сохранён как main_layout.xml , загрузите его для вашей Activity следующим образом:

Котлин

fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_layout)
}

Ява

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_layout);
}

Фреймворк Android вызывает метод обратного вызова onCreate() в вашей Activity при Activity запуске. Подробнее о жизненных циклах Activity см. в разделе «Введение в Activities» .

Атрибуты

Каждый объект View и ViewGroup поддерживает собственный набор XML-атрибутов. Некоторые атрибуты специфичны для объекта View . Например, TextView поддерживает атрибут textSize . Однако эти атрибуты также наследуются любыми объектами View , расширяющими этот класс. Некоторые атрибуты являются общими для всех объектов View , поскольку наследуются от корневого класса View , например, атрибут id . Другие атрибуты считаются параметрами макета , которые описывают определённые ориентации макета объекта View , определяемые родительским объектом ViewGroup этого объекта.

ИДЕНТИФИКАТОР

С любым объектом View может быть связан целочисленный идентификатор, который позволяет однозначно идентифицировать View в дереве. При компиляции приложения этот идентификатор указывается как целое число, но обычно он назначается в XML-файле макета как строка в атрибуте id . Это XML-атрибут, общий для всех объектов View , и он определяется классом View . Он используется очень часто. Синтаксис идентификатора внутри XML-тега следующий:

android:id="@+id/my_button"

Символ @ в начале строки указывает, что XML-анализатор анализирует и расширяет оставшуюся часть строки идентификатора и идентифицирует её как ресурс идентификатора. Символ плюс (+) означает, что это новое имя ресурса, которое необходимо создать и добавить к ресурсам в файле R.java .

Фреймворк Android предлагает множество других ресурсов идентификаторов. При ссылке на идентификатор ресурса Android символ «плюс» не нужен, но необходимо добавить пространство имён пакета android следующим образом:

android:id="@android:id/empty"

Пространство имен пакета android указывает, что вы ссылаетесь на идентификатор из класса ресурсов android.R , а не на класс локальных ресурсов.

Чтобы создавать представления и ссылаться на них из своего приложения, вы можете использовать следующий распространенный шаблон:

  1. Определите представление в файле макета и присвойте ему уникальный идентификатор, как в следующем примере:
    <Button android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_button_text"/>
  2. Создайте экземпляр объекта представления и захватите его из макета, обычно в методе onCreate() , как показано в следующем примере:

    Котлин

    val myButton: Button = findViewById(R.id.my_button)

    Ява

    Button myButton = (Button) findViewById(R.id.my_button);

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

Идентификатор не обязательно должен быть уникальным во всём дереве, но он должен быть уникальным в той его части, в которой выполняется поиск. Часто это может быть всё дерево, поэтому лучше сделать его уникальным, когда это возможно.

Параметры макета

Атрибуты макета XML с именем layout_ something определяют параметры макета для View , которые соответствуют ViewGroup в которой он находится.

Каждый класс ViewGroup реализует вложенный класс, расширяющий ViewGroup.LayoutParams . Этот подкласс содержит типы свойств, определяющие размер и положение каждого дочернего представления в соответствии с группой представлений. Как показано на рисунке 2, родительская группа представлений определяет параметры макета для каждого дочернего представления, включая дочернюю группу представлений.

Рисунок 2. Визуализация иерархии представлений с параметрами макета, связанными с каждым представлением.

Каждый подкласс LayoutParams имеет собственный синтаксис для установки значений. Каждый дочерний элемент должен определять LayoutParams , подходящий для своего родительского элемента, хотя он также может определять другие LayoutParams для своих дочерних элементов.

Все группы представлений включают ширину и высоту с помощью layout_width и layout_height , и каждое представление должно их определить. Многие LayoutParams включают необязательные поля и границы.

Вы можете указать ширину и высоту с точными измерениями, но, возможно, вам не захочется делать это часто. Чаще всего для задания ширины или высоты используется одна из следующих констант:

  • wrap_content : сообщает вашему представлению, что его размер должен соответствовать размерам, требуемым его содержимым.
  • match_parent : сообщает вашему представлению, что оно станет настолько большим, насколько это позволяет родительская группа представлений.

Как правило, мы не рекомендуем указывать ширину и высоту макета в абсолютных единицах, таких как пиксели. Лучше использовать относительные единицы измерения, такие как пиксельные единицы, не зависящие от плотности (dp), wrap_content или match_parent , поскольку это способствует корректному отображению вашего приложения на экранах устройств с различными размерами. Допустимые типы единиц измерения определены в ресурсе Layout .

Положение макета

Вид имеет прямоугольную геометрию. Он имеет местоположение, выраженное парой координат (левая и верхняя) , и два измерения: ширину и высоту. Единицей измерения местоположения и размеров является пиксель.

Вы можете получить местоположение представления, вызвав методы getLeft() и getTop() . Первый возвращает левую ( x ) координату прямоугольника, представляющего представление. Второй возвращает верхнюю ( y ) координату прямоугольника, представляющего представление. Эти методы возвращают местоположение представления относительно его родителя. Например, когда getLeft() возвращает 20, это означает, что представление расположено на 20 пикселей правее левого края своего непосредственного родителя.

Кроме того, существуют удобные методы, позволяющие избежать ненужных вычислений: getRight() и getBottom() . Эти методы возвращают координаты правого и нижнего краев прямоугольника, представляющего вид. Например, вызов getRight() аналогичен следующему вычислению: getLeft() + getWidth() .

Размер, отступы и поля

Размер представления выражается шириной и высотой. Представление имеет две пары значений ширины и высоты.

Первая пара называется «измеренная ширина» и «измеренная высота» . Эти параметры определяют размер представления в пределах его родительского элемента. Вы можете получить измеренные параметры, вызвав методы getMeasuredWidth() и getMeasuredHeight() .

Вторая пара называется шириной и высотой , или иногда шириной чертежа и высотой чертежа . Эти размеры определяют фактический размер изображения на экране во время отрисовки и после компоновки. Эти значения могут, но не обязательно, отличаться от измеренных ширины и высоты. Ширину и высоту можно получить, вызвав методы getWidth() и getHeight() .

Для измерения размеров представления учитывается его отступ (padding). Отступ выражается в пикселях для левой, верхней, правой и нижней частей представления. С помощью отступа можно сместить содержимое представления на определённое количество пикселей. Например, отступ слева, равный двум, сдвигает содержимое представления на два пикселя вправо от левого края. Вы можете задать отступ с помощью метода setPadding(int, int, int, int) и получить его, вызывая getPaddingLeft() , getPaddingTop() , getPaddingRight() и getPaddingBottom() .

Хотя представление может определять отступы, оно не поддерживает поля. Однако группы представлений поддерживают поля. Подробнее см. в ViewGroup и ViewGroup.MarginLayoutParams .

Более подробную информацию об измерениях см. в разделе Измерение .

Помимо программной настройки полей и отступов, вы также можете задать их в своих XML-макетах, как показано в следующем примере:

  <?xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" >
      <TextView android:id="@+id/text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="16dp"
                android:padding="8dp"
                android:text="Hello, I am a TextView" />
      <Button android:id="@+id/button"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginTop="16dp"
              android:paddingBottom="4dp"
              android:paddingEnd="8dp"
              android:paddingStart="8dp"
              android:paddingTop="4dp"
              android:text="Hello, I am a Button" />
  </LinearLayout>
  

В предыдущем примере показано, как применяются поля и отступы. TextView поля и отступы применяются равномерно по всему периметру, а Button показано, как их можно применять независимо к разным краям.

Общие макеты

Каждый подкласс класса ViewGroup предоставляет уникальный способ отображения вложенных в него представлений. Самый гибкий тип макета, предоставляющий наилучшие инструменты для поддержания неглубокой иерархии макета, — это ConstraintLayout .

Ниже приведены некоторые распространенные типы макетов, встроенные в платформу Android.

Создайте линейный макет

Выстраивает дочерние элементы в одну горизонтальную или вертикальную строку и создает полосу прокрутки, если длина окна превышает длину экрана.

Создавайте веб-приложения в WebView

Отображает веб-страницы.

Создавайте динамические списки

Если содержимое макета динамическое или не определено заранее, можно использовать RecyclerView или подкласс AdapterView . RecyclerView как правило, является лучшим вариантом, поскольку он использует память более эффективно, чем AdapterView .

Возможные общие макеты с RecyclerView и AdapterView включают следующее:

Список

Отображает прокручиваемый список из одного столбца.

Сетка

Отображает прокручиваемую сетку столбцов и строк.

RecyclerView предлагает больше возможностей и возможность создания собственного менеджера макетов .

Заполнение представления адаптера данными

Вы можете заполнить AdapterView такой как ListView или GridView привязав экземпляр AdapterView к Adapter , который извлекает данные из внешнего источника и создает View , представляющий каждую запись данных.

Android предоставляет несколько подклассов класса Adapter , полезных для извлечения различных типов данных и построения представлений для AdapterView . Два наиболее распространённых адаптера:

ArrayAdapter
Используйте этот адаптер, если источником данных является массив. По умолчанию ArrayAdapter создаёт представление для каждого элемента массива, вызывая toString() для каждого элемента и помещая содержимое в TextView .

Например, если у вас есть массив строк, которые вы хотите отобразить в ListView , инициализируйте новый ArrayAdapter с помощью конструктора, чтобы указать макет для каждой строки и массива строк:

Котлин

    val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray)
    

Ява

    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
            android.R.layout.simple_list_item_1, myStringArray);
    

Аргументы в пользу этого конструктора следующие:

  • Context вашего приложения
  • Макет, содержащий TextView для каждой строки в массиве
  • Массив строк

Затем вызовите setAdapter() для вашего ListView :

Котлин

    val listView: ListView = findViewById(R.id.listview)
    listView.adapter = adapter
    

Ява

    ListView listView = (ListView) findViewById(R.id.listview);
    listView.setAdapter(adapter);
    

Чтобы настроить внешний вид каждого элемента, можно переопределить метод toString() для объектов в массиве. Или, чтобы создать представление для каждого элемента, отличное от TextView (например, если вам нужен ImageView для каждого элемента массива), расширьте класс ArrayAdapter и переопределите getView() , чтобы он возвращал нужный тип представления для каждого элемента.

SimpleCursorAdapter
Используйте этот адаптер, когда данные поступают из Cursor . При использовании SimpleCursorAdapter укажите макет для каждой строки в Cursor и какие столбцы в Cursor должны быть вставлены в представления нужного макета. Например, если вы хотите создать список имён и номеров телефонов людей, вы можете выполнить запрос, который вернёт Cursor содержащий строку для каждого человека и столбцы для имён и номеров телефонов. Затем вы создаёте массив строк, указывающий, какие столбцы из Cursor должны быть в макете для каждого результата, и массив целых чисел, указывающий соответствующие представления, в которые должен быть вставлен каждый столбец:

Котлин

    val fromColumns = arrayOf(ContactsContract.Data.DISPLAY_NAME,
                              ContactsContract.CommonDataKinds.Phone.NUMBER)
    val toViews = intArrayOf(R.id.display_name, R.id.phone_number)
    

Ява

    String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
                            ContactsContract.CommonDataKinds.Phone.NUMBER};
    int[] toViews = {R.id.display_name, R.id.phone_number};
    

При создании экземпляра SimpleCursorAdapter передайте макет, который будет использоваться для каждого результата, Cursor , содержащий результаты, и эти два массива:

Котлин

    val adapter = SimpleCursorAdapter(this,
            R.layout.person_name_and_number, cursor, fromColumns, toViews, 0)
    val listView = getListView()
    listView.adapter = adapter
    

Ява

    SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
            R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
    ListView listView = getListView();
    listView.setAdapter(adapter);
    

Затем SimpleCursorAdapter создает представление для каждой строки в Cursor , используя предоставленный макет, вставляя каждый элемент fromColumns в соответствующее представление toViews .

Если в течение жизненного цикла вашего приложения вы измените базовые данные, считываемые адаптером, вызовите notifyDataSetChanged() . Это уведомит прикреплённое представление об изменении данных, и оно обновится.

Обработка событий щелчков

Вы можете реагировать на события щелчка по каждому элементу в AdapterView , реализуя интерфейс AdapterView.OnItemClickListener . Например:

Котлин

listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
    // Do something in response to the click.
}

Ява

// Create a message handling object as an anonymous class.
private OnItemClickListener messageClickedHandler = new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id) {
        // Do something in response to the click.
    }
};

listView.setOnItemClickListener(messageClickedHandler);

Дополнительные ресурсы

Посмотрите, как используются макеты в демонстрационном приложении Sunflower на GitHub.