Сбербанк делится опытом создания приложения в Material Design: стили и темы
Привет, Хабрахабр! Не так давно мы подводили итоги конкурса по Material Design, и в комментариях нас просили показать реально популярные и красивые Material-приложения. Что же, встречайте: «Сбербанк Онлайн» в новом, современном интерфейсе. Про процесс создания приложения интереснее узнать от самих создателей.
Мы передаём слово команде разработчиков Android-приложения Сбербанка, чтобы вы услышали об опыте создания такой сложной штуки, как UI мобильного банк-клиента, из первых уст. Большую часть поста написал freeuser, так что спасибо говорите ему. ;)
Android 5, основанный на Material Design, доступен пользователям уже полтора года. 18 месяцев — срок немалый, и сейчас можно с уверенностью сказать, что и пользователям, и разработчикам пришлась по душе философия визуального языка представления информации от Google.
Сегодня мы расскажем, как в нашей расширяющейся и развивающейся команде протекал процесс перехода приложения »Сбербанк Онлайн» для Android на Material Design, и осветим сопутствующие технические аспекты. Процесс непосредственно создания интерфейса будет подробно описан в следующей статье.
Наша основная задача — сделать удобное и понятное приложение, которым было бы приятно пользоваться. Вместе с тем, переход на Material Design позволил бы качественно улучшить и процесс разработки. То есть, программисты и дизайнеры меньше будут отвлекаться на «процесс», больше — на результате.
Единый визуальный язык, который разработали в Google, позволил меньше задумываться о том, «что делать», сосредоточившись на вопросе «как делать». И если предыдущая попытка (Holo) не остановила калькирование интерфейсов с iOS или придумывание велосипедов, то новая система (MD) практически полностью «убила» зоопарк дизайнерских изысков. Приложения не стали скучнее или однообразнее, но стали единообразнее: MD позволяет крайне свободно использовать имеющиеся инструменты и возможности визуального языка, вместе с тем сохраняя преемственность интерфейсов, что удобно и пользователям, и разработчикам. Когда все приложения работают одинаково и различные элементы интерфейса в них работают одинаково, вам не требуется привыкать к новым приложениям. Берёте и пользуетесь.
Простота, удобство для пользователя и эффективность — вот ключевые понятия, от которых мы отталкивались в работе над новым приложением.
Общие проблемы для разработки почти всегда одни и те же:
- Программисты не всегда внимательно относятся к макетам;
- Дизайнеры слабо осведомлены о том, насколько трудозатратна для программиста реализация того или иного варианта верстки;
- Изменения в одной разметке (layout) не масштабируются автоматом на другие.
В корне этих «общих» проблем лежат вполне конкретная беда, которую, увы, очень тяжело уладить. Дизайнеры и программисты говорят на своих языках и не понимают друг друга. В чём заключается недопонимание?
- Программист не может смоделировать мышление дизайнера, из-за чего не может установить от чего ему отталкиваться в верстке;
- Дизайнер не знает, какую именно информацию выдавать программисту для того, чтобы тот сверстал масштабируемо и с точным соответствием макету.
Рассмотрим частный случай типичного рабочего процесса:
На макетах экранов приложения дизайнеру необходимо отметить размеры и цвет текста, ширину и высоту элементов. При нулевой коммуникации (и, следовательно, нулевом понимании потребностей и возможностей друг друга) задача решается в лоб: на макетах зашиваются непосредственно значения — размер, цвет.
Далее, исполнитель верстки либо жестко забивает значения в разметку, либо заводит новый ресурс (size, color), либо пытается по значению отыскать уже имеющийся ресурс.
Жесткое зашивание значений в разметку либо несистематизированное заведение ресурсов влекут за собой невозможность повторного использования одних и тех же значений: нарушается и принцип DRY, и вообще сама логика разделения конструкции и её стилей. Работать над таким приложением примерно также «приятно», как над веб-страницей, на которой половина значений стилей указана прямо внутри div«ов.
Если встает необходимость изменить цвет текста на информационных ячейках, то необходимо пройтись по каждой разметке (при этом есть ненулевая вероятность пропустить какой-либо файл). Стоимость операции возрастает многократно при экспериментах «Последовательно проверить несколько цветов/отступов».Наконец, если псевдоним заведен для ресурса не системным образом, то, скорее всего, его название неактуально для нового экрана, и программист вместо использования готового ресурса создаст новую сущность.
Чтобы всего этого бардака избежать, требуется наладить здоровую атмосферу в рабочем коллективе:
- Между программистами и дизайнерами должна быть налажена коммуникация: они должны понимать и принимать потребности и возможности друг друга;
- Список требуемых данных для построения универсальной и масштабируемой вёрстки приложения должен быть чётко закреплён: так программисты получат все необходимые для них значения, а дизайнеры смогут отталкиваться от имеющихся значений при построении новых экранов.
Своеобразным «первым мостиком» в построении коммуникации между программистами и дизайнерами выступили Google Material Guidelines. Гайдлайны достаточно гибки, чтобы не стеснять творческую мысль дизайнера и одновременно они определяют закономерности, удобные для моделирования программистом.
Для масштабируемой верстки в OS Android используется такой инструмент как стили. Стили — это набор пар «атрибут-значение». Значениями атрибутов могут выступать цвета, ColorStateList, Drawable, текст, размеры и другие стили.
При исследовании ресурсов последних платформ и библиотеки AppCompat можно обнаружить, что стили делятся на следующие группы:
- TextAppearance — внешний вид текстового поля. Определяют цвета текста и ссылок, подсказок в текстовом поле, фон выделенного текста, стиль и гарнитуру шрифта;
- Widget-стиль — стиль View. Определяют специфические для конкретного виджета атрибуты (например, progressDrawable для ProgressBar), виджет-стили TextView и его наследников могут указывать используемый TextAppearance;
- Theme — тема. Темы — это своего рода «стили стилей», стили активити; Определяют ключевые атрибуты, характерные для данного экрана, стили виджетов. Грамотно составив тему, мы избежим прямых ссылок на цвета и стили виджетов внутри разметок (layout), установим единообразный внешний вид для View в рамках приложения;
- ThemeOverlay — занимают промежуточное положение между виджет-стилями и темами. Если обычный виджет-стиль применяется только к одному View, тема — ко всем View в пределах экрана, то ThemeOverlay проставляют на определенный контейнер (ViewGroup) в разметке. В дальнейшем мы покажем, где именно он применяется и как.
В нашем случае нормально построенный рабочий процесс выглядит так:
- Программисты и дизайнеры, основываясь на Google Material Guidelines, формируют единые таблицы ресурсов (далее в статье вы их увидите);
- Каждому ресурсу (размеру, стилю, цвету) присваивается псевдоним;
- Дизайнер в макете указывает именно псевдоним. По этому псевдониму программист находит ресурс (размер, стиль) и указывает его в разметке.
Прежде чем мы займемся настройкой стилей, давайте рассмотрим свойства этого инструмента поближе — чтобы воспользоваться им с максимальной эффективностью.
Наследование стилей
Стили могут наследоваться друг от друга двумя способами. Вот первый:
Theme.Material в таком случае унаследует у родительской темы Theme значение атрибута isLightTheme и переопределит значение colorBackground.
Второй способ — указать родителя явно.
В таком случае Theme.Material.Sbrf унаследует значение атрибута isLightTheme у Theme.AppCompat.Light.NoActionBar.
Ссылки на значения атрибутов
Стили низкого уровня (TextAppearance и Widget-стили) могут ссылаться на значение атрибута, установленного темой.
Допустим, в приложении есть 2 темы: красная и желтая. Нам необходимо, чтобы на экранах с желтой темой некоторый TextAppearance задавал своим TextView желтый бэкграунд, а на экранах с красной темой — красный бэкграунд.
Пусть TextAppearance.Example проставлен на некоторый TextView.
Тогда при применении TextAppearance для этого виджета Андроид подставит в качестве значения textColor именно то значение colorPrimary, которое прописано в данной теме.
Основные цвета приложения
Рассмотрим атрибуты, указывающие основные цвета приложения.
Атрибут |
Назначение |
colorPrimary |
Главный цвет приложения. Обычно в него красят AppBar |
colorPrimaryDark |
Более темная версия главного цвета — используется для статус-бара |
colorAccent |
Акцентный цвет приложения. Используется косвенным образом для кнопок, SeekBar, ProgressBar |
android: colorBackground |
Дефолтный цвет фона |
android: colorForeground |
Противоположность вторичному цвету фона |
android: colorForegroundInverse |
Вторичный цвет фона |
dividerHorizontal |
Цвет горизонтального разделителя |
dividerVertical |
Цвет вертикального разделителя |
У Сбербанка есть брендбук, в соответствии с которым требуется оформлять всё: печатную продукцию, интернет-рекламу, сайты, визитки, пакеты и т.д. Официальные цвета регламентированы, их «ближайшие аналоги» — тоже. В соответствии с брендбуком Главным цветом (colorPrimary) для нашего приложения стал зелёный, а Акцентным (colorAccent) — оранжевый. Кроме того, colorPrimary относился к позитивным действиям (подтверждение, покупка), а colorAccent, как правило, — к негативным (отмена).
Стилизация TextView
Настало время разобраться с цветами для текстов, которые предлагаются библиотекой AppCompat. Всего есть две темы (тёмная и светлая), в каждой из которых есть две группы цветов: стандартные и инвертированные. Для тёмной темы (наследницы Theme.AppCompat) стандартными будут светлые оттенки, инвертированными — тёмные. Для светлой темы (которая также наследует значения у Theme.AppCompat), соответственно, наоборот.
Заданные библиотекой цвета имеют свои «названия» и функциональные назначения:
- textColorPrimary — наиболее «сочная» форма цвета.
- textColorSecondary — чуть более бледная форма цвета.
- textColorTertiary — еще более бледная форма.
- textColorHint — цвет подсказок в EditText.
- textColorLink — цвет ссылок.
- textColorHighlight — цвет фона выделенного текста.
- textColorPrimaryActivated — в обычном состоянии то же, что и textColorPrimary, в активированном состоянии — textColorPrimaryInverse
- textColorSecondaryActivated — аналогично textColorPrimaryActivated, только со вторичным цветом.
Кроме того, есть цвета с «жутковатыми» названиями типа textColorPrimaryDisableOnly (используются для текста CompoundButton«ов: радиокнопки, чекбоксы), или textColorPrimaryNoDisable, textColorSecondaryDisableOnly, textColorSecondaryNoDisable — использование последних трех в стилях и Text Appearance обнаружить не удалось.
Мы составили таблицу цветов, используемых в приложении. Каждому атрибуту мы поставим значением ColorStateList — пару из цветов для доступного элемента и недоступного:
Атрибут |
АЦвет обычного состояния |
АЦвет недоступного состояния (disabled-элементов) |
textColorPrimary |
#E6000000 (90% черного) |
#44000000 (26% черного) |
textColorSecondary |
#CC000000 (80% черного) |
#44000000 (26% черного) |
textColorTertiary |
#88000000 (53% черного) |
#44000000 (26% черного) |
textColorHint |
#61000000 (38% черного) |
#19000000 (10% черного) |
textColorPrimaryInverse |
#FFFFFFFF (100% белого) |
#44FFFFFF (26% белого) |
textColorSecondaryInverse |
#E8FFFFFF (91% белого) |
#44FFFFFF (26% белого) |
textColorTertiaryInverse |
#96FFFFFF (59% белого) |
#44FFFFFF (26% белого) |
textColorHintInverse |
#61FFFFFF (38% белого) |
#19FFFFFF (10% белого) |
Для наглядного отображения текстовой информации AppCompat работает не только с цветами, но и с размером шрифта и его начертанием. Данный набор параметров называется TextAppearance.
Название |
АЦвет текста |
АРазмер шрифта |
АГарнитура шрифта |
Caption |
Secondary |
12sp |
Regular |
Body1 |
Primary |
14sp |
Regular |
Body2 |
Primary |
14sp |
Medium |
Button |
Primary |
14sp |
Medium |
Subhead |
Primary |
16sp |
Regular |
Menu |
Primary |
16sp |
Medium |
Title |
Primary |
20sp |
Medium |
Headline |
Primary |
24sp |
Regular |
Display1 |
Secondary |
34sp |
Regular |
Display2 |
Secondary |
45sp |
Regular |
Display3 |
Secondary |
56sp |
Regular |
Display4 |
Secondary |
112sp |
Light |
Хоть в таблице это и не указано, но для Title и для Menu определены также инвертированные Text Appearance.
Наглядные примеры правильно сформированных Text Appearance можно увидеть в спецификации гайдлайнов Google Material Design. На основе этих стилей и информации из гайдлайнов разработчики совместно с дизайнерами создали следующее семейство Text Appearance:
Название |
Цвет текста |
Размер шрифта |
Гарнитура шрифта |
Caption |
Tertiary |
12sp |
Regular |
Hint |
Hint |
14sp |
Regular |
Subheader |
Tertiary |
14sp |
Medium |
Button |
Primary |
14sp |
Medium |
Body0 |
Tertiary |
14sp |
Regular |
Body1 |
Secondary |
14sp |
Regular |
Body2 |
Primary |
16sp |
Regular |
Body3 |
Secondary |
16sp |
Medium |
Input1 |
Primary |
20sp |
Regular |
Input2 |
Tertiary |
20sp |
Regular |
Title |
Primary |
20sp |
Medium |
Headline |
Primary |
24sp |
Regular |
Display1 |
Primary |
32sp |
Regular |
Display2 |
Tertiary |
32sp |
Regular |
Из каждого TextAppearance, приведенного выше (за исключением Button), мы формируем Inverse, Primary и Accent-варианты (для цвета текста используются заданные ранее значения атрибутов textColor*Inverse, colorPrimary, colorAccent).
Когда дизайнеры передают нам макеты, то напротив текстовых полей (TextView) они указывают сокращенное имя «Title Default» соответствует TextAppearance.Material.Sbrf.Title, а «Subheader Inverse» соответствует TextAppearance.Material.Sbrf.Subheader.Inverse.
Стилизация EditText
С цветами и оформлением текста мы более-менее разобрались, и осталось ещё одно место, в котором надо всё привести в порядок: речь идёт о элементах EditText и TextInputLayout. Проблема в том, что «тема по умолчанию» выглядит неаккуратно, и её надо причесать по фен-шую. :)
Для невнимательных поясним:
- Линия EditText в несфокусированном и задисейбленном состоянии должна быть бледнее;
- Линия EditText в сфокусированном состоянии должна быть сплошной зелёной (colorPrimary), а не черной-оранжевой;
- Ошибка выделяется оранжевым цветом (colorAccent), а не красным;.
- Размер шрифта EditText-а должен быть крупнее.
Внутри библиотеки AppCompat мы обнаружили что при создании View из разметки AppCompatActivity подменяет определенные виджеты на их AppCompat-наследников. Делается это, судя по всему, для поддержки background tinting и подтягивания стилей из AppCompat-тем. В частности, EditText заменяется на AppCompatEditText. (Следовательно, если вам нужен кастомный виджет, то создавать подкласс нужно не непосредственно из EditText, а из AppCompatEditText).
Стиль AppCompatEditText-у задается в теме через атрибут editTextStyle. По умолчанию стиль — Widget.AppCompat.EditText, задающий определенный TextAppearance, фон и цвет текста (то есть, цвет из TextAppearance игнорируется).
Взглянем на разметку бэкграунда EditText-а для версии 5.0 (для старых версий background в целом похож, разница только в том, где происходит его подкрашивание (tinting) — в самой разметке, как на 5.x, или в конструкторе виджета на более ранних версиях).
-
-
-
А вот сами ресурсы textfield_default_mtrl_alpha и textfield_activated_mtrl_alpha:
Как видно из разметки, тонкая линия подкрашивается цветом, являющимся значением атрибута colorControlNormal. Заменим этот цвет.
#1A000000
За сфокусированное состояние (а также за цвет курсора и text-select-handle-ов) отвечает colorControlActivated — изменим и его, пусть он имеет то же значение, что и colorPrimary:
Размер шрифта EditText-а — задаётся через TextAppearance.
Результат:
Со стилизацией EditText покончено. Настало время TextInputLayout. По умолчанию он применяет к себе стиль Widget.Design.TextInputLayout, извлекая из него следующие атрибуты:
- hintTextAppearance — TextAppearance плавающего лейбла (на нашем скрине в нем написано слово «Подсказка») в сфокусированном состоянии;
- android: textColorHint — цвет плавающего лейбла в несфокусированном состоянии, цвет подсказки в EditText. Если значение атрибута у TextInputLayout не указано, то подтянется значение этого же атрибута у EditText;
- android: hint — собственно текст плавающего лейбла, перегружает подсказку самого EditText;
- hintAnimationEnabled — применять ли анимацию при «переходе» подсказки из EditText-а в плавающий лейбл.
- errorTextAppearance — TextAppearance лейбла с ошибкой. В данном случае подкрашивается линия EditText-а;
- errorEnabled — следует ли при отрисовке TextInputLayout заранее закладывать место под лейбл с ошибкой;
- counterTextAppearance — TextAppearance счетчика длины текста.
- counterEnabled — следует ли при отрисовке TextInputLayout показывать счетчик длины текста.
- counterMaxLength — максимально допустимая длина текста.
- counterOverflowTextAppearance — TextAppearance индикатора ошибки превышения длины текста.
Составим таблицу свойств TextAppearance вспомогательных TextView. Все они наследуются от TextAppearance.AppCompat.Caption (textColorSecondary, 12sp, regular), изменяется только цвет.
Название Цвет текста |
? attr/colorControlActivated |
TextAppearance.Design.Error |
#FFDD2C00 |
TextAppearance.Design.Counter |
? attr/colorControlActivated |
TextAppearance.Design.Counter.Overflow |
#FFDD2C00 |
Дальше всё просто: заменим цвет текста на colorAccent:
Применим этот TextAppearance в стиле TextInputLayout:
Применим свежесозданный стиль к TextInputLayout в разметке. Вот что у нас получилось:
Напоследок внесем важное улучшение. Обычно для каждого виджета существует атрибут в теме, по которому он в своем конструкторе может взять актуальный для данной темы стиль. TextInputLayout (как и другие виджеты библиотеки Design) такого атрибута в теме не имеет. Введем его на стороне приложения:
Внедрим его в тему:
Теперь мы можем по этому атрибуту подтягивать стиль виджета в разметке:
Стилизация AppBar-а
С текстовыми полями, шрифтами и цветами мы справились. Осталась ещё пара важных элементов, в которых надо навести порядок: AppBar и Toolbar. В типичном Activity при использовании Material-дизайна верхняя панель по стилю отличается от контента Activity: она отличается другим фоном, другим цветом текста. Google предполагает, что мы для этого будем пользоваться атрибутом actionBarTheme. С ним и будем работать.
Атрибут actionBarTheme ссылается на ThemeOverlay — своего рода мини-тему, применяемую к отдельному ViewGroup, его дочерним элементам, их дочерним элементам и так далее. При применении ThemeOverlay одноименные атрибуты темы Activity получают новое значение. Для большей наглядности рассмотрим 3 скриншота: на первом к верхнему бару и виджетам в нем не применяются ни стиль, ни ThemeOverlay, на втором применяются зелёный стиль и ThemeOverlay с белым текстом, на третьем — белый стиль и ThemeOverlay с зелёным текстом.
Цвет фона AppBarLayout. В ресурсах библиотеки Design указано, что Background этого виджета красится в первичный цвет приложения (colorPrimary). Создадим стили для AppBarLayout:
По аналогии с TextInputLayout создадим в теме атрибут appBarLayoutStyle, чтобы по нему в разметке получать актуальный стиль виджета.
Стиль TabLayout. Решается аналогично задаче с AppBarLayout.
Цвет иконок в toolbar«е задается посредством атрибута colorControlNormal. Однако, здесь есть некоторые проблемы, которые надо решить. Цвет colorControlNorman мы использовали для линии EditText-а в несфокусированном состоянии (автоматом цвет также применился для цвета незаполненного прогресса в детерминированном ProgressBar и в SeekBar). ThemeOverlay с белым текстом переопределяет colorControlNormal как #FFFFFF (белый). ThemeOverlay с зелёным переопределяет colorControlNormal как? attr/colorPrimary (у нас он, как вы помните, зелёный).
Определим ThemeOverlay.
И применим их в разметке:
Отступ текста в toolbar«е от левого края. Google Material Guidelines предписывают его выставлять в 72dp, если есть кнопка Up, и 16dp, если её нет. Как вариант, мы можем в двух разных темах для таких Activity проставить два разных стиля toolbar«а с двумя разными отступами. Мы решили не менять весь стиль из-за одного незначительного параметра, а менять сам этот параметр.
Заведем новый атрибут в теме:
<declare-styleable name="Theme.Material.Sbrf">
<attr name="toolbarContentInsetStart" format="dimension"/>
declare-styleable>
Определим новый стиль toolbar«а:
Определим новый атрибут в 2 темах, в родительской теме переопределим стиль toolbar«а.
Теперь, в зависимости от указанной темы, текст будет иметь различный отступ от левого края. То, что и требовалось сделать по гайдлайнам.
Цвет текста в toolbar«е. В стилях AppCompat по умолчанию задан цвет для текста: textColorPrimary. В самой теме textColorPrimary заведён как 90% черный. зелёный ThemeOverlay переопределяет textColorPrimary как 100% белый. Белый ThemeOverlay переопределяет его как? attr/colorPrimary.
Ограничения ThemeOverlay. Не применяются к View фрагментов, лежащих внутри AppBarLayout.
Приложение стало доступно в Google Play на днях, и самое время подвести итоги наших реформ.
Мы значительно сократили время, необходимое на вёрстку приложения: благодаря унифицированным TextAppearance, отступам и стилям создание новых экранов и редактирование старых происходит быстро и эффективно. Больше никаких бессмысленных трат времени на ручное измерение отступов, на проверку цветов в ColorPicker-е (оттуда еще поди достань альфа-канал для цвета): посмотрел на псевдонима ресурса в макете, выбрал соответствующий ресурс, работаешь дальше.
Программисты понимают, от чего отталкиваются и что подразумевают дизайнеры при решении UX-задач. Они учитывают это при стилизации виджета. Например, если дизайнер отрисовал зелёную кнопку на макете, то в приложении должен быть не безликий кирпич-заглушка, а кнопка, реагирующая на нажатие. Дизайнеры осознают, какой элемент для платформы Андроид родной, а какой — требуется сильно дорабатывать и сложно поддерживать.
Налаженное взаимопонимание между программистами и дизайнерами позволило выдавать результат быстрее и надежнее. Правки в верстку вносились по щелчку пальцев: чего стоит одна только замена зелёного аппбара на белый (достаточно добавить новый стиль AppBarLayout, новый ThemeOverlay, соединить их в одной теме и назначить её основной для всего проекта).
Естественно, некоторые идеи оказались неудачными. Мы сделали неверное предположение, что нам хватит 5 минимальных высот ячеек; на практике же оказалось, что дизайнеры при проектировании не используют такую характеристику, а отталкиваются непосредственно от контента. Система именований отступов оказалось неудачной и не наглядной.
Несмотря на это, основная масса принятых правил оказалась чрезвычайно эффективной на практике. С помощью новых инструментов мы создали гармоничное приложение в стиле Material. Приглашаем всех в Google Play, где можно на деле оценить результаты нашей работы.
Спасибо за внимание, в следующем выпуске мы расскажем про процесс создания интерфейса нашего приложения.