DayNight: настраиваем автоматическую смену темы в android в зависимости от времени суток
Мы уже писали, что вышла новая библиотека поддержки Android Support Library 23.2. Новая либа добавляет сразу несколько интересных вещей: это и поддержка векторных изображений в android ниже 5.0, и новые элементы материального дизайна, такие как Bottom Sheets.
Одна из фишек – новая тема DayNight с поддержкой автоматического переключения между светлой и темной темой оформления в зависимости от времени суток. Ее то мы сегодня и потестируем.
Об этом смотрите видео, а под видео – подробнее от разработчиков том, как гибко можно настроить работу и ресурсы для темы DayNight.
Theme.AppCompat.DayNight – это переключение между Theme.AppCompat (темной) и Theme.AppCompat.Light (светлой) на основе времени суток. Это имеет много преимуществ для пользователей, особенно если у вас content app . Эта функция работает на API v14 и выше, на младших версиях будет по умолчанию светлая тема.
Как использовать Theme.AppCompat.DayNight ?
Просто унаследуйте вашу тему от одного из вариантов DayNight в файле res/values/styles.xml:
Затем необходимо включить функцию в вашем приложении. Сделайте это путем вызова статического метода AppCompatDelegate.setDefaultNightMode(), который принимает одно из четырех значений:
- MODE_NIGHT_NO. Всегда используется дневная тема (светлая).
- MODE_NIGHT_YES. Всегда используется ночная тема (темная).
- MODE_NIGHT_AUTO. Автоматическое изменение между светлой/темной, в зависимости от времени суток.
- MODE_NIGHT_FOLLOW_SYSTEM (по умолчанию). Это системный параметр, который является по существу MODE_NIGHT_NO на момент написания (подробнее об этом ниже).
Вызов метода static, поэтому его можно вызвать в любое время. Устанавливаемое значение не сохраняется, поэтому вам нужно установить его каждый раз, когда ваше приложение стартует. Рекомендуется устанавливать его в статический блок в классе вашего приложения (если у вас он один), или вашей Activity, так:
static
Метод setLocalNightMode()
Можно переопределить значение по умолчанию в каждом компоненте путем вызова его AppCompatDelegate setLocalNightMode(). Это удобно, когда вы знаете, что только некоторые компоненты должны использовать функцию DayNight, или для тестирования, чтобы не сидеть и ждать ночи для проверки макета.
Обратите внимание, что этот вызов не заботится о восстановлении, если изменить режим ночь после любого вызова inflate(), он не будет иметь никакого эффекта. В этом случае можно использовать вызов recreate():
public class MyActivity extends AppCompatActivity < public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); if (savedInstanceState == null) < // Set the local night mode to some value getDelegate().setLocalNightMode( AppCompatDelegate.MODE_NIGHT_. ); // Now recreate for it to take effect recreate(); >> >
Как проверить, какой режим может использовать мое приложение?
Просто проверьте конфигурацию ресурсов:
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; switch (currentNightMode) < case Configuration.UI_MODE_NIGHT_NO: // Night mode is not active, we're in day time case Configuration.UI_MODE_NIGHT_YES: // Night mode is active, we're at night! case Configuration.UI_MODE_NIGHT_UNDEFINED: // We don't know what mode we're in, assume notnight >
Если приложение выглядит странно
Например, текст нечитабельный или иконки неправильного цвета.
Поскольку эта функция изменяет тему вашего приложения, вам нужно убедиться, что ваши макеты/стили/графика корректно отображались в светлых и темных условиях.
Возьмите за правило для этих вещей всегда использовать атрибуты темы, по возможности. Вот наиболее важные:
- ?android:attr/textColorPrimary – Цвет текста общего назначения. Будет черным для светлой темы, и белым для темной темы.
- ?attr/colorControlNormal – Цвет значка общего назначения.
WebView
Существует один большой нюанс этой функции: WebViews. Поскольку они не могут использовать атрибуты темы, и вы редко имеете контроль над любым веб-контентом, существует высокая вероятность того, что ваш WebView будет слишком контрастным против динамического оформления приложения. Поэтому проверьте ваше приложение в обоих режимах, чтобы убедиться что оформление не будет раздражать пользователя.
Местоположение
Чтобы иметь возможность рассчитать корректное переключение между днем или ночью, нам нужно знать ваше местоположение. Если ваше приложение уже имеет разрешения для определения местоположения, AppCompat будет попытаться захватить последнее известное местоположение от LocationManager и использовать их для вычисления времени восхода и захода солнца. Однако он не будет просить разрешения от вашего имени.
Если вы не установили эти разрешения (или просто нет последнего известного местоположения), в настоящее время используются некоторые фиксированные значения. Например это 6 утра (начало дня) и 10 вечера (начало ночи), но это может измениться в будущем, по словам разработчиков.
Если вы ориентируетесь на Android SDK версии 23, вы будете использовать разрешения времени выполнения. Можно реализовать в вашем приложении настройку, позволяя пользователю выбрать включение функциональности DayNight. Это хороший способ, чтобы запрашивать разрешения определения местоположения, если требуется высокая точность определения времени восхода/заката.
Почему просто не сделать AUTO по умолчанию?
Существует несколько причин, которые станут понятны после просмотра примерного сценария для использования этого:
- Изменить тему продления от Theme.AppCompat.DayNight.
- Добавить установку в вашем приложении, предоставив пользователю выбор. На этом этапе сохранить настройки (вероятно, в SharedPreference). Вызов setDefaultNightMode () с выбранным значением.
Тогда в следующий раз, когда ваше приложение запускается, прочитать сохраненное значение и вызвать setDefaultNightMode () снова с выбранным значением.
Таким образом, основная причина заключается в том , не желательно, чтобы ваш пользователь обновил приложение, и вдруг обнаружил , что оно меняет цвет случайным образом (для него) на основе времени суток. Кроме того, по умолчанию MODE_NIGHT_FOLLOW_SYSTEM, поэтому если разработчики добавят user-visible setting на платформу в будущем, AppCompat будет автоматически использовать его.
Можно ли использовать свои собственные ресурсы для темы день / ночь?
Да, можно. AppCompat в простых терминах просто позволяет использовать отдельные ресурсы ночного режима в любое время. Они на самом деле были доступны в платформе, начиная с API 8, но использовались только в очень конкретном сценарии: в режиме автомобиля и стыковки.
Так что под капотом Theme.AppCompat.DayNight просто реализован в таком виде:
res/values/themes.xml
res/values-night/themes.xml
Это означает, что вы можете также добавить ваши ресурсы для дневного и ночного времени. Просто используйте –night квалификатор для вашей папки ресурсов: drawable-night, values-night, и т.д.
Вам також може сподобатися
Дизайн android приложений 0 8 083
Перевод статьи на Медиуме о построении пользовательского интерфейса с помощью компонента разметки ConstraintLayout. Создайте
Дизайн android приложений 2 1 892
[:ru]Сегодня в очередном выпуске Дизайна андроид приложений обзор интересной библиотеки от команды DevLight. Кто
Дизайн android приложений 1 1 833
В этом учебнике мы создадим простое Android приложение для отображения списка данных с использованием ViewStub
Дизайн android приложений 0 5 959
В этом уроке вы узнаете, как создавать интерактивные значки для пунктов меню, отображаемых в тулбаре
Дизайн android приложений 0 10 956
Android Учебник: создание RecyclerView, CardView и меню опций для элемента RecyclerView В этом уроке
Дизайн android приложений 0 24 913
Рассмотрим новый компонент библиотеки поддержки материального дизайна BottomNavigationView. Это нижняя панель навигации, позволяющая переключаться между
Коментарі: 2
У меня в приложении по собственному стилю цвет текста (заголовка) в action bar был белый, но после установки на DayNight стал чёрным, и никакие стили и темы никак не влияют, чтобы обратно сделать белым, как это решить?
Додати коментар Скасувати відповідь
Щоб відправити коментар вам необхідно авторизуватись.
Как программно отключить темную тему в приложении Android?
На новых Android, доступна функция включения темной темы для всего устройства и соответственно адаптация приложений под темные цвета. Но эта адаптация изменяет мое приложение на некоторых устройствах (самое главное что это происходит не всех устройствах), делая темный текст на темном фоне, соответственно делая его нечитабельным, тоже самое происходит с другими элементами, что выглядит ужасно и непрактично. Можно ли как то отключить принудительную адаптацию под темную тему программно в приложении?
Отслеживать
81.1k 7 7 золотых знаков 72 72 серебряных знака 153 153 бронзовых знака
задан 7 дек 2020 в 15:50
Артик Казорио Артик Казорио
61 2 2 серебряных знака 6 6 бронзовых знаков
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
//системная тема AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); //light тема AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); //ночная тема AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
Отслеживать
ответ дан 8 дек 2020 в 7:46
81.1k 7 7 золотых знаков 72 72 серебряных знака 153 153 бронзовых знака
- java
- android
- android-themes
-
Важное на Мете
Похожие
Подписаться на ленту
Лента вопроса
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.10.27.43697
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
Как убрать темную тему в андроид студио
Кроме применение отдельных стилей к отдельным элементам, мы можем задавать стили для всего приложения или activity в виде тем. Тема предтавляет коллекцию атрибутов, которые применяются в целом ко всему приложению, классу activity или иерархии виджетов.
Мы можем сами создать тему. Однако Android уже предоставляет несколько предустановленных тем для стилизации приложения, например, Theme.AppCompat.Light.DarkActionBar и ряд других.
По умолчанию приложение уже применяет темы. Так, откроем файл AndroidManifest.xml . В нем мы можем увидеть следующее определение элемента application, представляющего приложение:
Задание темы происходит с помощью атрибута android:theme . В данном случае используется ресурс, который называется в моем случае Theme.ViewApp . По умолчанию файлы тем определены в папке res/values . В частности, здесь можно найти условный каталог themes , в котором по умолчанию есть два элемента: themes.xml :
Один файл представляет светлую тему, а другой — темную. Например, откроем файл themes.xml со светлой темой:
- @color/purple_500
- @color/purple_700
- @color/white
- @color/teal_200
- @color/teal_700
- @color/black
- ?attr/colorPrimaryVariant
Здесь мы можем увидеть, что тема определяется как и стиль с помощью элемента style .. Атрибут parent указывает на родительскую тему, от которой текущая тема берет все стилевые характеристики. То есть тема «Theme.ViewApp» использует другую тему — «Theme.MaterialComponents.DayNight.DarkActionBar». И кроме того, определяет ряд своих собственных стилей.
Также можно заметить, что здесь определяются не только характеристики для атрибутов, но и семантические имена, например, colorPrimary , которому сопоставлен ресурс «@color/purple_500».
При необходимости мы можем изменить эти характеристики или дополнить тему новыми стилевыми характеристиками. Например, изменим цвет свойства colorPrimary , которое применяется в том числе в качестве фонового цвета заголовка и кнопки:
- #1565C0
И соответственно изменится цвет по умолчанию для фона заголовка и кнопки:
Создание собственной темы
Вместо использования встроенных тем мы можем создать свою. Для этого добавим в папку res/values новый файл mythemes.xml и определим в нем следующее содержимое:
Итак, мы создали стиль «MyTheme», который унаследован от стиля Theme.AppCompat.Light . В этом стиле мы переопределили два свойства: высоту шрифта (textSize) — 28sp, а также цвет текста (textColor) — #FF018786 .
Теперь определим этот стиль в качестве темы приложения в файле AndroidManifest.xml :
Пусть у нас будет следующая разметка в activity_main.xml
Как видно, для элементов TextView не устанавливается атрибут textSize и textColor , однако поскольку они определены в теме, которая применяется глобально к нашему приложению, то элементы TextView будут подхватывать эти стилевые характеристики:
Применение темы к activity
Выше темы применялись глобально ко всему приложению. Но также можно применить их к отдельному классу Activity. Для этого надо подкоррективать файл манифеста AndroidManifest. Например:
Атрибут android:theme элемента указывает на применяемую к MainActivity тему. То есть глобально к приложению применяется тема «Theme.ViewApp», а к MainActivity — «MyTheme».
Применение темы к иерархии виджетов
Также можно применить тему к иерархии виджетов, установив атрибут android:theme у элемента, к которому (включая его вложенные элементы) мы хотим применить тему. Например, примение темы к ConstraintLayout и ее элементам:
Android-разработчикам: как сократить время реализации тёмной темы с пары месяцев до недели
Привет, меня зовут Влад Шипугин, я Android-разработчик в Redmadrobot. В этой статье я хочу поделится опытом реализации тёмной темы, создания удобного UI Kit, как для разработки, так и для дизайнеров. Я расскажу про использование Material Components и работу с Vector Drawable. Также вы узнаете, как быстро поддержать режим edge-to-edge с использованием Window Insets и познакомитесь с моей библиотекой — edge-to-edge-decorator.
Где-то полгода назад мы начали разработку тёмной темы для приложения «Ростелеком Ключ». Наши дизайнеры уже писали про ценность тёмной темы, как её спроектировать и передать в разработку. В этой статье я продолжу рассказ от лица разработчика, расскажу с какими проблемами мы столкнулись в процессе реализации тёмной темы, почему это заняло у нас 3 месяца и как реализовать тёмную тему всего за одну неделю.
Этой осенью Android 10 исполнился год, и именно такое время требуется, чтобы изучить все новинки, реализовать их и протестировать, поэтому в статье будет живой опыт из реальных проектов.
Не важно, давно ли вы начали разработку под Android или только делаете ваше первое приложение, думаю, вам будет интересно почитать эту статью, потому что в ней я поделился всем накопленным опытом и самым актуальным набором материалов по дизайну Android-приложений.
Как сделать удобный UI Kit
Изначально у нас был простой план: дизайнеры делают тёмную тему, а мы просто добавляем файл value-night/color.xml и всё. Но позже мы столкнулись с проблемами. И теперь, когда приложение давно опубликовано в сторе, я могу рассказать о решении этих проблем, чтобы вы никогда не наступали на наши грабли.
Проблема №1: сложность при выборе названий для палитры цветов
Первый вариант именования цветов — использование названия цвета как есть: “realblue”, “darkgrey” и так далее.
Думаю, каждый сталкивается с проблемой, когда при использовании такого подхода, в будущем, при редизайне, получается, что “realblue” меняет свой hex на hex красного цвета или жёлтого. Это можно исправить, если переименовать цвет во всем приложении, но тогда возрастает вероятность ошибиться, а отлавливать такие ошибки крайне тяжело.
Второй вариант — это абстрактные названия, мы решили использовать C1, C2 (С — от слова Color) и так далее. Эта абстракция позволяет отвязать значение цвета от его названия. Сегодня это может быть красный, а потом желтый — это не важно, и вам не нужно рефакторить всё приложение.
Но тут появляется другая проблема — ты не можешь держать таблицу соответствий цветов в голове. Приходится всё время её открывать: при обсуждении с дизайнером или при разработке — всегда нужно проверять конкретный цвет приложения.
Редко, когда удается запомнить конкретную цифру цвета. «Какой тут должен быть цвет: С4 или С7?» — станет самым частым вопросом на обсуждениях дизайна. Мы долгое время пытались найти баланс между понятным названием цвета, таким как “realblue” и максимально абстрактным цветом для простоты рефакторинга и редизайна: С1, C2, C3 и так далее.
Есть и альтернативный, третий вариант — когда названия цветов зависят от компонента, — например, гайдлайны Material Design или Apple HIG.
Этот вариант казался самым логичным, но в нём было больше всего проблем:
- Цвета даёт дизайнер. И начинающим дизайнерам сложно придумывать названия цветов, а подключать всегда арт-директора для такой мелочи невыгодно.
- Некоторые цвета могут содержать одинаковый hex. В целом, в этом нет проблемы, но как оказалось, в палитре цветов Zeplin не может содержаться два цвета с одинаковым hex, но разными названиями.
- Сложно объединить рекомендации Material Design и Apple HIG в одну палитру, да и стандартных цветов может быть недостаточно для приложения.
Много встреч прошло за обсуждением названий цветов. Они должны были быть удобными для всех: Android- и iOS-разработчиков, и дизайнеров. Через некоторое время мы остановились на третьем варианте, но сформировали чёткие правила по наименованию цветов, чтобы не тратить много времени на придумывание названия. Также мы договорились добавлять цвета с одинаковым hex отличающимся на единицу.
Название цвета
iOS
Android
Вот пример готовой палитры из Zeplin:
Позже мы отказались от Zeplin и перешли на Figma. Вот такая палитра в Figma у нас получилась. А подробнее про переход с Zeplin на Figma уже писал наш iOS разработчик Даниил Субботин @subdan в статье про утилиту экспорта UI Kit из Figma — figma-export.
Проблема №2: непонятные стили шрифтов
Особых проблем в работе со штифтовыми стилями у нас не было. Дизайнеры обычно заносят все шрифтовые стили приложения в UI Kit, но их наличие не гарантирует, что в макетах будут использоваться только они. Периодически то там, то тут, Zeplin не определял указанный шрифт и приходилось отвлекать дизайнера, чтобы узнать какой именно шрифт необходимо использовать.
Сейчас в макетах всегда отображаются правильные стили шрифтов. Дизайнеры добились этого за счёт перехода со Sketch + Zeplin на Figma — она лучше распознает шрифтовые стили.
Да, иногда ошибки встречаются, но это происходит крайне редко, потому что все шрифты прописаны в мастер-компонентах и UI Kit. Это уже ответственность дизайнеров. Если я находил ошибку, то оставлял комментарий и дизайнер всё исправлял.
Пример наших шрифтовых стилей можно посмотреть тут.
Проблема №3: дублирование иконок и трудности с их именованием
Пока дизайнеры переделывали палитру цветов, я столкнулся с новой проблемой — иконки должны быть разных цветов в зависимости от выбранной пользователем темы. Самый простой вариант — это добавить альтернативный набор иконок в директорию drawable-night. Но за простоту нужно платить:
- Количество иконок увеличивается в два раза, а значит, приложение весит больше. И App Bundle не поможет, потому что тема меняется динамически и все иконки должны находиться в итоговом APK.
- Названия иконок разных цветов должны всегда совпадать. Если дизайнер опечатался или изменил название, то вы добавите новую иконку рядом, а не замените старую. В таком случае искать ошибку придется вручную, а это сложно и долго.
- Всегда нужно помнить про альтернативные цвета иконок. Если добавляешь новую иконку, то нужно всегда добавлять её для темной темы, и на раннем этапе мы об этом часто забывали
Эти проблемы довольно существенны и такой вариант меня сразу не устроил. Позже я наткнулся на рекомендации Google и Apple. Они советуют использовать все иконки одного цвета: черного или белого, и перекрашивать их в нужный цвет в рантайме.
Мы сначала использовали иконки белого цвета, но потом столкнулись с проблемами их видимости. В Figma мы добавляли их на черный фон, а вот в операционной системе черный фон есть не везде, и иконок бывает просто не видно. Поэтому мы перешли на использование черных иконок.
Ещё одна проблема — названия иконок придумывают дизайнеры и не всегда они совпадают с ходом мысли разработчиков: дизайнеры называют иконку исходя из визуальной составляющей, а разработчики исходя из предметной области. Поэтому разработчики переименовывают иконки у себя в проекте.
Ещё бывают моменты, когда дизайнер сам решает поменять название иконки и забывает сообщить об этом разработчикам. Все эти изменения приводят к тому, что при обновлении или добавлении иконок может появиться дублер иконки, но с другим названием. В таком случае много времени придется потратить на поиски одинаковых иконок.
Чтобы такого не было, следует сразу договориться о правильном именовании иконок. Мы остановились на таком варианте:
Название иконки
iOS
Android
Пример с нашим набором иконок можно посмотреть здесь.
Проблема №4: организация иллюстраций
Перекрашивать иконки удобно, но если картинка содержит больше одного цвета, то такой вариант не подходит. В данном случае остается добавить иллюстрацию альтернативного цвета в директорию drawable-night и попросить дизайнера подготавливать иллюстрации в альтернативной теме.
У нас были случаи, когда иллюстраций не хватало или они рендерились криво. Чтобы избежать ошибок, мы собрали все иллюстрации в одном удобном для QA месте, и включили их в ручное регрессионное тестирование. Позже этот процесс можно будет автоматизировать с помощью скриншотного тестирования.
Вот пример того, что у нас получилось.
Проблема №5: отсутствие базовых компонентов или неправильное их использование
При проектировании дизайна приложений дизайнер сначала делает экраны, а потом переносит основные компоненты в UI Kit. При этом не каждый дизайнер уделяет достаточно времени технической составляющей при описании этих компонентов. В UI Kit могут отсутствовать некоторые состояния, которые разработчик может встретить в процессе реализации экранов. Такой UI Kit создается неполным, и если дизайнер сразу не сделает хороший UI Kit, то после создания тот и вовсе перестает поддерживаться.
При данном подходе UI Kit не является единым «источником правды». Можно сказать, что в данном случае — это UI Kit для «галочки», и так делать нельзя.
Но как только дизайн-макеты начинают передавать в разработку, то единственным «источником правды» должен стать UI Kit и мастер-компоненты в нем. Иначе UI Kit будет вам мешать и замедлять работу, а не ускорять её.
Ценность UI Kit в том, что вы реализуете все базовые компоненты, а потом из этих компонентов собираете экраны. Для этого можно использовать мастер-компоненты в Figma, и styles или CustomViews в Android. В таком случае UI Kit экономит вам много времени.
Вот пример с описанием кнопок приложения, а полный UI Kit можно посмотреть тут.
Как правильно реализовать UI Kit
После создания дизайнерами UI Kit можно подключаться и разработчикам. Мой план по реализации UI Kit с учетом темной темы был такой:
- Привести цвета, тему приложения и стили компонентов в порядок.
- сделать палитру цветов (color.xml);
- описать тему приложения;
- описать стили компонентов, которые отличаются от базовой темы.
- Заменить все иконки на черный и окрашивать их в нужный цвет в момент отрисовки.
- Добавить альтернативные цвета values-night/color.xml.
- Добавить выбор темы в настройках приложения.
Я уже писал выше про утилиту Figma-export и статью, в которой раскрыты подробности её реализации. Эта утилита помогает избежать проблем с экспортом цветов, иконок и иллюстраций из Figma. С её помощью экспорт компонентов происходит автоматически, что позволяет полностью исключить человеческий фактор из этого процесса.
Реализуем палитру цветов
С помощью Figma-export создание палитры цветов происходит одной командой:
./figma-export colors -i figma-export.yaml
После этого в вашем приложении добавится или изменится файл color.xml.
Реализуем тему приложения
Для реализации темы в Android-приложении следует использовать библиотеку “material-components”. Именно так я и поступил: создал палитру цветов в color.xml и начал делать тему приложения. Но после этого я столкнулся с проблемой — toolbar , cardview и ещё пару компонентов имели не тот цвет.
Прописывать все цвета по месту использования компонента мне показалось плохим решением — слишком много дублирования.
На тот момент, в библиотеке “material-components”, ещё не было документации по темам и стилям. Как и многие Android-разработчики, я не знал, как правильно описывать тему приложения. Разработчики из Google даже шутили про это на Android Dev Summit 2019.
Моя тема приложения была описана неправильно и материальные компоненты, такие как кнопки, иконки и текстовые поля, выглядели не так, как я планировал. Например, цвет toolbar использовал цвет primary для светлой темы и, почему-то, переключался на surface в темной.
Позже оказалось, что в теме приложения, по умолчанию, цвет toolbar принимает значение, равное атрибуту ?attr/colorPrimarySurface . Тогда, чтобы понять почему так происходит, мне пришлось ковыряться в исходниках материальных компонентов. Мне удалось понять, какой смысл вкладывали авторы в описание темы приложения, и потом статья про темы и стили в Android-приложениях подтвердила мои догадки.
Сейчас вы можете узнать интересные детали в новой статье по материальным компонентам от Google. Картинка из этой статьи наглядно показывает, как работает тема с атрибутом colorPrimarySurface .
При реализации темы приложения важно понять концепции цветов и атрибутов, а также различия темы и стилей. Сейчас проблем с документацией больше нет. Я не стану описывать эти вещи в данной статье, чтобы не повторяться.
Google представили подробную серию статей по материальным компонентам. Поэтому, я рекомендую их к прочтению. Они помогут вам полностью разобраться в создании темы для Android-приложений:
Доп. информацию по материальным компонентам можно найти тут и в конце статьи:
- Android Dev Summit ’18: «Best Practices for Themes and Styles»
- Android Dev Summit ’19: «Developing themes with style»
- Google Design Tutorials: Migrating your app to Material Components for Android
- Статья от Redmadrobot: «Темы и стили в Android-приложениях»
- Серия статей на Medium Android developers
- What’s your text’s appearance?
- Android styling: themes vs styles
- Android styling: common theme attributes
- Android Styling: prefer theme attributes
- Android Styling: themes overlay
- Migrating to Material Components for Android
- We Recommend Material Design Components
- Material Theming with MDC: Color
- Material Theming with MDC: Shape
- Material Theming with MDC: Type
- Material Motion with MDC
- Dark Theme with MDC
Также, при реализации темы приложения, следует подключить дизайнера, чтобы он помог создать описание темы приложения и сконвертировать удобную палитру цветов для дизайна, в тему материальных компонентов. Здесь на помощь приходит раздел материальных компонентов, MaterialThemeBuilder и примеры от Google:
- Репозиторий с примерами использования Material Components от Google
- Reply
- Owl
В итоге, я остановился на таком варианте описания темы и стилей.
Структура организации ресурсов:
- colors.xml — цвета приложения;
- type.xml — шрифты приложения;
- shape.xml — формы приложения;
- themes.xml — темы приложения;
- styles_button.xml — кнопки приложения;
- styles_text_input.xml — текстовые поля;
- styles_list_item.xml — элементы списков;
- styles.xml — прочие стили виджетов.
Реализуем стили шрифтов
В теме приложения с material-components стандартным решением для реализации шрифтов является textAppearance. В нашем приложении используются в два раза меньше шрифтов и всего три цвета для текста. А ещё textAppearance можно описывать не все атрибуты — например, там нет свойства android:lineSpacingMultiplier . Поэтому, я решил не использовать textAppearance , а использовал просто стили, которые прописывались каждому текстовому полю.
Например, мы нигде не использовали стиль Header2 и вместо него применяли унаследованный от него стиль с указанием цвета: Header2.Primary или Header2.Secondary . Такой вариант позволял сразу определить и цвет текстового поля и его шрифт.
Иногда приходилось делать исключения и использовать просто стиль, как Header2 , с указанием цвета прямо в верстке экрана, например, для отображения ошибок, но таких мест всего 2–3 в приложении.
Стиль шрифта содержит следующие атрибуты:
- textSize — размер;
- lineHeight — межстрочный интервал, когда в текстовом поле две строки;
- android:minHeight и android:gravity — нужны, чтобы указать межстрочный интервал, когда в текстовом поле всего одна строка (да, lineHeight в таком случае игнорируется и приходится выкручиваться костылями :))
- android:fontFamily — начертание шрифта. Вот один из примерв описания шрифта:
Переиспользуем иконки при помощи окрашивания
Как я писал ранее, все иконки в приложении следует делать одного цвета, и потом их раскрашивать. Чтобы добавить все иконки из Figmа, вновь воспользуемся утилитой Figma-export:
./figma-export icons -i figma-export.yaml
После этого вы увидите, какие иконки удалятся, а какие добавятся или изменятся. Останется только покрасить их в нужный цвет.
В Android давно добавили поддержку перекрашивания иконок, это различные tint, но до сих пор это работает плохо и не на всех версиях Android, поэтому я написал extension для работы с drawable через drawable compat, и придерживался следующего алгоритма:
- Если можешь сделать tint в верстке — делай tint .
- Если это кнопка или компонент с несколькими состояниями, то тут поможет selector .
- Если требуется программная смена цвета, то необходимо использовать DrawableCompat для корректной установки цвета, и обязательно нужно сделать mutate , иначе иконка закешируется, и поменяет цвет во всем приложении. Для этого я написал следующие extension-функции:
fun Drawable.withTint(context: Context, @ColorRes color: Int): Drawable < return DrawableCompat.wrap(this).mutate().apply < DrawableCompat.setTint(this, ContextCompat.getColor(context, color)) >> fun Int.toDrawableWithTint(context: Context, @ColorRes color: Int): Drawable
Добавляем иллюстрации
С добавлением иллюстраций тоже всё просто. В Figma-export есть нужная команда для их добавления в проект:
./figma-export images -i figma-export.yaml
Вызываем команду, и она добавляет иллюстрации в values и, если, вы поддерживаете тёмную тему, то и в values-night.
Реализуем стили компонентов
Я уже выше делился материалами по реализации темы приложения и стилей компонентов. Если вы их почитаете, то вопросов у вас возникнуть не должно.
Пример того, как я организовал стили компонентов кнопок в styles-button.xml
Также стоит учитывать, что в верстке вы можете использовать просто Button или AppCompatButton , потому что есть такой компонент, как MaterialComponentsViewInflater, который автоматически будет переводить их в MaterialButton , если ваша тема наследуется от «material-components».
Вот кусочек кода из него:
@NonNull @Override protected AppCompatButton createButton( @NonNull Context context, @NonNull AttributeSet attrs ) < if (shouldInflateAppCompatButton(context, attrs)) < return new AppCompatButton(context, attrs); >return new MaterialButton(context, attrs); >
Как поддержать режим edge-to-edge
На этапе проектирования палитры цветов, нам очень сильно мешали цвета statusBar , да и в целом, окрашивание statusBar всегда вызывало проблемы на разных версиях Android. Раньше его цвет был равен colorPrimaryDark , а теперь Google отказались от этого варианта и рекомендуют использовать режим edge-to-edge. Кроме этого, мой OnePlus получил обновление до Android 10, поэтому я решил попробовать добавить поддержку режима edge-to-edge.
Режим edge-to-edge — это новая концепция из материального дизайна, которая заключается в том, что вы отрисовываете контент под системными компонентами: statusBar и navigationBar — и телефон становится визуально более безрамочным.
Отрисовка контента под statusBar позволяет добавить в ваше приложение поддержку челок и вырезов под камеру, а под navigationBar — визуально улучшает работу с жестами из новой жестовой навигации в системе Android.
Для того, чтобы добавить поддержку режива edge-to-edge в ваше приложение нужно:
- Добавить поддержку системных отступов ( insets ).
- Активировать режим edge-to-edge для statusBar и navigationBar . По факту вам нужно сделать их прозрачными.
Добавляем поддержку Window Insets
Если простыми словами, то при работе с Window Insets, вы получаете размер системных компонентов и вставляете их как padding в верстку для ваших компонентов экрана AppBar или RootView . Insets поддерживается всеми версиями Android, что позволяет реализовать концепцию edge-to-edge для всех пользователей. Подробности можно почитать или посмотреть в докладе Константина Цховребова с AppsConf.
Сначала я использовал его решение, а потом, когда вышла новая версия библиотеки Insetter от Криса Бэйнса, перешел на неё. Вам предлагаю сразу использовать Insetter.
Также я собрал пару полезных статей, на случай, если вы захотите разобраться в этом детальнее:
Материалы по режиму edge-to-edge лежат тут и в конце статьи:
- Пример реализации edge-to-edge из каталога компонентов
- Medium Android developers: «WindowInsets — Listeners to layouts»
- Серия статей на Medium Android developers про Gesture Navigation
- Medium Android developers: Gesture Navigation: Going edge-to-edge (I) и перевод
- Gesture Navigation: Handling visual overlaps (II)
- Gesture Navigation: Handling gesture conflicts (III)
- Gesture Navigation: Immersive Modes (IV)
Окрашиваем statusBar и navigationBar
Эффект безрамочности в режиме edge-to-edge достигается за счет того, что вы отрисовываете контент под statusBar и navigationBar и делаете их прозрачными. При этом, нужно сохранять контрастность иконок в этих компонентах.
Тут существует одна проблема, которая находится глубоко в системе и исправить её после релиза OS уже нельзя. Это изменение цвета иконок в системных компонентах ( statusBar и navigationBar ) со светлого на темный. Поэтому, нужно учитывать следующие правила, в зависимости от версии Android:
- до 6.0 версии Android иконки statusBar и navigationBar всегда светлые и перекрасить их в темный цвет нельзя. Флаг View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR доступен с 23 API. Если у вас контент всегда темного цвета, то проблем не будет. Но чтобы сохранить контрастность иконок на фоне контента, следует добавлять на системные компоненты наложение фона, например, черного фона с 50% прозрачности;
- с версии Android 6.0 можно задать, какими будут иконки в statusBar : белыми или черными. Однако navigationBar будет вести себя как в предыдущих версиях, поэтому наложение можно убрать только для statusBar . Флаг View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR доступен с 26 API;
- с версии Android 8.0 можно выбрать белый или черный цвет иконок для обоих компонентов. Поэтому наложения можно убрать полностью.
Я нашел интересный пример WindowPreferencesManager, который реализовывал эту логику в приложении-каталоге материальных компонентов. Но там было много лишнего и разбираться в этом, думаю, захочет не каждый, поэтому я сделал мини утилиту edge-to-edge-decorator. Она хорошо кастомизируется под ваши нужды и реализует логику окрашивания statusBar и navigationBar за вас. Подробнее про реализацию можно почитать в документации.
Пример работы библиотеки:
Добавляем тёмную тему приложения
Теперь, после того, как у вас готов UI Kit приложения и вы изучили и поддержали новый подход с использованием Material Components, можно вернуться к реализации темной темы в приложении.
Я рекомендую следовать согласно следующему алгоритму:
- Изучаем гайды и статьи по проектированию темной темы (ссылки лежат в конце статьи).
- Дизайнер готовит первый прототип и цветовую схему темной темы приложения.
- Создание полноценной темной палитры цветов.
- Добавляем альтернативную палитру цветов в приложениях и проверяем или кастомизируем тему, и стили компонентов для темной темы.
Если основная тема вашего приложения описана правильно, то добавление темной темы не создаст проблем: нужно просто добавить color.xml в values-night, как мы и планировали в самом начале (как же мы тогда ошибались :))
Сама активация темной темы хорошо описана в документации. Коротко, что нужно сделать:
1) Поменять базовую тему приложения на DayNight .
2) Установить нужный режим отображения через метод AppCompatDelegate.setDefaultNightMode.
В системе доступно 4 варианта темы:
- всегда светлая: AppCompatDelegate.MODE_NIGHT_NO ;
- всегда тёмная: AppCompatDelegate.MODE_NIGHT_YES ;
- выбирается в зависимости от режима энергосбережения (Android 9 и ниже): AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY ;
- переключается в зависимости от настроек системы (Android 10 и выше): AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM ;
Добавляем выбор темы приложения
Светлая тема
Темная тема
После того, как вы реализовали тёмную тему, вишенкой на торте станет выбор темы в настройках приложения. Почему это важно? Потому что ресурсы values-night были добавлены ещё в API level 8, но включение темной темы на уровне системы реализовали только в Android 10. Чтобы темная тема работала у всех пользователей, необходимо добавить возможность её выбора в приложении.
Для удобного API я написал вот такой класс:
enum class NightModeType( val customOrdinal: Int, @NightMode val value: Int, @StringRes val title: Int ) < MODE_NIGHT_NO( 0, AppCompatDelegate.MODE_NIGHT_NO, R.string.mode_night_no ), MODE_NIGHT_YES( 1, AppCompatDelegate.MODE_NIGHT_YES, R.string.mode_night_yes ), MODE_NIGHT_FOLLOW_SYSTEM( 2, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, R.string.mode_night_follow_system ), MODE_NIGHT_AUTO_BATTERY( 2, AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY, R.string.mode_night_auto_battery ); companion object < fun fromValue(@NightMode value: Int) = values().firstOrNull < it.value == value >?: getDefaultMode() fun fromCustomOrdinal(ordinal: Int): NightModeType < return if (ordinal == 2) < getDefaultMode() >else < values().firstOrNull < it.customOrdinal == ordinal >?: getDefaultMode() > > fun getDefaultMode(): NightModeType < return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) < MODE_NIGHT_FOLLOW_SYSTEM >else < MODE_NIGHT_AUTO_BATTERY >> > >
А выбор темы можно реализовать таким образом:
private fun createNightModeChooserDialog(command: ShowNightModeChooserDialog): AlertDialog < return AlertDialog .Builder(ContextThemeWrapper(requireContext(), R.style.ThemeOverlay_AppTheme_AlertDialog)) .apply < setTitle(getString(R.string.item_dark_theme_text_view_title_text)) val nightModes = arrayOf( getString(NightModeType.MODE_NIGHT_NO.title), getString(NightModeType.MODE_NIGHT_YES.title), getString(NightModeType.getDefaultMode().title) ) val selectedMode = command.selectedMode.customOrdinal setSingleChoiceItems(nightModes, selectedMode) < dialog, which ->val nightMode = NightModeType.fromCustomOrdinal(which) persistentStorage.saveNightMode(nightMode.value) AppCompatDelegate.setDefaultNightMode(nightMode.value) dialog.dismiss() > setNegativeButton( getString(R.string.fragment_dialog_night_mode_chooser_button_cancel_text), null ) > .create() >
И тут тоже есть проблема: выбранная пользователем тема нигде не запоминается, поэтому её необходимо сохранять. Сделать это можно вот такой проверкой в методе onCreate у вашего activity:
override fun onCreate(savedInstanceState: Bundle?) < checkNightMode() setTheme(R.style.AppTheme) super.onCreate(savedInstanceState) >private fun checkNightMode()
Заключение
Вместо пары недель на реализацию тёмной темы у нас ушло три месяца. Но мы не просто сделали тёмную тему на проекте «Ростелеком Ключ», но и подняли дизайн приложения на новый уровень:
- сформировали четкий и полный UI kit в Figma;
- автоматизировали экспорт UI kit в Figma и опубликовали утилиту figma-export;
- правильно реализовали все базовые компоненты в Android приложении;
- поддержали новый режим edge-to-edge, и опубликовали библиотеку edge-to-edge-decorator, которая поможет быстро добавить режим edge-to-edge на других проектах.
Так сказать, мы вышли из зоны комфорта, ради темной темы, и поправили кучу моментов в основном процессе работы 🙂
Материалы для глубокого изучения
Все ссылки я оформил в виде gist, поэтому можете сохранить его себе поставив звездочку
- android
- material design
- темная тема
- figma
- zeplin
- material theming
- android development
- ui kit
- edge to edge
- разработка мобильных приложений