Урок 6: Modifier (модификаторы) и аргументы composable функций
Оглавление:
Введение
Мы уже коснулись темы модификаторов, когда кастомизировали некоторые наши элементы и занимались их позиционированием. Сейчас я хочу более подробно раскрыть эту область, потому, что возможности настройки отображения элементов практически безграничные. А главное – это делается достаточно изящно и легко.
Итак, как вы уже понимаете отображение можно настраивать двумя способами:
- С помощью параметров функции
- С помощью Modifier
Как правило аргументы используются только для специализированных параметров элемента, а модификаторы в свою очередь можно применить к вообще любому элементу. Прописывать весь набор всех возможных модификаторов для каждой функции-элемента нецелесообразно. Иначе бы они были громоздкими с массой повторяющихся параметров.
Например, для текста мы уже использовали такие параметры, как text (он является обязательным), и fontSize. У него еще есть целая куча параметров, таких как шрифт, межстрочный интервал, цвет и так далее. Все это должно быть знакомо тем, кто уже верстал на XML. Применим некоторые из них.
Установка цвета текста в Jetpack Compose
Улучшим внешний вид хедера, используя стандартные параметры функции Text. Начнем с изменения цвета, причем нужные мне цвета я хочу определить в специально созданном для этого средой разработки файле. Перемещаемся в ui.theme.Color.kt. Здесь уже есть стандартные цвета в ARGB формате с прописанной прозначностью. 0xFF — это альфа-канал (то есть прозрачность) в 8-битном формате. FF означает, что цвет будет полностью непрозрачный.
Стандартные значения пока оставим без изменений, чтобы не ломать тему. Добавлю сюда еще собственные какие-нибудь два цвета. Пусть будет основной черный и серый цвет для подзаголовков. Как видите теперь цвета хранятся в Kotlin файле, соответственно объявляются они в виде переменных.
val mainBlackColor = Color(0xFF1E1E1E)
val subtitleGrayColor = Color(0xFF8C8C8C)
Все что остается теперь – это передать параметру color нужную переменную с созданным только что цветом. Для заголовка и подзаголовка.
Установка шрифтов в Jetpack Compose
Теперь еще давайте поменяем шрифт. Я уже скачал нужные файлы шрифтов и поместил в ресурсный каталог font. Чтобы установить шрифт для текста соответственно обращаюсь к параметру fontFamily и задаю ему значение такого вида: FontFamily(Font(R.font.gilroy_semibold)). Где в параметр функции font передаем ID ресурса файла загруженного шрифта.
Для примера распределю шрифты таким образом: полужирный для заголовка, средний для подзаголовка и жирный для текста кнопок. Обращаю внимание, что для изменения текста на кнопках мы обращаемся к параметру именно Text функции.
Пересоберем проект и убедимся, что изменения подтянулись. Великолепно.
Модификаторы в Jetpack Compose / Modifier
С модификаторами все поинтереснее и есть некоторые нюансы их использования. Как я уже сказал, модификаторы мы можем применять вообще к любым composable элементам.
Причем мы можем не только изменять внешний вид компонентов, но и управлять их функциональностью и поведением. Например, мы можем менять размер элементов, добавлять отступы, перестраивать верстку — вы уже видели, как это работает.
Но еще мы можем делать composable элементы интерактивными. Что это значит? Мы можем, например, сделать любой компонент кликабельным, чтобы на него можно было нажимать. Или добавить возможность перетаскивания – реализовать drag-and-drop. А еще мы можем настроить масштабирование, чтобы элементы можно было зумировать, или добавлять скроллинг, если контент не помещается на экране. И это только малая часть того, что можно сделать.
Добавление изображения / Image
Хочу продемонстрировать некоторые ключевые особенности на примере вот такого изображения. Точнее тут будет картинка с текстом поэтому создам отдельную функцию, а внутри Column, чтобы сначала было изображение, а под ним текст.
Картинку я уже добавил его к себе в ресурсный каталог Drawable. Чтобы сверстать его воспользуемся методом Image. Берем тот, который первым параметром принимает тип Painter – он подходит как для растровых, так и для векторных изображений.
Кстати, для добавления кликабельной иконки (аналог ImageButton) в Compose обычно используется функция IconButton. Но я расцениваю свое изображение, как картинку, а также это нужно для демонстрации возможностей модификаторов.
Если провалиться в декларацию увидим, что обязательными параметрами являются Painter(в него будем передавать наш ресурс изображения) и contentDescription(как вы помните из курса по XML он используется для описания содержимого картинки для поддержки, например, скринридеров).
В качестве описания пока оставлю пустую строку, а чтобы установить изображение использую следующую конструкцию: painterResource(R.drawable.rocket). Передавая в метод painterResource ID drawable ресурса. На превью пока ничего не отобразилось, поэтому сделаю принудительный рефреш. Отлично.
Заготовка готова, добавлю вызов функции в setContent и посмотрим что получилось. Ну для начала стоит центрировать текст и добавим немного отступа сверху. Делается это конечно с помощью модификатора. Причем центрирование можем применить либо к колонке, либо к самому тексту. Воспользуюсь параметром horizontalAlignment у колонки, чтобы центрировались текст и картинка.
Окей. Напомню, серый фон мы пока не убираем намеренно, он не только показывает границы рабочего контейнера, но и в данном случае показывает границы изображения, что нам пригодится.
Модификация изображения – кликабельность
Начнем кастомить нашу картинку, для начала хочу, чтобы размер был поскромнее. Применим модификатор и обратимся к методу size – он изменит размер сразу по обеим осям. Мне подойдет 120.dp. Но можно вызвать по отдельности with или height.
Далее нужно, чтобы картинка была кликабельная. Да, сделаем из нее кнопку. Это тоже делается легко, вешаю модификатор clickable и обязательным параметром этой экстеншн функции является onClick для передачи какого-то коллбэка при нажатии пользователя. Оставим пока просто пустую лямбду.
Посмотрим как это выглядит – более менее, но эффект нажатия мне не нравится. Нужен такой же красивый ripple эффект, как на кнопках. Мы же делаем стильное приложение, не так ли? Это решается добавлением параметра indication = ripple(). Но есть небольшой нюанс, для его корректной работы нужно добавить interactionSource. Что это такое?
Ripple-эффект и InteractionSource
Пока объясню коротко, это мы будем разбирать отдельно и более подробно, чтобы было понятно. Объект InteractionSource позволяет управлять и отслеживать взаимодействия. Для того чтобы эффект нажатия, такой как ripple, работал корректно, ему нужно знать, когда начинается и заканчивается взаимодействие (например, когда пользователь нажимает на элемент, а затем отпускает его).
InteractionSource временно хранит информацию о том, что происходит с элементом: нажат ли элемент, есть ли фокус и так далее. Без InteractionSource и информации о взаимодействии, эффект ripple не знает, когда применять свою анимацию. Таким образом, он не сможет «знать» о статусе нажатия. Этого пока должно быть достаточно. После перезапуска видим, что ripple отрабатывает великолепно.
Порядок модификаторов
Теперь сделаем нашу картинку круглой формы, делается это очень просто. Вызываем экстеншен clip у модификатора и передаем значение CircleShape. Это все, что нужно сделать. А теперь внимание. При клике ripple-эффект по идее должен был сработать только по области изображения, однако, он занимает всю площадь блока изображения – то есть квадрат. Почему?
Для модификаторов критически важен порядок их расположения. Они применяются снизу вверх (или справа налево). Каждый модификатор изменяет поведение или внешний вид элемента и влияет на последующие модификаторы.
Модификаторы работают как цепочка преобразований: каждый модификатор оборачивает предыдущий, и результат передаётся дальше вверх.
Поэтому правильный порядок модификаторов важен: более низкоуровневые изменения (например, обрезка) нужно размещать ближе к элементу, а более высокоуровневые (например, кликабельность) – дальше от элемента.
Как это работает у нас?
- Сейчас
clipнижеclickable, то есть вызывается первым:- сначала применяется
clip(CircleShape), который обрезает видимую часть изображения. - Затем применяется
clickable. Но!clickableопределяет область клика и рипл на основе полной области элемента, а не на основе обрезки (clip). Поэтому риппл становится квадратным.
- сначала применяется
- Если ставим
clipвышеclickable:- Сначала обрабатывается кликабельность, и рипл будет применён ко всему прямоугольнику.
- Затем обрезается видимая часть элемента до круга. В этом случае обрезается вообще все – и видимая часть, и риппл часть. Поэтому риппл становится также по форме круга.
Modifier для добавления тени
Добавим тень на кнопку. Используем модификатор shadow(3.dp, CircleShape), где первым параметром задается размер тени, а вторым можно придать как раз требуемую нам форму. Также ставим форму круга.
Возникает вопрос “куда ставить-то”? Попробуем в самый конец – на превью без изменений. Если повыше – то же самое. Но если поставить над вызовом clip – тень появилась. Потому, что сначала происходит обрезка всего содержимого, а сверху мы добавляем тень. В ином случае она тоже обрезается.
Image(
painter = painterResource(R.drawable.rocket),
contentDescription = "",
modifier = Modifier
.size(120.dp)
.shadow(3.dp, CircleShape)
.clip(CircleShape)
.clickable(
onClick = {},
indication = ripple(),
interactionSource = remember { MutableInteractionSource() }
)
)
Теперь можно закомментировать отладочный фон и посмотреть как выглядит готовый экран. Великолепно. Изображение стало объемным и теперь напоминает кнопку.
Другие модификаторы
Окей. Какие еще модификаторы часто используются? Все перечислять не буду, но упомяну некоторые из них:
- Семейство заполнения –
.fillMaxSize() .fillMaxWidth() .fillMaxHeight(). Их мы уже использовали для заполнения всего доступного пространства. - .background – отвечает за окраску фона какого-то элемента
- .clip – который уже использовали, может принимать значение для скругления углов элемента – RoundedCornerShape.
- .alpha – отвечает за прозрачность элементов.
- .rotate – позволяет изменить положение относительно собственной оси, в параметр передаем градус поворота типа Float.
Когда мы погрузимся в хранение состояния – продемонстрирую еще более эффектные возможности, которые дают нам модификаторы. А пока этого достаточно для базового понимания.
Бесплатные Telegram-боты для обучения
Практика с проверкой кода и помощью ИИ-ментора
AndroidSprint AI Mentor
Проверяет Pull Request'ы в GitHub, проводит тестовые собеседования с голосом и таймером, помогает разбираться с кодом 24/7
Попробовать ИИ-ментора →KotlinSprint Bot
22 урока Kotlin, 220 тестов, 120 практических задач с код-ревью
Начать обучение Kotlin →