Сбербанк делится опытом создания приложения в Material Design: стили и темы
Мы передаём слово команде разработчиков 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, текст, размеры и другие стили.
- 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.
Ссылки на значения атрибутовНастройка стилей и тем
Основные цвета приложенияРассмотрим атрибуты, указывающие основные цвета приложения.
Атрибут Назначение 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. Заменим этот цвет.
За сфокусированное состояние (а также за цвет курсора и text-select-handle-ов) отвечает colorControlActivated — изменим и его, пусть он имеет то же значение, что и colorPrimary:
Размер шрифта EditText-а — задаётся через TextAppearance.
- 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), изменяется только цвет.
Дальше всё просто: заменим цвет текста на 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’а с двумя разными отступами. Мы решили не менять весь стиль из-за одного незначительного параметра, а менять сам этот параметр.
Заведем новый атрибут в теме:
Определим новый стиль 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, где можно на деле оценить результаты нашей работы.
Спасибо за внимание, в следующем выпуске мы расскажем про процесс создания интерфейса нашего приложения.