Быстрый способ встроить темную тему в приложение

В последнее время одним из частых запросов, от пользователей к разработчикам приложений, стал вопрос о темной теме.
Причины просты: темная тема легче для глаз при использовании приложения ночью, это экономит заряд батареи, особенно если устройство использует OLED-экран, и самый важный из них : выглядит круто.
Обычно ответ разработчика на этот вопрос представляет собой некую вариацию следующего ответа:
Не сейчас. Было бы неплохо, но… может быть, позже
Понятно, что разработчики хотят минимизировать затраты на производство своего приложения, сосредоточившись на том, что действительно важно с точки зрения функциональности и стабильности. Но, неужели нужно чем-то жертвовать, чтобы получить темный режим? Давайте выясним.
Как и с чего начать?
Если вы хотите без особых усилий интегрировать темный режим в свое новое приложение, это не составит большого труда. Также можно добавить его в уже существующее приложение Flutter, но это потребует немного больше времени и некоторого рефакторинга.
Вот что нам понадобится:
- Адаптивные цвета
- Адаптивный стиль текста
- Способ переключения между темами
Адаптивные цвета
Лучше меньше, да лучше. Под этим я подразумеваю: постарайтесь свести количество цветов в вашем приложении к минимуму . Поверьте, вам не нужны все 50 оттенков серого.
Начнем с определения абстрактного класса для каждого цвета нашей темы:
abstract class LightThemeColors {
static Color get background => const Color(0xFFFFFFFF);
static Color get primaryContent => const Color(0xFF000000);
static Color get primaryAccent => const Color(0xFF99283F);
}
abstract class DarkThemeColors {
static Color get background => const Color(0xFF000000);
static Color get primaryContent => const Color(0xFFE1E1E1);
static Color get primaryAccent => const Color(0xFFC7482A);
}
Что касается цветов, которые следует использовать, обратите внимание на рекомендуемую цветовую палитру от Apple или рекомендации Google по материальному дизайну.
Теперь давайте определим класс, который будет хранить оба варианта цвета и переключаться между ними в зависимости от выбранной темы.
class ThemedColor {
final Color light;
final Color dark;
const ThemedColor({
@required this.light,
@required this.dark,
}) : assert(light != null),
assert(dark != null);
Color getColor(BuildContext context) {
switch (Theme.of(context).brightness) {
case Brightness.light:
return light;
case Brightness.dark:
return dark;
}
throw UnsupportedError('${Theme.of(context).brightness} is not supported');
}
}
На следующем этапе мы реализуем наш цветовой класс, который будем использовать во всем приложении:
abstract class AppColors {
static Color background(BuildContext context) => ThemedColor(
light: LightThemeColors.background,
dark: DarkThemeColors.background,
).getColor(context);
static Color primaryContent(BuildContext context) => ThemedColor(
light: LightThemeColors.primaryContent,
dark: DarkThemeColors.primaryContent,
).getColor(context);
static Color primaryAccent(BuildContext context) => ThemedColor(
light: LightThemeColors.primaryAccent,
dark: DarkThemeColors.primaryAccent,
).getColor(context);
}
Теперь можно использовать этот класс везде, где у нас есть доступ к текущему BuildContext следующим образом:
Widget _buildButton(BuildContext context) {
return MaterialButton(
onPressed: onPressed,
color: AppColors.primaryAccent(context),
child: _buildButtonContent(),
);
}
Адаптивные стили текста
С цветами разобрались, теперь сосредоточимся на создании различных стилей текста.
abstract class AppTextStyles {
static const fontFamily = 'ВСТАВЬТЕ НАЗВАНИЕ ВАШЕГО ШРИФТА';
// СТИЛЬ ЗАГОЛОВКОВ //
static TextStyle headline1(BuildContext context, {Color color}) {
return TextStyle(
color: color ?? AppColors.primaryContent(context),
fontSize: 34,
fontFamily: fontFamily,
fontWeight: FontWeight.bold,
);
}
// СТИЛЬ ТЕКСТА 1 //
static TextStyle body1(BuildContext context, {Color color}) {
return TextStyle(
color: color ?? AppColors.primaryContent(context),
fontSize: 18,
fontFamily: fontFamily,
fontWeight: FontWeight.bold,
);
}
// СТИЛЬ ТЕКСТА 2 //
static TextStyle subtitle1(BuildContext context, {Color color}) {
return TextStyle(
color: color ?? AppColors.primaryContent(context),
fontSize: 16,
fontFamily: fontFamily,
fontWeight: FontWeight.normal,
);
}
}
Как и в случае с цветовым классом, мы можем использовать эти стили везде, где есть BuildContext. Например так:
Widget _buildTitleText(BuildContext context) {
return Text(
title,
style: AppTextStyles.body1(
context,
color: AppColors.primaryAccent(context),
),
);
}
Начинаем волшебство
Наконец, когда у нас есть все необходимые строительные блоки, мы можем реализовать основную логику, необходимую для того, чтобы Flutter автоматически переключал тему в зависимости от того, включен ли темный режим в настройках телефона.
Начнем с определения карты, в которой будут храниться данные для каждого из двух режимов темы:
final Map<ThemeMode, ThemeData> appThemes = {
ThemeMode.light: ThemeData(
accentColor: LightThemeColors.primaryAccent,
scaffoldBackgroundColor: LightThemeColors.background,
brightness: Brightness.light,
),
ThemeMode.dark: ThemeData(
accentColor: DarkThemeColors.primaryAccent,
scaffoldBackgroundColor: DarkThemeColors.background,
brightness: Brightness.dark,
)
};
Последний момент - нужно указать нашему MaterialApp переключаться между заданными темами следующим образом:
Widget _buildMaterialApp() {
return MaterialApp(
title: MyApp.appName,
themeMode: ThemeMode.system,
theme: appThemes[ThemeMode.light],
darkTheme: appThemes[ThemeMode.dark],
routes: navigationRoutes,
home: _buildHome(),
);
}
Также можно вручную указать свойство themeMode в MaterialApp, которое позволит нам предоставить пользователю полный контроль над отображаемой темой. Пользователь сам решит, какую тему он хочет использовать, выбрав соответствующий вариант в настройках приложения.
На этом все! И, как вы только что убедились, ничего сложного во внедрении темной темы нет.