Введение в Material You
Самое большое изменение, которое мы увидели в Android 12, — это Material You, который является последней версией языка дизайна Material от Google.
Google описывает Material You следующим образом
«стремится создать дизайн, который будет индивидуальным для каждого стиля, доступным для каждой потребности, живым и адаптивным для каждого экрана».
Во время разработки Android 12 Google создал новый движок тем под кодовым названием «monet», который генерирует богатую палитру пастельных цветов на основе обоев пользователя. Затем эти цвета применяются к различным частям системы, а их значения становятся доступны через API, к которому могут обращаться пользовательские приложения, что позволяет приложениям решать, хотят ли они также изменить цвет своего пользовательского интерфейса. Google активно внедряет Material You, и компания обновила большинство своих приложений, чтобы включить в них динамические цвета. Исходный код «monet» стал доступен для AOSP с выходом Android 12L. Ранее движок темы был эксклюзивом для Pixel.
Примечание: Monet не будет работать на устройствах с версией Android старше 12.
Кредиты изображений: material.io
Подробнее о новом Material You и движке динамических тем Google можно узнать здесь:
- Material You
- Динамический цвет
Необходимые условия
- Базовые знания Flutter
- Установленная версия Flutter 2.10.1 или более поздняя
- Физическое устройство под управлением Android 12 или новой версии ОС с поддержкой monet. (Эмулятор Android 12 пока не поддерживает monet).
Во время написания этого поста я использую устройство с установленной Android 12L Custom Rom, которая основана на AOSP.
Поскольку исходный код «monet» недавно был открыт, устройства, кроме Pixels, получат Android 12 с поддержкой дианамовых тем позднее в этом году.
Окончательный результат
В этом руководстве мы создадим простое приложение для удостоверения личности с помощью виджетов Text() и FloatingActionButton(), чтобы продемонстрировать реализацию monet.
Исходный код этого проекта доступен здесь: GitHub
Скриншоты
Если вы установили приложение на Android OS < Android 12, то приложение будет выглядеть следующим образом:
Давайте начнем!
- Создание приложения Flutter
Откройте Android Studio и создайте новый проект Flutter, как обычно, или, если вы предпочитаете командную строку, выполните приведенную ниже команду в терминале (для пользователей vscode).
flutter create id_card_monet
- Добавить зависимости
Откройте файл pubspec.yaml, который будет доступен в папке вашего проекта. В данном случае id_card_monet/pubspec.yaml
.
Мы будем использовать пакет «dynamic_color», который поддерживается на flutter 2.10.1 и более поздних версиях.
Пакет dynamic_color
опубликован material.io и доступен здесь.
Добавьте dynamic_color: ^1.1.2
в секцию зависимостей файла pubspec.yaml.
Ваша секция зависимостей должна выглядеть следующим образом:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
dynamic_color: ^1.1.2
После внесения изменений в файл pubspec.yaml, вам нужно нажать на всплывающее окно с надписью Pub get
в android studio или ввести flutter pub get
в терминале, если вы используете vscode. Это позволит загрузить все необходимые зависимости вашего проекта.
Установите ссылку на пакет dynamic_color flutter.
- Импортируйте необходимый пакет dart в main.dart
Перейдите в файл id_card_monet/lib/main.dart
в вашем проекте и добавьте этот import import 'package:dynamic_color/dynamic_color.dart';
, чтобы вы могли использовать виджеты из файла dynamic_color.dart.
- Удаление кода шаблона из файла main.dart
В файле main.dart вы увидите код шаблона, предоставленный Flutter, в котором мы будем писать наше дерево виджетов, поэтому лучше удалить его.
После удаления кода шаблона добавьте следующий блок кода:
void main() => runApp(const IdCard());
Если вы получаете ошибки типа IdCard() function is not defined, просто следуйте руководству и все будет хорошо!
- Создание StatefulWidget
В файле main.dart введите stful
и ваша IDE или редактор создаст StatefulWidget и введите имя StatefulWidget как ‘IdCard’.
Если приведенный выше метод не работает, просто скопируйте и вставьте приведенный ниже код:
class IdCard extends StatefulWidget {
const IdCard({Key? key}) : super(key: key);
@override
State<IdCard> createState() => _IdCardState();
}
class _IdCardState extends State<IdCard> {
@override
Widget build(BuildContext context) {
return Container();
}
}
Мы используем StatefulWidget в качестве состояния одного из наших виджетов.
- Используйте DynamicColorBuilder
В настоящее время ваш _IdCardState выглядит следующим образом
class _IdCardState extends State<IdCard> {
@override
Widget build(BuildContext context) {
return Container();
}
}
Замените приведенный выше фрагмент кода на
class _IdCardState extends State<IdCard> {
int age = 0;
@override
Widget build(BuildContext context) {
return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
return MaterialApp();
},
);
}
}
Мы возвращаем DynamicColorBuilder() вместо Container().
Он создает дочерний виджет этого виджета, предоставляя светлый и темный ColorScheme.
ColorSchemes будет равно null, если динамический цвет не поддерживается (т.е. на не-Android платформах и устройствах до Android S), или если цвета еще не получены.
- Настройка Scffold и AppBar с динамическими цветами
Замените _IdCardState на:
class _IdCardState extends State<IdCard> {
int age = 0;
@override
Widget build(BuildContext context) {
return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
return MaterialApp(
home: Scaffold(
backgroundColor: darkDynamic?.background ?? Colors.white, // *1
appBar: AppBar(
title: Text(
'ID Card',
style: TextStyle(
fontWeight: FontWeight.bold,
letterSpacing: 1.9,
color: darkDynamic?.onPrimaryContainer ?? Colors.white, // *2
),
),
centerTitle: true,
backgroundColor:
darkDynamic?.secondaryContainer.withAlpha(50) ?? Colors.blue, // *3
elevation: 6.0,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
age += 1;
});
},
child: const Icon(
Icons.add,
color: Colors.black,
),
backgroundColor: darkDynamic?.primary.withOpacity(1) ?? Colors.blue, // *4
),
body:
),
);
},
);
}
}
Как мы уже говорили, у нас есть палитры lightDynamic
и darkDynamic
. Мы можем использовать любую из них или переключаться между ними, когда ОС Android переключается со светлого режима на темный. В этом приложении мы используем палитру darkDynamic
.
Обе палитры предоставляют множество различных цветов, которые вы можете использовать в своем приложении.
Вот некоторые из них.:
- первичный
- третичный
- вторичныйКонтейнер
- вторичный
- onPrimary
- фон
- onPrimaryContainer и многое другое.
Вы можете получить к ним доступ через $colorPaletteName и оператор . (точка).
Например, darkDynamic?.primary
.
Вы можете узнать о конкретном использовании каждого цвета и настройке вашего приложения на material.io
Теперь перейдем к коду.
Обратитесь к комментариям к приведенному выше фрагменту, чтобы понять, о какой строке я говорю.
*1 backgroundColor: darkDynamic?.background ?? Colors.white,
Для backgroundColor нашего виджета Scaffold мы используем палитру darkDynamic и цвет фона, извлеченный из наших обоев. Если используется Android OS старше Android 12 (S) или по каким-то причинам не работает динамическая палитра, то для фона строительных лесов будет установлен «белый» цвет из библиотеки материалов.
*2 color: darkDynamic(?).onPrimaryContainer(?). Colors.white,
.
Устанавливает цвет заголовка AppBar на onPrimary Container, извлеченный из обоев, и если dynamicPalette возвращает null, то цвет заголовка AppBar устанавливается пустым.
*3 darkDynamic(.secondaryContainer.withAlpha(50) ?? Colors.blue,
.
Это похоже на вышеприведенную реализацию, но, если вы заметили, я добавил альфу к извлеченному цвету secondaryContainer. Вы можете настроить цвета по своему усмотрению.
Есть несколько методов, которые можно вызвать и настроить цвета:
- withOpacity(double opacity)
- withAlpha(int a)
- withGreen(int a)
- withRed(int a)
- withBlue(int a)
- harmonizeWith(Color color)
Метод harmonizeWith
может быть использован для смещения оттенка цвета в сторону переданного цвета
*4 backgroundColor: darkDynamic(.primary.withOpacity(1) ?? Colors.blue,
Здесь я установил непрозрачность на 1, что является максимальной непрозрачностью, которую вы можете передать, и это значение по умолчанию.
- Добавление других виджетов в наше приложение
Замените _IdCardState кодом, приведенным ниже, чтобы завершить пользовательский интерфейс нашего приложения.
class _IdCardState extends State<IdCard> {
int age = 0;
@override
Widget build(BuildContext context) {
return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
return MaterialApp(
home: Scaffold(
backgroundColor: darkDynamic?.background ?? Colors.white, // *1
appBar: AppBar(
title: Text(
'ID Card',
style: TextStyle(
fontWeight: FontWeight.bold,
letterSpacing: 1.9,
color: darkDynamic?.onPrimaryContainer ?? Colors.white, // *2
),
),
centerTitle: true,
backgroundColor:
darkDynamic?.secondaryContainer.withAlpha(50) ?? Colors.blue, // *3
elevation: 6.0,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
age += 1;
});
},
child: const Icon(
Icons.add,
color: Colors.black,
),
backgroundColor: darkDynamic?.primary.withOpacity(1) ?? Colors.blue, // *4
),
body: Padding(
padding: const EdgeInsets.fromLTRB(30, 40, 30, 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
'NAME',
style: TextStyle(
color:
darkDynamic?.primary.withOpacity(0.9) ?? Colors.black, // *5
letterSpacing: 2,
),
),
const SizedBox(height: 10),
Text(
'Vinay :)',
style: TextStyle(
color: darkDynamic?.secondary ?? Colors.blue, // *6
letterSpacing: 2,
fontSize: 28.0,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 30),
Text(
'CURRENT AGE',
style: TextStyle(
color:
darkDynamic?.primary.withOpacity(0.9) ?? Colors.black, // *7
letterSpacing: 2,
),
),
const SizedBox(height: 10),
Text(
'$age',
style: TextStyle(
color: darkDynamic?.secondary ?? Colors.blue, // *8
letterSpacing: 2,
fontSize: 28.0,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 30),
Text(
'CONTACT',
style: TextStyle(
color:
darkDynamic?.primary.withOpacity(0.9) ?? Colors.black, // *9
letterSpacing: 2,
),
),
const SizedBox(
height: 10,
),
Row(
children: <Widget>[
Icon(
Icons.email,
color: darkDynamic?.secondary ?? Colors.blue, // *10
),
const SizedBox(width: 10),
Text(
'xyz@gmail.com',
style: TextStyle(
color: darkDynamic?.secondary ?? Colors.blue, // *11
letterSpacing: 1,
fontSize: 18,
),
),
],
),
],
),
),
),
);
},
);
}
}
Для завершения пользовательского интерфейса я использовал виджеты Text(), SizedBox() и Icon().
В *5, *6, *7, *8, *9, *10 и *11 используется та же логика, что и в предыдущих пунктах, чтобы реализовать динамические темы в вашем приложении.
- Полный код main.dart
import 'package:flutter/material.dart';
import 'package:dynamic_color/dynamic_color.dart';
void main() => runApp(const IdCard());
class IdCard extends StatefulWidget {
const IdCard({Key? key}) : super(key: key);
@override
State<IdCard> createState() => _IdCardState();
}
class _IdCardState extends State<IdCard> {
int age = 0;
@override
Widget build(BuildContext context) {
return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
return MaterialApp(
home: Scaffold(
backgroundColor: darkDynamic?.background ?? Colors.white, // *1
appBar: AppBar(
title: Text(
'ID Card',
style: TextStyle(
fontWeight: FontWeight.bold,
letterSpacing: 1.9,
color: darkDynamic?.onPrimaryContainer ?? Colors.white, // *2
),
),
centerTitle: true,
backgroundColor: darkDynamic?.secondaryContainer.withAlpha(50) ??
Colors.blue, // *3
elevation: 6.0,
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
age += 1;
});
},
child: const Icon(
Icons.add,
color: Colors.black,
),
backgroundColor:
darkDynamic?.primary.withOpacity(1) ?? Colors.blue, // *4
),
body: Padding(
padding: const EdgeInsets.fromLTRB(30, 40, 30, 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
'NAME',
style: TextStyle(
color: darkDynamic?.primary.withOpacity(0.9) ??
Colors.black, // *5
letterSpacing: 2,
),
),
const SizedBox(height: 10),
Text(
'Vinay :)',
style: TextStyle(
color: darkDynamic?.secondary ?? Colors.blue, // *6
letterSpacing: 2,
fontSize: 28.0,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 30),
Text(
'CURRENT AGE',
style: TextStyle(
color: darkDynamic?.primary.withOpacity(0.9) ??
Colors.black, // *7
letterSpacing: 2,
),
),
const SizedBox(height: 10),
Text(
'$age',
style: TextStyle(
color: darkDynamic?.secondary ?? Colors.blue, // *8
letterSpacing: 2,
fontSize: 28.0,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 30),
Text(
'CONTACT',
style: TextStyle(
color: darkDynamic?.primary.withOpacity(0.9) ??
Colors.black, // *9
letterSpacing: 2,
),
),
const SizedBox(
height: 10,
),
Row(
children: <Widget>[
Icon(
Icons.email,
color: darkDynamic?.secondary ?? Colors.blue, // *10
),
const SizedBox(width: 10),
Text(
'xyz@gmail.com',
style: TextStyle(
color: darkDynamic?.secondary ?? Colors.blue, // *11
letterSpacing: 1,
fontSize: 18,
),
),
],
),
],
),
),
),
);
},
);
}
}
Если вы добрались до этого места, то поздравляем!
Последний шаг — протестировать ваше приложение с дианамической темой на вашем устройстве 📱.
Посетите этот проект на GitHub и ⭐ сохраните его, чтобы вы могли обратиться к нему в любое время.
Благодарности:
Абхишек Пандей за помощь.