Урок 5: Column & Row – позиционирование

Урок 5: Column & Row – позиционирование

Введение

Мы уже научились создавать свои первые composable функции для отображения UI. Теперь расскажу как работать с позиционированием элементов на экране.

Итак, мы имеем одну собственную composable функцию для удобства, в которой есть другая системная composable функция для отображения текста. Метод StudyAppHeader не обязательно должен содержать только одну строку – это header. И предположим, что в моем приложении в шапке есть заголовок и подзаголовок.

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

Text(
    text = "AndroidSprint",
    fontSize = 26.sp,
    modifier = Modifier.padding(innerPadding)
)
Text(
    text = "Изучение Kotlin & Android через практику",
    fontSize = 16.sp,
)

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

Это связано с тем, что Jetpack Compose по умолчанию не знает в каком порядке мы хотим отображать элементы. Нам необходимо явно сказать ему как должно происходить размещение. Это делается с помощью вызова специальных composable layout функций.

Column в Jetpack Compose

Оберну наши два текста в функцию с интуитивно понятным названием Column и тексты будут располагаться один под другим. Внутри функции Column есть параметры для внешних модификаций, позиционирование по вертикали и горизонтали, а также последний параметр лямбда-функция. Это уже привычный для нас параметр говорит о том, что внутри мы можем поместить другие composable функции. То есть сами элементы, например.

Column {
    Text(
        text = "AndroidSprint",
        fontSize = 26.sp,
        modifier = Modifier.padding(innerPadding)
    )
    Text(
        text = "Изучение Kotlin & Android через практику",
        fontSize = 16.sp,
    )
}

Для демонстрации запущу приложение. Заглавная надпись “AndroidSprint” установлена с системными паддингами сверху и снизу (позже я вынесу этот паддинг во внешний контейнер). А подзаголовок имеет уменьшенный текст и располагается снизу, как полагается.

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

Row в Jetpack Compose

Итак, чтобы расположить элементы в ряд используется composable layout функция Row. Работает она точно также, как Column, но располагает элементы горизонтально. Внутри размещаю три объекта кнопки с помощью соответствующих функций Button.

Остановимся подробнее на создании кнопки:

  • Мы используем компонент Button, чтобы создать кнопку.
  • Если посмотреть параметры кнопки, увидим, что параметр onClick – обязательный (то есть без значения по умолчанию). Поэтому в качестве заглушки тут будет пустая лямбда. Потом сюда можно будет вешать какой-нибудь вызов метода перехода на новый экран.
  • Button последним параметром принимает composable функции с контекстом RowScope.(), поэтому мы вызываем Text, чтобы добавить текст на кнопку. RowScope нам говорит о том, что скорее всего внутри кнопки спрятан Row и элементы будут располагаться в ряд. Вспоминайте (или лучше пересмотрите 3 урок про DSL и Scope). Впрочем мы будем добавлять только один компонент с текстом.

Скопирую до трех. Ну и на превью уже отрендерились наши кнопки в ряд, осталось только их переименовать. Например, пусть будут такие разделы: уроки, тесты и практика.

@Composable
fun MainNavButtons() {
    Row {
        Button(onClick = { }) {
            Text(text = "Уроки")
        }
        Button(onClick = { }) {
            Text(text = "Тесты")
        }
        Button(onClick = { }) {
            Text(text = "Практика")
        }
    }
}

Создание основного контейнера в setContent

Как вы понимаете, кнопки видны только в превью. Если запустить приложение – там ничего не окажется, так как в setContent не вызывается функция MainNavButtons. Нам нужно сделать это. Как вы думаете, где окажутся кнопки на устройстве?

Да, у самых границ экрана, потому, что системный паддинг применяется только к тексту AndroidSprint. Давайте прямо в setContent создадим основной контейнер, а именно колонку с вертикальным позиционированием, у которой будет системный паддинг по умолчанию.

И вот этой самой колонке зададим модификатор, который задавали заголовку. Просто перенесем его, чтобы innerPadding уже применялся к основному контейнеру. Соответственно из заголовка и из функции хэдэра этот параметр можно вообще убрать, функция стала более аккуратная и независимая. И внутри setContent все элементы автоматически будут располагаться:

  1. друг под другом, а не с наложением друг на друга,
  2. с необходимым системным отступом, чтобы не заезжать на системные элементы сверху и снизу экрана.

Видите какая логика вырисовывается? Мы создаем отдельные части экрана в виде отдельных пользовательских методов, которые могут иметь какую-то несложную логику и набор функций. А потом компануем их в основном контейнере в setContent.

Box в Jetpack Compose

Есть еще третий основной тип контейнера в Compose, который называется Box. Это аналог FrameLayout из XML верстки, когда мы можем размещать одни элементы внутри других (или друг на друге). Все используется по ситуации, например, если я в хедере поменяю колонку на Box, то тексты наложатся друг на друга. Думаю с этим должно быть понятно, останавливаться не будем.

Возможности позиционирования

И раз уж мы разговариваем на тему контейнеров – разберем возможности их позиционирования. Тут тоже ничего сложного, но сначала всегда нужно сформировать задачу, которую мы хотим выполнить. Для начала нам нужно, чтобы тексты и кнопки располагались по центру экрана. Что у нас для этого есть?

Обратимся к параметрам колонки главного контейнера. Есть параметр горизонтального позиционирования horizontalAlignment, который по-умолчанию имеет значение Alignment.Start. Поэтому элементы прижаты к левому краю. Нам нужно передать этому параметру новое значение.

Пишем horizontalAlignment = Alignment.CenterHorizontally и видим, что практически ничего не изменилось. Едва заметно, что сдвинулись только кнопки. Чтобы раскрыть это дело для наглядности я добавлю фон нашей колонке также через модификатор. Причем модификаторы добавляются через классическую цепочку вызовов по аналогии работы с каким-нибудь билдером.

За окрашивание фона отвечает экстеншн-функция background, в которую в качестве параметра отправим системный цвет LightGray. Соберем проект и обнаружим, что на самом деле границы колонки заканчиваются там, где и текст. То есть ее ширина по умолчанию стоит по ширине контента.

Следовательно, чтобы корректно работало центрирование элементов внутри – нужно саму колонку растянуть по всей ширине экрана. Это также делается через модификатор с помощью fillMaxWidth(). Причем, если растянуть по всем осям – увидим применимый нами ранее системный паддинг и снизу. Вот он, где белая незадействованная полоса.

setContent {
	  Scaffold(
	      content = { innerPadding: PaddingValues ->
	          Column(
	              modifier = Modifier
	                  .padding(innerPadding)
	                  .background(Color.LightGray)
	                  .fillMaxSize()
	              ,
	              horizontalAlignment = Alignment.CenterHorizontally
	          ) {
	              StudyAppHeader()
	              MainNavButtons()
	          }
	
	      }
	  )
}

Соответственно у объекта Alignment есть несколько свойств, используя их ситуативно, можно позиционировать элементы по своему вкусу.

Зафиксируем мысль: Alignment используется для выравнивания элементов внутри контейнера. То есть где именно будет находиться элемент относительно границ контейнера, например, по центру, слева или справа.

Теперь добавлю такой же параметр центрирования для колонки внутри хедера, чтобы текст и там выравнивался по центру.

Хорошо, есть еще один параметр у Column или Row – Arrangement. Он используется для управления пространственным распределением элементов внутри контейнеров. Он определяет, как пространство между элементами будет распределено.

Для колонки такое расстояние можно установить только по вертикали, поэтому мы видим параметр verticalArrangement. А для Row мы увидим параметр horizontalArrangement соответственно. Итак обращаемся к объекту Arrangement и берем значение Center. Таким образом все элементы перемещаются к центру.

Какие еще значения можно задать? Для примера продублирую вызов функции с кнопками, чтобы получилось три компонента по вертикали.

  • SpaceEvenly – распределяет пространство между всеми элементами равномерно. Каждое пространство между элементами будет одинаковым, включая отступы до начала и после последнего элемента
  • SpaceBetween – распределяет пространство между элементами, оставляя отступы только между ними. Первому элементу не присваивается отступ перед ним, а последнему элементу – после.
  • SpaceAround – распределяет пространство вокруг элементов, создавая отступы как перед первыми, так и после последних элементов, при этом отступы между элементами будут равными между собой.

Ну и добавим немного стиля, разделив заголовок и кнопки между собой. Есть такая системная функция, как Spacer. К ней применяем модификатор height, например, в 30.dp. Таким образом между нашими основными функциями с наборами элементов появился отступ. Выглядит уже лучше.

Позиционирование текста

Стоит добавить, что мы можем повесить модификатор позиционирования на сам элемент текста – align. Когда мы используем его для компонента Text, мы фактически управляем тем, как этот текст будет выровнен внутри своего родительского контейнера. Если поставить заголовку Modifier.align(Alignment.End), то он предсказуемо сместится в правую сторону своего контейнера и так далее.

Стилизация кнопок

Далее хотел бы уделить внимание позиционированию кнопок, расположим их более аккуратно. Кнопки по умолчанию занимают только пространство, необходимое для их текста. Нам нужно сделать так, чтобы каждая кнопка занимала равное место, и для этого мы воспользуемся модификатором weight.

При добавлении Modifier.weight(1f) к каждой кнопке мы указываем, что она должна занимать равную долю доступного пространства в Row. Это значит, что если у нас три кнопки, каждая из них будет занимать 1/3 ширины контейнера.

Чтобы одновременно у кнопок было одинаковое расстояние между ними, мы добавим горизонтальный отступ в 5 пикселей для каждой кнопки Modifier.padding(horizontal = 5.dp).

Наконец, сам Row также будет включать горизонтальные отступы, чтобы кнопки не сливались с краями экрана. Тоже используем отступ в 5.dp.

Ширина и высота элементов в Compose

Хорошо. Если мы захотим задать принудительную ширину и высоту какому-то элементу – это делается также с помощью модификатора. Используются одноименные функции width и height. Не забудем в таком случае убрать fillMaxSize и вот – контейнер теперь приобрел форму заданного размера.

И это пока все, что я хотел рассказать относительно контейнеров.

Бесплатные Telegram-боты для обучения

Практика с проверкой кода и помощью ИИ-ментора

AndroidSprint AI Mentor

Проверяет Pull Request'ы в GitHub, проводит тестовые собеседования с голосом и таймером, помогает разбираться с кодом 24/7

Попробовать ИИ-ментора →

KotlinSprint Bot

22 урока Kotlin, 220 тестов, 120 практических задач с код-ревью

Начать обучение Kotlin →

AndroidSprint Bot

Тесты по Android SDK, Jetpack Compose, архитектуре приложений

Тесты по Android →

Тебе также может быть интересно

Узнать подробнее
Курс AndroidSprint

Глубокое обучение Android разработке с 0 до получения оффера. Только персональная практика с гарантией получения продуктового опыта.

Курс AndroidSprint - Глубокое <strong>обучение Android разработке с 0 до получения оффера</strong>. Только персональная практика с гарантией получения продуктового опыта.
Узнать подробнее
Узнать подробнее
Практикум по Kotlin

Изучение Котлин с 0 для профессиональной разработки. Личный ментор и разбор кода задач через git-flow.

Практикум по Kotlin - Изучение Котлин <strong>с 0 для профессиональной разработки</strong>. Личный ментор и разбор кода задач через git-flow.
Узнать подробнее
Узнать подробнее
Бесплатные уроки по Kotlin разработке

Самостоятельное освоение базы по языку для дальнейшего развития в Android/back-end разработке или в автотестах.

Бесплатные уроки по Kotlin разработке - <span>Самостоятельное освоение базы по языку для дальнейшего развития в Android/back-end разработке или в автотестах.</span>
Узнать подробнее
Узнать подробнее
Onboarding в разработку

Полное обучение Android разработке с нуля до получения оффера. Делаем упор на практику и обратную связь

Onboarding в разработку - <span>Полное обучение Android разработке с нуля до получения оффера. Делаем упор на практику и обратную связь</span>
Узнать подробнее
Узнать подробнее
Обучающий Kotlin телеграм бот (с тестами)

Ваш основной инструмент для изучения основ языка. Бесплатные тесты и практика внутри.

Обучающий Kotlin телеграм бот (с тестами) - Ваш основной <span>инструмент для изучения основ языка.</span> Бесплатные тесты и практика внутри.
Узнать подробнее
Узнать подробнее
Бесплатные уроки по Android разработке

Самостоятельное обучение разработке Андроид приложений. Понятные видеоуроки с разжеванными примерами.

Бесплатные уроки  по Android разработке - Самостоятельное <span>обучение разработке Андроид приложений.</span> Понятные видеоуроки с разжеванными примерами.
Узнать подробнее
Узнать подробнее
Курс по UI/Unit тестированию

Для ручных тестировщиков, которые готовы осваивать автотесты с использованием актуального стека технологий. [в разработке]

Курс по UI/Unit тестированию - Для ручных тестировщиков, которые <span>готовы осваивать автотесты</span> с использованием актуального стека технологий. [в разработке]
Узнать подробнее
Узнать подробнее
Обучающий Android телеграм бот (с тестами)

Бесплатные теоретические тесты для самопроверки. А также информер на практических спринтах по Android.

Обучающий Android телеграм бот (с тестами) - Бесплатные <span>теоретические тесты для самопроверки.</span> А также информер на практических спринтах по Android.
Узнать подробнее