Урок 4: Верстаем экран по макету из Figma, ShapeDrawable

Что такое Figma (Фигма)?

Давайте поверстаем, используя один из разобранных в прошлом уроке контейнеров. У нас есть дизайн-макет, который вы тоже можете посмотреть здесь https://clck.ru/34ow4g.

Макет нарисован в Figma — в настоящее время самый популярный инструмент для дизайна сайтов и мобильных приложений. Он векторный, браузерный, позволяет работать командам совместно и в то же время мощный. И еще есть десктопное приложение. Расскажу необходимый разработчику минимум.

В данном случае LearnEnglishWordsApp — файл дизайна. Он содержит набор страниц, на панели слева. Каждая страница содержит фреймы. При дизайне мобильных приложений, фрейм — это как правило одно из состояний экрана.

Фрейм — одно из состояний экрана

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

Экраны нарисованы с iOS статус-баром, но не стоит обращать на это внимание. А в целом привыкайте – нелюбовь дизайнеров к дизайну под Android это отдельный мем. Выложу, его в своем телеграм канале, там, если что, не только мемчики. Заходите по ссылке https://t.me/ievetrov_dev и познакомимся поближе.

Напомню контекст. В рамках этих уроков мы будем вдыхать визуал и жизнь в приложение для изучения иностранных слов. Логику которого мы реализовали в виде телеграм-бота в рамках курсовой работы по Kotlin.

Предлагаю начать непосредственно с экрана изучения конкретного слова.

Чтобы верстать, нам нужно освоить по сути 4 навыка:

  1. Экспорт графики — иконок / изображений
  2. Просмотр размеров и отступов
  3. Просмотр цветов
  4. Просмотр шрифтов и размеров текстов

Верстка иконки закрытия

Экспорт из Figma

Начнем с иконки закрытия экрана изучения слова. Выбираем раздел Export, жмем +, экспортируем в вектор, как наиболее легкий и адаптивный тип – выбираем SVG.

Иконка закрытия экрана

Теперь создадим разметку для нашего экрана. Создадим новый лейаут, назовем activity_learn_word и зададим основной контейнер LinearLayout. Сразу пропишем ему вертикальную ориентацию.

Окей. Вообще для добавления изображений используется вью ImageView. Но так как это иконка, на которую будем нажимать, предлагаю использовать вью ImageButton. Это специализированная версия ImageView, которая позволяет изображению вести себя как кнопка.

Импорт векторной иконки в проект

Чтобы импортировать иконку идем в ресурсы и жмем ПКМ на каталоге drawable. Выбираем New → Vector Asset. Не перепутайте с Image Asset — он используется для добавления растровых изображений. Далее пункт Local file и ищем скаченную иконку в загрузках. Остается только проверить, что размер соответствует тому, что нам нужен и переименовать. Иконки рекомендуется именовать с префиксом ic, разделяя слова нижним подчеркиванием (snake case). Я назову ее ic_close. Все, теперь наша иконка сгенерировалась в формате XML и видна в ресурсах.

Чтобы присвоить иконку нашей “вьюшке” прописываем тег src. Далее прописываем ссылку на элемент. Можно начать вводить его название и среда разработки найдет его. Кстати, одно из преимуществ использования префиксов — проще искать в списках.

Ссылка пишется в таком формате: после знака @ указывается каталог и через слэш название ресурса — в нашем случае drawable.

Хорошо. Сделаем фон прозрачным. Почти для всех элементов можно задать цвет фона с помощью атрибута background. К сожалению, если так сделать, у кнопки пропадает кликабельность (визуальный отклик), но сейчас не будем на этом заострять внимание. Зададим прозрачный цвет, а именно передадим сюда ссылку на ресурс цвета, который хранит код прозрачного цвета. Но нам не нужно создавать его своими руками и помещать в ресурсы. Такой цвет есть в стандартной библиотеке Android, поэтому в ссылке сначала указываем android, затем прописываем transparent.

Создание цвета фона элемента из стандартной библиотеке Android

В любой ресурс мы можем перейти по ссылке, осуществив клик вместе с ctrl или cmd. Жмем на прицел и убеждаемся, что действительно этот ресурс хранится в нашем SDK.

Далее нам остается только задать для нее правильные отступы. Идем в Figma и смотрим какой отступ должна иметь иконка сверху и слева, для этого жмем Alt или Option и наводим курсором. 10dp от статус-бара и 24dp слева. Пропишем эти значения с помощью margin.

Данное решение рабочее, но не оптимальное:

  • Нет эффекта нажатия, которое давало пользователю обратную связь.
  • Область нажатия довольно мала, так как соответствует размеру иконки. Разработчику также нужно думать о том, какая область кликабельная. Она не всегда совпадает с отображаемой областью.
  • Нет описания contentDescription — чтобы дать информацию голосовым помощникам озвучивать элементы интерфейса.

По ходу курса мы будем освещать озвученные темы.

Верстка изучаемого слова

Далее разместим “вью” для слова, которое будет задаваться для изучения. Для текста используем тег TextView. Расставим соответствующие отступы и зададим горизонтальное позиционирование по центру. Смотрим размер текста в макете — 50sp. Задаем его, используя атрибут textSize.

Сам текст задается в атрибуте text, но обратите внимание, что задав его тут он так и останется статичным. Конечно, позже программно мы будем задавать этому тексту другие значения, поэтому разработчики как правило пишут сюда рандомный текст. Однако из-за риска что этот текст может попасть к пользователям, распространенной практикой является проставлять специальный тег для разработки — tools. Там можно писать любые значения-заглушки с гарантией того, что этого точно не увидит пользователь.

Для этого пропишем атрибут и импортируем его пространство имен в документ и пропишем тестовый текст. Как видите в превью отображается значение из tools, в приложении только текст из обычного атрибута текст. Иногда текст в tools заведомо пишут длинным, чтобы посмотреть, как такой текст внутри “вью” выглядит. Чтобы обработать поведение, связанное переносами или отступами справа.

Верстка вариантов ответа

Окей. Приступаем к самому интересному. Глядя на дизайн-макет мы видим ряд вложенных контейнеров. То есть это будет один общий Linear для элементов вариантов ответа с вертикальной ориентацией. Каждый элемент — это контейнер с горизонтальной ориентацией, чтобы в нем разместить порядковый номер и само слово рядом друг с другом. Задача ясна, поехали.

Верстка вариантов ответа

Вложенные лейауты

  • Задаем общий LinearLayout для элементов. Высота по размеру контента, ширина пусть занимает все пространство родителя. Сразу добавим отступы сверху и по сторонам. Не забываем указать ориентацию — элементы внутри будут располагаться вертикально.
  • Теперь добавляем еще один вложенный контейнер для первого элемента. Ориентация для вложенных в него элементов теперь будет горизонтальная. Ширина будет ограничена родительским контейнером, а высоту возьмем из макета.
  • Далее приступим к внутренним элементам — номер варианта и вариант ответа. Номер варианта это ничто иное как TextView с цифрой и фоном. Добавим цифру и рядом вариант ответа — на одном уровне вложенности. Размеры у контейнера фиксированные, берем из макета.

Вот что получается. В XML макете вложенность можно наблюдать по отступам слева. То есть тут мы имеем общий контейнер элемента. Внутри него на одном уровне находится текст для цифры варианта ответа и само слово. При наведении на макет можно наблюдать контуры созданных нами элементов.

Контуры созданных элементов

Стилизация (позиционирование, размер текста и фон)

  • Зададим размеры для шрифта текста цифры варианта в 16sp и добавим gravity, чтобы текст расположился по центру, а также размеры TextView цифры (40dp на 40dp).
  • Ему же добавим внешний отступ слева по макету 12dp.
  • Его родителю тоже добавим gravity, чтобы внутри себя центрировать все по вертикали.
  • У текста слова тоже есть отступ слева — 8dp.
  • Зададим соответствующие макету размеры текста.
  • Для цифры добавим фон с помощью атрибута background, цвет берем из макета. Чтобы задать цвет текста используется атрибут textColor.

Создание фонов (ShapeDrawable)

Отлично. Теперь приступаем к самому невероятному. Нашим контейнерам нужно добавить обводку и закругленные углы. Параметр для закругленных углов называется Corner Radius. Если провалиться в Figma в элемент этой фигуры можно увидеть, что это значение равно 10.

Параметр для закругленных углов Corner Radius

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

Вот как выглядит алгоритм создания описанного фона:

  1. Идем в drawable и создаем Drawable Resource File
  2. Задаем название. По правилам именования сначала пишем тип, затем конкретизируем назначение. Назову его hape_rounded_variants.
  3. Корневой элемент XML’льки задаем shape (дословный перевод “форма”). Все, по сути этот файл уже можно присваивать вью номера варианта в качестве бэкграунда, но у него нет никаких свойств и поэтому он никак не видоизменится. Пропишем android:background="@drawable/shape_rounded_variants” и убедимся в этом. Как и в случае с иконкой закрытия мы указываем ссылку на файл с типом ресурса drawable. Для стилизации фигуры добавим внутри нее два специальных тега.
  4. Первый тег solid — отвечает за заливку фигуры. У тега надо указать атрибут цвета заливки — color с соответствующим макету значением.
  5. Второй тег corners — обращается к углам фигуры. В качестве атрибута устанавливаем скругление через radius10dp.

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

Теперь создадим фигуру с обводкой для контейнера варианта ответа по аналогии. Тег, который отвечает за обводку — stroke. В качестве атрибутов тега устанавливаем толщину линии — по макету это 1dp. И соответствующий цвет. Присваиваем background для контейнера элемента и проверяем. Все работает.

Осталось продублировать эти элементы. У трех оставшихся корректируем верхний отступ. По макету это 18dp. И меняем порядковую нумерацию ответов.

Базовая верстка view вариантов ответа завершена. Обработка поведения и стилизация для отображения правильного и неправильного вариантов ответа будет нами производиться из кода файла MainActivity.kt.

Наконец обратим внимание на макет в его нижней части экрана. Нас интересует кнопка пропуска изучаемого слова на первом экране и информационная плашка с результатом ответа на втором. Между этими элементами есть одно общее свойство — оба они прижаты к нижней части экрана.

Кнопка пропуска изучаемого слова

LinearLayout и layout_gravity

Дальше дело за малым. Добавить кнопку, контейнер с несколькими вложенными элементами внутри. И спозиционировать с помощью атрибута layout_gravity со значением bottom, казалось бы. Но вот какая штука. LinearLayout дословно переводится, как “линейный макет”. Это говорит о том, что элементы располагаются в нем последовательно друг за другом и никак иначе. Вот почему применение атрибута android:layout_gravity=»bottom” для кнопки не сработает и не имеет смысла в целом.

Расположение элементов линейно. Поэтому для вертикальной ориентации элементы внутри контейнера можно позиционировать с помощью android:layout_gravity только по горизонтали. А для горизонтальной ориентации — только по вертикали.

Для решения нашей задачи можно было бы воспользоваться добавлением пустого компонента View (или Space) между элементами, в котором будет атрибут android:layout_weight="1”. Он займет все свободное пустое пространство между элементами, которые надо раздвинуть и прижмет кнопку в низу экрана. Но этот способ выглядит костыльным.

И это наглядная демонстрация, когда лучшим решением будет замена основного контейнера на ConstraintLayout ради единственной цели — добавить отношение к выбранной стороне.

Замена основного контейнера на ConstraintLayout

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

Позиционирование кнопки

Верстка и стилизация футера

Стилизация кнопки. По макету ширина 310dp и высота 58dp. Цвет кнопки синий и задается атрибутом backgroundTint. Берем код значения типа Hex. И кнопка почти готова. Ее форма по прежнему отличается от дизайна, но об этом будет ниже. Приступим к следующему состоянию, когда выбрано правильное слово.

Скрою эту кнопку с помощью атрибута visibility. Данный атрибут можно применять к любым view и принимать он может один из трех параметров:

  • visible — элемент виден на экране пользователя, поведение по умолчанию.
  • invisible — элемент не виден на экране, но занимает пространство и влияет на позиционирование других “вью”, которые от него зависят.
  • gone — элемент полностью пропадает из макета и не занимает никакого места в пространстве.

Поэтому я буду использовать gone, чтобы по умолчанию кнопка была скрыта. Показывать будем ее программно из кода MainActivity по определенным условиям. Почему я не сделал один элемент и для кнопки пропуска и для кнопки продолжения? Теоретически можно было бы манипулировать окрашиванием и видимостью элементов программно — то есть из MainActivity. Ну, не всегда хорошо менять большое количество точечных элементов через код. Лучше постараться минимизировать изменения UI таким способом. В долгой перспективе это может привести к ошибкам. Где возможно — лучше ограничиться только изменениями контента и менять только видимость элементов.

Окей. Взглянем на макет информационного блока. Его можно сверстать по аналогии с вложенными LinearLayout. То есть один горизонтальный, чтобы разместить иконку и текст “Правильно”. И он будет обернут в вертикальный лейаут, чтобы ниже разместить кнопку. Однако используя ConstraintLayout можно вообще обойтись без вложенности, поэтому так и сделаем.

Собственно начнем с контейнера.

  • Ширина будет родительская, то есть на весь экран, а высота по размеру контента.
  • Проставим констрейнты. Прижимаем к родителю вниз и по бокам.
  • Цвет задается с помощью background, проставляем Hex код цвета по макету.

Готово. Как видите высота у него 0, поэтому ничего не видно. Его тело появится по мере того, как внутренние элементы будут занимать место. Но у нас есть великолепный атрибут tools. Проставим в него высоту контейнера, нам удобно, а на пользователя это никак не повлияет.

Элемент иконка «с пальцем вверх»

Далее иконка с пальцем вверх. Можно создать элемент с фоном shapeDrawable и поместить внутрь иконку, но зачем, когда можно экспортировать сразу группу. Пусть это будет единой иконкой.

Иконка с пальцем вверх

Импортируем в проект, как вектор. Для размещения элемента используем ImageView. Задаем размер по размеру контента (у иконки будут фиксированный размер) и проставляем ссылку на ресурс в атрибут src. Констрейнты будут сверху и слева, проставляем иконке марджины согласно макету.

Теперь текст. Его нам надо разместить справа от иконки и с вертикальным центрированием относительно этой икноки. Это делается до неприличия просто также с помощью констрейнтов. Добавляем TextView. Констрейнты для него будут такие:

  • Слева — чтобы разместить относительно иконки.
  • Сверху и снизу — для центрирования относительно иконки. Помните, в вводном уроке по верстке я говорил, что если разместить элемент между двумя другими view, он занимает положение по центру? Если присвоить констрейнты к верху и низу иконки соответственно, элемент будет располагаться строго по центру относительно ImageView.

Осталось только добавить отступ слева и стилизовать текст согласно макету.

Добавляем Button

Великолепно! И, наконец, кнопка. Добавляем тэг Button, позиционируем по бокам и сверху. За ориентир берем тоже иконку. Пропишем отступы сверху и снизу. Стилизация тоже простая — размеры и цвет текста.

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

Проблема в том, что при создании проекта Android Studio добавляет в проект вспомогательные библиотеки, в том числе Material Design. При ее добавлении механизм обработки XML разметки подменяется и, к примеру, Button под капотом заменяется на MaterialButton.

Material Design — это спецификация по дизайну приложений от Google. Рекомендации по визуальному оформлению приложений. Включает дизайнерские рекомендации по анимациям, шрифтам, цветам и так далее. Заметили же, что оформление системных компонентов Android и iOS выполнено в едином стиле — это все специализированные спецификации по дизайну. Для каждой операционной системы они свои.

Что делать? Давайте в теге ключевое слово Button заменим на android.widget.Button.

Button заменяем на android.widget.Button

Таким образом мы принудительно указали прямой путь к компоненту Button из стандартной библиотеки Android (благодяря указанному пути до компонента). Таким образом при инфлейтинге, когда вызывается создание кнопки, система будет обращаться к конкретному компоненту. А не подменяться на сторонюю библиотеку Material, как было в данном случае. И.. все починилось.

Подытожим. Это решение вполне рабочее, но не идеальное. Мы напрямую используем класс кнопки из SDK, поэтому есть вероятность, что она может по разному выглядеть в разных версиях Android. В будущих уроках капнем немного глубже и научимся корректно работать с переопределением поведения и внешнего вида компонентов из Material Design.

Для тех, кто собрался стать Android-разработчиком

Пошаговаясхема
Пошаговая
схема

Описание процесса обучения от основ Kotlin до Android-разработчика

Бесплатныеуроки
Бесплатные
уроки

Авторский бесплатный курс по основам языка программирования Kotlin

Обучающийбот
Обучающий
бот

Тренажер и самоучитель по Котлин – бесплатные тесты и практика

Поделиться уроком

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *