Всем привет!

Я — Дарья Касьяненко, эксперт Центра непрерывного образования факультета компьютерных наук НИУ ВШЭ.

Сейчас мир Android‑разработки активно меняется, но многие из используемых технологий остаются актуальными, и их стоит знать каждому начинающему разработчику. Мой коллега Даниел Цуркан, эксперт и преподаватель курса «Android‑разработчик на Kotlin» Центра непрерывного образования ФКН, подробно расскажет о компонентах View и ViewGroup, которые лежат в основе построения пользовательских интерфейсов Android‑приложений.

Эта статья станет полезной как для тех, кто только начинает свой путь в Android‑разработке, так и для junior‑разработчиков, которые хотят углубиться в детали создания интерфейсов на базе View и ViewGroup. В статье разберем ключевые особенности, с которыми сталкиваются разработчики при создании пользовательских интерфейсов с использованием View, рассмотрим актуальность этого подхода для разработки визуальной части Android‑приложений, а в качестве наглядного примера — создадим экран авторизации пользователя.

Даниел Цуркан

эксперт и преподаватель курса «Android‑разработчик на Kotlin» Центра непрерывного образования ФКН

View или Compose

Jetpack Compose — это современный инструмент для разработки пользовательских интерфейсов на Android. На момент написания статьи Compose является предпочитаемым фреймворком для создания UI‑слоя в приложениях. Основные характеристики фреймворка включают:

  • Декларативный способ описания интерфейса

  • Применение функций для создания отдельных элементов UI

  • Реактивное обновление интерфейса при изменении состояния данных

  • Использование Canvas — платформы позволяющей создавать графические элементы, фигуры и отображать текст, — для отрисовки интерфейса

При этом View все также остается ключевым элементом, знание которого необходимо Android‑разработчику:

  • Множество существующих приложений по‑прежнему полностью или частично основаны на View

  • Compose является фреймворком, поддерживающим только Kotlin, поэтому проекты, где для работы с интерфейсом используется Java, не могут быть полностью переведены на Compose

  • Compose использует View для создания корневого элемента интерфейса

Интерфейс приложений


Начнем с определения интерфейсного слоя. Интерфейс приложения — это все то, что видит и с чем взаимодействует пользователь. Также можно сказать, что интерфейс — это состояние приложения на уровне данных.

Задачи интерфейсного слоя приложения:

  • Отображение данных в том виде, в котором мы хотим представить их пользователю

  • Получение информации от пользователя (с помощью кликов по кнопкам, ввода информации, свайпов и т. д.)

Все, что вы видите при работе с приложением, состоит из виджетов или уже известных вам View‑элементов.

View

View — это класс в стандартной библиотеке Android, который служит основой для всех компонентов интерфейса. Любой элемент, который вы видите на экране, будь то текст, изображение или кнопка, является производным от класса View.

Чтобы отобразить элементы интерфейса, необходимо наследовать этот класс и добавить логику для их визуализации. Однако делать это вручную необязательно, особенно на начальных этапах разработки. В стандартной библиотеке Android уже есть множество предопределенных классов для различных элементов интерфейса (см. рис. 1), которые можно использовать сразу.

Рис. 1. Иерархия View в стандартной библиотеке Android. Источник 

Список может показаться внушительным, однако большинство элементов используются редко, поэтому нет необходимости заучивать весь перечень. В повседневной разработке вы чаще всего будете работать с основными виджетами, такими как: TextView, Button, EditText, ImageView, CheckBox, Switch и ProgressBar.

Для описания виджетов в Android используется язык разметки XML. Описание блока с текстом выглядит следующим образом:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Авторизация" />

View можно создавать программно, однако для большинства задач по созданию интерфейса на начальном этапе достаточно использования XML‑разметки.

Разметку мы добавляем в файл, созданный при инициализации приложения: res/layout/activity_main.xml. Верстку из этого файла мы рендерим в методе onCreate() класса MainActivity с помощью функции setContentView().

Рис. 2. TextView
Рис. 2. TextView

Постановка задачи (Экран авторизации)

Перед тем, как перейти к следующему разделу, хочется обозначить задачу для практического примера — мы создадим экран авторизации (см. рис. 3).

Рис. 3. Экран авторизации
Рис. 3. Экран авторизации

Экран состоит из достаточно стандартных элементов: изображение (кнопка «Назад»), текст, поле ввода, кнопка.

Атрибуты View

Дизайн простых View получается достаточно примитивным, что нам, конечно, не подходит. Если сравнить дизайн (см. рис. 3) и наш результат (см. рис. 2), мы поймем, что размер текста, его толщина и другие параметры не совпадают. Для имплементации необходимого дизайна нам потребуется определение атрибутов View.

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

  • id - id объекта

  • text - текст

  • visibility - видимость объекта

  • textSize - размер текста

  • paddingTop - отступ контента от верхнего края прямоугольника

  • layout_height/layout_width - высота/ширина виджета

  • layout_marginTop/layout_marginBottom/… - отступы для различных сторон виджета

Атрибуты делятся на два типа:

  • те, которые определяют расположение объекта относительно других элементов в разметке, начинаются с префикса layout_. Программно эти атрибуты передаются в объекте LayoutParams.

  • те, которые влияют на отображение контента, — внутри View

То, как делятся два типа атрибута, можно увидеть на примере (см. рис. 4) — атрибут gravity отвечает за расположение текста внутри View, а атрибут layout_gravity (см. рис. 4) отвечает за расположение View внутри контейнера (для желтого квадрата layout_gravity атрибут принимает значение right).

Рис. 4. Различия атрибутов «gravity» и «layout_gravity»
Рис. 4. Различия атрибутов «gravity» и «layout_gravity»

Теперь вернемся к задаче и приведем текст в соответствие с дизайном — для этого дополним текст атрибутами. Получим следующий код и результат (см. рис. 5):

<TextView
    android:id="@+id/title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="24dp"
    android:layout_marginTop="80dp"
    android:text="Авторизация"
    android:textColor=”#332E2B"
    android:textSize="26sp"
    android:textStyle="bold" />
Рис. 5. Текст, дополненный атрибутами
Рис. 5. Текст, дополненный атрибутами

Координатная сетка View

Теперь поговорим о расположении View на экране. Начнем с того, что каждая View представляет собой прямоугольник, хотя это может быть не столь очевидно на первый взгляд — например, при работе с TextView. Но даже текстовый виджет является прямоугольником, что можно увидеть на рисунке 4, где все прямоугольники — это TextView. Для наглядности у них переопределен атрибут background.

Координатная система, в которой располагаются View, начинается в левом верхнем углу экрана. Значения по оси X увеличиваются слева направо, а по оси Y — сверху вниз.

Кроме того, каждый View имеет атрибуты: left (или start), top, right (или end) и bottom, которые задают координаты его границ по осям X и Y (см. рис. 6). Эти атрибуты определяют положение элемента на экране.

Рис. 6. Координатная сетка View
Рис. 6. Координатная сетка View

ViewGroup

ViewGroup — это невидимый контейнер, который определяет структуру интерфейса, организуя расположение дочерних элементов (View) на экране — это особенно важно для создания сложных макетов. Класс ViewGroup является абстрактным и служит базовым для всех контейнеров.

Иерархия классов, унаследованных от ViewGroup (см. рис. 1), включает в себя наиболее часто используемые контейнеры из стандартной библиотеки Android: FrameLayout, LinearLayout, ConstraintLayout и RecyclerView.

FrameLayout

FrameLayout — это контейнер, который размещает дочерние View друг на друге в виде стека, где каждый новый элемент накладывается поверх предыдущего (см. рис. 7). С помощью атрибута layout_gravity можно управлять положением дочерних View внутри контейнера, например, разместить один виджет сверху, а другой — снизу.

Применение FrameLayout:

  • Создание простых интерфейсов: Используется для отображения одного или нескольких виджетов без сложного позиционирования

  • Попеременное отображение элементов: Удобен для реализации сменяющихся View — таких, как переключение между загрузочным индикатором и контентом

  • Оверлеи: Часто применяется для создания наложений — например, для отображения всплывающих кнопок поверх изображения или видео

  • Фоновые изображения: FrameLayout хорошо подходит для размещения фоновых изображений под другим контентом, что делает его полезным для создания визуально привлекательных экранов

Рис. 7. Наложение виджетов во FrameLayout
Рис. 7. Наложение виджетов во FrameLayout

LinearLayout

LinearLayout — это контейнер, который располагает дочерние элементы последовательно в одном направлении: вертикально или горизонтально (см. рис. 8). Порядок отображения задается атрибутом orientation, который может принимать два значения: vertical и horizontal.

Одной из ключевых особенностей контейнера является возможность использования атрибута weight, который позволяет задавать относительные размеры для дочерних элементов. Это особенно полезно, когда нужно, чтобы элементы занимали пропорциональные доли пространства, например, чтобы один элемент занимал 2/3 экрана, а другой — 1/3.

Применение LinearLayout:

  • Упорядочивание элементов в одном направлении: LinearLayout подходит для простого размещения элементов по вертикали или горизонтали

  • Относительное масштабирование: Благодаря атрибуту weight контейнер полезен для случаев, когда нужно динамически распределить пространство между элементами

Рис. 8. Расположение виджетов в LinearLayout. Источник
Рис. 8. Расположение виджетов в LinearLayout. Источник

ConstraintLayout

ConstraintLayout — это мощный контейнер, позволяющий создавать гибкие, масштабируемые и адаптивные интерфейсы. Его основное преимущество заключается в том, что он позволяет размещать элементы относительно друг друга или родительского контейнера, используя так называемые «ограничения» (constraints) для управления их положением (см. рис. 9).

С помощью набора атрибутов, таких, как layout_constraintTop_toBottomOf, layout_constraintTop_toTopOf, layout_constraintStart_toEndOf и многих других, можно точно задавать расположение элементов относительно друг друга или родительского контейнера. Это позволяет избежать необходимости в избыточной вложенности контейнеров, что улучшает производительность приложения.

Рис. 9. Позиционирование ConstraintLayout. Источник
Рис. 9. Позиционирование ConstraintLayout. Источник

ConstraintLayout также позволяет связывать несколько элементов в цепочку (chain), чтобы они выравнивались друг относительно друга и занимали доступное пространство равномерно или по заданным пропорциям.

Рис. 10. ConstraintLayout chain. Источник
Рис. 10. ConstraintLayout chain. Источник

Применение ConstraintLayout:

  • Создание сложных макетов: ConstraintLayout позволяет строить сложные экраны с множеством взаимосвязанных элементов, избегая глубоких вложенностей

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

RecyclerView

RecyclerView — это мощный контейнер, предназначенный для эффективной работы с большими наборами данных, отображающимися в виде списков или сеток.

Структура RecyclerView:

  • ViewHolder — объект, который содержит ссылки на виджеты в элементах списка. Помогает оптимизировать производительность, переиспользуя уже созданные View

  • Adapter — мост, связывающий данные с элементами отображения в RecyclerView

  • LayoutManager — компонент, который управляет расположением и размерами элементов внутри списка, отвечая за разметку и скролл

RecyclerView также поддерживает отображение элементов с различными типами данных. Для этого в адаптере можно переопределить метод getItemViewType(position: Int), который позволит адаптировать список для разных типов контента.

Помимо основного функционала, RecyclerView предоставляет обширные возможности для настройки отображения списков. Более 25 встроенных классов и интерфейсов позволяют реализовать продвинутые сценарии использования, включая ItemAnimator, ItemDecorator, DiffUtil, SnapHelper и тд.

Применение RecyclerView:

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

  • Отображение разнородных данных: RecyclerView позволяет адаптировать интерфейс для показа списков с элементами разных типов, например, новостные ленты, состоящие из текстов, изображений и видео

  • Гибкое управление макетом: Используя различные LayoutManager, можно отображать данные не только в виде вертикальных списков, но и сеток (GridLayoutManager) или каруселей (LinearSnapHelper)

Реализуем экран авторизации

Для имплементации экрана авторизации используем ConstraintLayout, так как он наполнен разными элементами, находящимися в разных областях интерфейса. К тому же мы хотим, чтобы наша верстка была адаптивной и не ломалась при изменении размеров экрана (например при запуске приложения на планшете).

Для добавления кнопки «Назад» используем ImageView.

<ImageView
    android:id="@+id/back_button"
    android:layout_width="20dp"
    android:layout_height="20dp"
    android:layout_marginStart="22dp"
    android:layout_marginTop="28dp"
    android:src="@drawable/ic_back_arrow"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

В новой верстке мы добавили отступы, «ограничения» и ссылку на drawable ресурс (графический ресурс).

Добавляем заголовок под кнопкой «Назад»:

<TextView
    android:id="@+id/title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="24dp"
    android:layout_marginTop="30dp"
    android:text="Авторизация"
    android:textColor="#332E2B"
    android:textSize="26sp"
    android:textStyle="bold"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/back_button" />

Добавляем текст-подсказку над полем ввода e-mail с помощью кода ниже:

<TextView
    android:id="@+id/email_top_hint"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="24dp"
    android:layout_marginTop="24dp"
    android:text="Email"
    android:textColor="#86796F"
    android:textSize="14sp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/title" />

Добавляем верстку для поля ввода e-mail:

<EditText
    android:id="@+id/email"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:layout_marginStart="20dp"
    android:layout_marginTop="4dp"
    android:layout_marginEnd="20dp"
    android:autofillHints="emailAddress"
    android:background="@drawable/edit_text_background"
    android:hint="Email"
    android:inputType="textEmailAddress"
    android:paddingStart="16dp"
    android:paddingEnd="8dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/email_top_hint" />

Для реализации обертки поля ввода с закругленными углами используем drawable-ресурс (XML-файл, добавленный в папку drawable) для текста:

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#F6F5F4" />
    <corners android:radius="12dp" />
    <padding
        android:bottom="0dp"
        android:left="8dp"
        android:right="0dp"
        android:top="0dp" />
</shape>

Код для подсказки под полем ввода текста приведен ниже:

<TextView
    android:id="@+id/email_bottom_hint"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginStart="24dp"
    android:layout_marginTop="4dp"
    android:layout_marginEnd="24dp"
    android:text="Отправим письмо с кодом подтверждения"
    android:textColor="#86796F"
    android:textSize="14sp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/email" />

Для отрисовки кнопки «Далее» применяем следующий код:

<Button
        android:id="@+id/next_button"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_marginStart="20dp"
        android:layout_marginEnd="20dp"
        android:layout_marginBottom="8dp"
        android:background="@drawable/button_background"
        android:text="Далее"
        android:textColor="#F6F5F4"
        app:backgroundTint="@color/black"
        app:layout_constraintBottom_toTopOf="@+id/next_button_hint"
        app:layout_constraintStart_toStartOf="parent" 

Для создания соответствующей формы кнопки используем следующий drawable-ресурс:

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#332E2B" />
    <corners android:radius="42dp" />
</shape>

И добавляем еще одну подсказку под кнопкой:

<TextView
    android:id="@+id/next_button_hint"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:layout_marginStart="24dp"
    android:layout_marginEnd="24dp"
    android:layout_marginBottom="16dp"
    android:text="Нажимая кнопку Далее…"
    android:textColor="#86796F"
    android:textSize="13sp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent" />

Результат работы представлен на рис. 11.

Рис. 11. Имплементация экрана авторизации
Рис. 11. Имплементация экрана авторизации

Заключение

Итак, давайте подведем итоги: в статье мы с вами рассмотрели базовые инструменты для верстки пользовательского интерфейса Android-приложений, View и ViewGroup,  а также подготовили макет для экрана авторизации. Теперь вы знаете, как пользовательский интерфейс выглядит программно, но это лишь небольшая часть того, что должен знать мобильный разработчик. Со слушателями курса «Android‑разработчик на Kotlin» в Центре непрерывного образования ФКН мы детально разбираем все аспекты работы с Android.

Желаю вам профессиональных успехов!

Комментарии (5)


  1. Rusrst
    25.10.2024 10:11

    Поскольку View является абстрактным классом, создать его объект напрямую невозможно. Чтобы отобразить элементы интерфейса, необходимо наследовать этот класс и добавить логику для их визуализации

    Да уж. Не хотел бы я учиться у экспертов у которых view абстрактный класс и его нельзя создать руками. ГПТ всё чаще статьи пишет, как я посмотрю. Ну и глючить чаще начинает...


    1. kasyanenko Автор
      25.10.2024 10:11

      Добрый день, благодарю за комментарий! Ошибку исправили.


      1. Rusrst
        25.10.2024 10:11

        Хм, а вы молодцы. В отличии от МТС хотя бы написали об исправлении :)


  1. Monade
    25.10.2024 10:11

    Верстку из этого файла мы рендерим в методе onCreate() класса MainActivity с помощью функции setContent.

    Мы же не на compose пишем, почему-то setContent


    1. kasyanenko Автор
      25.10.2024 10:11

      Спасибо за внимательное замечание! Вы правы: в данном случае действительно используется метод setContentView(), а не setContent. Мы уже внесли исправление в статью.