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

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

Макет определяет структуру пользовательского интерфейса в вашем приложении, например в действии . Все элементы макета построены с использованием иерархии объектов 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 . Дополнительные сведения о жизненных циклах действий см. в разделе Введение в действия .

Атрибуты

Каждый объект 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 , поскольку это помогает вашему приложению правильно отображаться на экранах различных размеров. Принятые типы измерений определены в ресурсе макета .

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

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

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

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

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

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

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

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

Чтобы измерить свои размеры, представление учитывает его отступы. Заполнение выражается в пикселях для левой, верхней, правой и нижней частей представления. Вы можете использовать отступы, чтобы сместить содержимое представления на определенное количество пикселей. Например, левое заполнение, равное двум, смещает содержимое представления на два пикселя вправо от левого края. Вы можете установить заполнение с помощью метода 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.