Урок 4: @Composable функции
Оглавление:
Введение
Пришло время начать работу в Android Studio. Потихоньку начнем писать код, попутно делая легкие привалы на разъяснения того, что же мы все таки пишем, но без духоты. Разбираем что такое и как работают Composable функции, а также что происходит внутри setContent и зачем нужен метод Scaffold.
Отличия Compose проекта от XML
Открываем созданный в самом начале пустой проект на Jetpack Compose или создаем новый. Давайте немного оглядимся. Не забываем переключить представление файлов в режим Project, чтобы видеть иерархию так, как она расположена у вас на компьютере.
- В каталоге с ресурсами мы теперь не увидим каталога layout, так как у нас больше не используются XML файлы для разметки экранов. Ничего не мешает нам создать их, и в настоящее время можно встретить гибридные проекты. Чаще всего, когда команда начинает плавный переход на компоуз с xml, это тема для отдельного урока.
- В основном каталоге видим стандартный MainActivity, в котором можете увидеть знакомый системный метод onCreate, который вызывается при создании Activity. Однако, вместо привычного setContentView (который принимал ID макета) видим специальную функцию setContent. Теперь внутри этой лямбды будут размещаться Composable функции.
- Также из основных отличий вы можете заметить, как в иерархии появился каталог ui – в нем нам предлагают хранить стили, ресурсы цветов и назначать темы. По аналогии с XML, но только теперь на языке Kotiln.
- Есть еще некоторые изменения в Gradle файле, в основном там прописаны импорты плагинов и библиотек для работы специально с Jetpack Compose. В целом пока что это все отличия.
Обращаю внимание, что в данном уроке подразумевается, что вы уже знакомы с основами верстки приложений, используя классический XML подход.
Создание первой Composable функции Text()
Разберемся в понятии Composable функций. Для наиболее качественного понимания я удалю все лишнее и мы напишем их заново самостоятельно.
enableEdgeToEdge() я тоже оставлю по умолчанию – это настройка позволяет приложению использовать всю доступную площадь экрана, включая области, которые могут быть покрыты системными элементами типа статус и навбара.
Запущу приложение, чтобы убедиться, что все собирается и никаких ошибок нет. Все хорошо. Итак, код нашего экрана мы будем писать в методе setContent. Сейчас уже понятно почему он так выглядит? Если провалимся в ее декларацию, увидим, что он расширяет класс Activity, а последний параметр является лямбдой.
Я хочу начать размещать название нашего практикума на экране. Для этого я воспользуюсь функцией с интуитивно понятным названием Text.
setContent {
Text()
}
Вы можете обратить внимание, что я называю написанное фукнцией, однако, следуя концепциям стиля языка Kotlin – тут явно что-то не то, ведь функции называются с маленькой буквы. Дело в том, что это специальная Composable функция. Они помечаются аннотацией @Composable и пишутся с большой буквы как классы. Конкретно этот метод – стандартный системный. Но мы также будем создавать и собственные, для генерации более сложного интерфейса и поддержания порядка в коде.
Давайте запомним:
- Функции в Compose нужны для описания состояния элемента экрана, то есть концептуально это должно быть нечто осязаемое – они считаются сущностями. Поэтому Composable функции всегда пишутся с заглавной буквы.
- Composable-фунции можно вызывать только из других Composable методов. Это поведение схоже с работой suspend функций, которую нельзя вызвать из обычной функции. Но мы можем вызывать обычные методы внутри suspend или Composable.
- Composable функция получает данные преимущественно из своих параметров (есть еще LocalComposition, на базе которого строятся темы оформления, но о нем позже). При этом Composable-фунция ничего не возвращает. UI рисуется исключительно за счет вызова такого метода.
- Ну и наконец, важно понимать, что эти функции предназначены для отрисовки интерфейса. А как мы помним это важнейший процесс, который выполняется в главном потоке. Следовательно никаких сложных вычислений или бизнес-логики в них содержаться не должно.
Окей. Внутри есть куча параметров для стилизации строки и есть один единственный параметр, который мы не передаем по умолчанию – это собственно сам текст. Поэтому в параметрах метода добавляем какую-нибудь строку, например, AndroidSprint. Причем название параметра можно указать явно. Вам это пригодится, когда параметров будет много, на этапе кастомизации и позиционирования этой строки.
Отсутствие отступов по умолчанию
Великолепный код, коллеги, умещается в несколько строк и не требует xml, давайте его запустим. Приложение запустилось и я вижу свой текст в углу экрана устройства – что происходит?
Раньше, когда в макете автоматически учитывались системные отступы, контент размещался ниже статус бара по умолчанию. Теперь, с появлением edge-to-edge дизайна и новой логики в библиотеках Jetpack Compose, автоматическое добавление отступов отключается. Вместо этого, нам предоставляется полный контроль над тем, как контент будет размещаться относительно системных баров. Это дает больше свободы, но требует явного указания правил размещения.
Как решить проблему?
- Например, можно задать такие параметры конкретному элементу с текстом, чтобы он имел стандартные отступы и располагался под статусбаром.
Параметры задаются с помощью модификаторов. Если мы вернемся к объявлению метода Text, увидим там параметр modifier с типом Modifier – он нам и нужен.
Важно. Казалось бы, зачем нам обращаться к modifier, если в функции Text и так целая куча параметров для кастомизации. Эти параметры текста отвечают за внешний вид самого текста (цвет, размер, шрифт), аModifierуправляет поведением и размещением текста в интерфейсе (отступы, фон, размеры или на него можно повесить слушатель кликов). Модификатор в принципе универсален и работает с любыми элементами, а не только с текстом.
Итак, добавлю этот именованный параметр. В значении обращусь к объектуModifierи вызову у него extension-функцию padding. Метод padding имеет несколько перегруженных вариантов с разными параметрами. Например, передав одно значение, мы задаем все 4 паддинга. Напишем70.dp. Обращаю внимание, что нужно именно через точку,dpздесь – является свойством-расширением класса Int, которое позволяет преобразовать число 70 в пиксели, независимые от плотности экрана.
setContent {
Text(
text = "AndroidSprint.ru",
modifier = Modifier.padding(70.dp)
)
}
- Запустим и посмотрим. В итоге мы создали компонент текста, а именно Composable функцию. В нее отправили два параметра: выводимый текст и модификатор паддинга в 70 пикселей по всем осям. Поэтому текст AndroidSprint имеет отступы и сверху, и слева.
Но нам этот вариант не подходит, так как для верстки экрана приложения все таки будет использоваться много разных компонентов. Дублировать модификаторы не очень хорошо.
- Следующий способ решения проблемы с позиционированием – создать собственный контейнер, добавить отступы и уже в нем размещать нужные нам элементы. Тоже неплохой вариант, но все таки пока еще похоже на костыль. Есть вариант получше, специально разработанный для настройки позиционирования.
- Использовать композабл функцию Scaffold – это что-то вроде базового каркаса (от англ. «строительные леса»). Он создан, чтобы упростить структуру и автоматизировать расположение стандартных элементов приложения. Его мы и будем использовать. Тем более при создании проекта среда разработки уже предлагает использовать именно эту функцию.
Как работает функция Scaffold
Как она работает?
- Вызываем метод Scaffold и открываем круглые скобки, в которые будем отправлять необходимые нам параметры.
- Внутри есть параметр content, который отвечает за основное содержимое экрана. Это тоже Composable тип, значит внутри этой лямбды можно размещать наши Composable функции. Пишем название этого параметра и в качестве его значения отправляем в лямбду нашу функцию с текстом.
- Последний штрих – передать в лямбду объект типа PaddingValues. Именно он содержит отступы, которые
Scaffoldавтоматически рассчитывает, чтобы контент не перекрывал системные элементы. Переменную с этим объектом отправляем в метод установки паддинга.
Также Scaffold позволяет добавить TopBar, меню и многое другое (он принимает достаточного много параметров). Но здесь я на это не буду тратить много времени, все это разжевывается в расширенном пошаговый практическом курсе.
Все. Теперь текст располагается ровно под статусбаром и нам не нужно костылить и рассчитывать дополнительные отступы самостоятельно. И еще я хочу увеличить размер текста, для этого просто укажу еще один параметр метода Text и укажу размер в 28.sp. Sp — это как dp, только для размеров шрифтов.
Создание собственной composable функции
Великолепно. Теперь займемся оптимизацией. Как вообще организовывается код при верстке с помощью Compose? Мы уже определились, что компоненты помещаются в setContent. Однако, таких компонентов будет много, они будут обладать гораздо большим количеством параметров и тем более многие из них почти всегда приходится переиспользовать. Размещать все в одном методе друг под другом некорректно и onCreate будет сильно перегружен.
Поэтому используется следующий подход. Создаются кастомные composable функции с понятными именами, внутрь помещается логика отображения одного или набора элементов. Затем созданные нами функции вызываются в setContent в нужном порядке. В кратце пока так, создадим же свой первый composable метод.
Это делается, как объявление обычной функции. Название придумываем свое, оно должно отображать суть того, что отрисовывается на экране. Пусть оно будет посвящено заголовку обучающего приложения – StudyAppHeader().
Чтобы функция стала composable добавляю соответствующую аннотацию. Внутрь скопирую системную функцию для отрисовки текста. При этом видим, что параметра паддинга нет, но его можно передать, поэтому так и сделаем. Тип нам уже известен.
Ну и все что остается, внутри setContent вместо текста вызвать функцию StudyAppHeader() с параметром паддинга, которая в свою очередь уже вызовет Text с определенными параметрами. Вот и все, onCreate разгружен, можно дальше создавать какую угодно логику в виде функций и комбинировать ее.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Scaffold(
content = { innerPadding: PaddingValues ->
StudyAppHeader(innerPadding)
}
)
}
}
}
@Composable
fun StudyAppHeader(innerPadding: PaddingValues) {
Text(
text = "AndroidSprint.ru",
fontSize = 28.sp,
modifier = Modifier.padding(innerPadding)
)
}
@Preview в Jetpack Compose
Отлично, все красиво, но я по прежнему после каждого изменения перезапускаю приложение, чтобы увидеть результат. Помните, в самом начале я перечислял преимущества Jetpack Compose? Так вот одна из его киллер-фич – это Preview.
Она позволяет отрисовываться composable функциям прямо внутри Android Studio без необходимости сборки проекта, установки APK файла на эмулятор или ваше устройство.
Как это реализовать? Нам нужно добавить дополнительную аннотацию @Preview для нашей composable функции. Получаем ошибку – превью можно использовать только для методов без параметров.
Поэтому мы сделаем еще одну функцию, в названии которой допишем Preview. Такие функции лучше делать приватными, чтобы к не было доступа извне, мало ли, кто-то из команды ошибочно будет использовать ее не по назначению.
Уберем из нее параметры и вызовем внутри нашу функцию с требуемым параметром. На вход она просит некий объект PaddingValues, в таком виде его и передадим. Нам по сути неважны сейчас системные отступы, важен только отрисовываемый текст.
@Composable
@Preview(showBackground = true)
private fun StudyAppHeaderPreview() {
StudyAppHeader(PaddingValues())
}
Чтобы отобразить содержимое превью в интерфейсе среды разработки нужно нажать на кнопку split в правом верхнем углу. Теперь мы видим и код, и то, что мы пометили аннотацией Preview. Дополнительно можно добавить параметр showBackground = true и у превьюшки появится фон. Более того, на превью визуально можно вывести полностью устройство. Добавив параметр showSystemUi превью развернется на полный экран и появятся элементы интерфейса. Кастомизировать и настраивать инструменты под себя вы можете как угодно, чтобы добиться максимального удобства.
Окей, что мы имеем. Мы создали композабл функцию StudyAppHeader, в которую поместили функцию с отображением нужного нам текста. Эта функция в свою очередь вызывается в функции Scaffold в качестве параметра content. Это нужно всего навсего, чтобы добавился стандартный отступ и текст располагался под статусбаром (или топбаром).
То есть я могу вырезать системную функцию Text и захардкодить прямо вместо вызова StudyAppHeader. Или вовсе вместо Scaffold, но тогда никакие отступы не будут учитываться.
Надеюсь это понятно и на базовых вещах мы останавливаться больше не будем, если остались проблемы с языком Kotlin – ищите плейлист на канале и изучайте. Верну все как было для комфортного продолжения работы.
В целом, можно сказать, сделали все необходимые приготовления нашего файла, чтобы в нем можно было комфортно верстать дальше. В следующем уроке научимся позиционировать элементы на экране относительно друг друга.
Бесплатные Telegram-боты для обучения
Практика с проверкой кода и помощью ИИ-ментора
AndroidSprint AI Mentor
Проверяет Pull Request'ы в GitHub, проводит тестовые собеседования с голосом и таймером, помогает разбираться с кодом 24/7
Попробовать ИИ-ментора →KotlinSprint Bot
22 урока Kotlin, 220 тестов, 120 практических задач с код-ревью
Начать обучение Kotlin →