Привет! Не так давно мы подводили итоги конкурса по 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’ов.
Если встает необходимость изменить цвет текста на информационных ячейках, то необходимо пройтись по каждой разметке (при этом есть ненулевая вероятность пропустить какой-либо файл). Стоимость операции возрастает многократно при экспериментах «Последовательно проверить несколько цветов/отступов».
Наконец, если псевдоним заведен для ресурса не системным образом, то, скорее всего, его название неактуально для нового экрана, и программист вместо использования готового ресурса создаст новую сущность.
Чтобы всего этого бардака избежать, требуется наладить здоровую атмосферу в рабочем коллективе:
Между программистами и дизайнерами должна быть налажена коммуникация: они должны понимать и принимать потребности и возможности друг друга;
Список требуемых данных для построения универсальной и масштабируемой вёрстки приложения должен быть чётко закреплён: так программисты получат все необходимые для них значения, а дизайнеры смогут отталкиваться от имеющихся значений при построении новых экранов.
Doing it right way
Своеобразным «первым мостиком» в построении коммуникации между программистами и дизайнерами выступили 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.Sbrf унаследует значение атрибута isLightTheme у Theme.AppCompat.Light.NoActionBar.
Ссылки на значения атрибутов
Стили низкого уровня (TextAppearance и Widget-стили) могут ссылаться на значение атрибута, установленного темой.
Допустим, в приложении есть 2 темы: красная и желтая. Нам необходимо, чтобы на экранах с желтой темой некоторый TextAppearance задавал своим 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, или в конструкторе виджета на более ранних версиях).
За сфокусированное состояние (а также за цвет курсора и text-select-handle-ов) отвечает colorControlActivated — изменим и его, пусть он имеет то же значение, что и colorPrimary:
Со стилизацией 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:
Применим свежесозданный стиль к TextInputLayout в разметке. Вот что у нас получилось:
Напоследок внесем важное улучшение. Обычно для каждого виджета существует атрибут в теме, по которому он в своем конструкторе может взять актуальный для данной темы стиль. TextInputLayout (как и другие виджеты библиотеки Design) такого атрибута в теме не имеет. Введем его на стороне приложения:
С текстовыми полями, шрифтами и цветами мы справились. Осталась ещё пара важных элементов, в которых надо навести порядок: 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’а с двумя разными отступами. Мы решили не менять весь стиль из-за одного незначительного параметра, а менять сам этот параметр.
Определим новый атрибут в 2 темах, в родительской теме переопределим стиль toolbar’а.
<style name="Theme.Material.Sbrf" parent="@style/Theme.AppCompat.Light.NoActionBar">
<item name="toolbarContentInsetStart">72dp</item>
<item name="toolbarStyle">@style/Widget.Material.Sbrf.Toolbar</item>
</style>
<style name="Theme.Material.Sbrf.Colored">
<i><!-- Здесь мы переопределим различные атрибуты, специфичные для данной темы --></i>
</style>
<style name="Theme.Material.Sbrf.Colored.IconlessList">
<i></i> <item name="toolbarContentInsetStart">16dp</item>
</style>
Теперь, в зависимости от указанной темы, текст будет иметь различный отступ от левого края. То, что и требовалось сделать по гайдлайнам.
Цвет текста в 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, где можно на деле оценить результаты нашей работы.
Спасибо за внимание, в следующем выпуске мы расскажем про процесс создания интерфейса нашего приложения.