Темы
Говоря о темах подразумевается не совсем то, к чему привыкли обычные Flutter разработчики: глобальные стили виджетов, цветовые схемы и тд. В рамках Duit темы - это мощная функция, которая решает несколько ключевых задач:
- Определение набора свойств виджетов, которые будут использоваться во всем приложении
- Уменьшение размера макетов экранов за счет выделения повторяющихся свойств в темы
- Поддержка дизайн-системы на стороне сервера
Принцип работы тем
Применение тем к виджетам состоит из нескольких этапов:
- Инициализация темы: Преобразование JSON-данных темы в токенизированный объект
DuitThemeс помощью классаDuitThemePreprocessorи регистрация вDuitRegistry - Получение токена темы: При создании атрибутов виджета извлекается соответствующий токен по ключу темы
- Слияние атрибутов: Объект атрибутов виджета сливается с атрибутами токена темы согласно правилам слияния
Вся эта логика выполняется автоматически при создании экземпляра ViewAttribute через метод ViewAttribute.from().
Создание темы
На текущем этапе DSL-библиотеки не имеют специфических классов или функций для создания и валидации тем на стороне бэкенда. Тема представляет собой хеш-таблицу следующего вида:
{
"text_bold": {
"type": "Text"
"data": {
"fontSize": 18.0,
"fontWeight": 700,
},
},
"text_secondary": {
"type": "Text"
"data": {
"fontSize": 14.0,
"fontWeight": 400,
},
},
"col_space_btw": {
"type": "Column",
"data": {
"mainAxisAlignment": "spaceBetween",
},
},
"custom_widget_theme1": {
"type": "CustomWidgetTagInsteadOfWidgetType",
"data": {
"some_property": "some_value",
},
},
}
Ключами хеш-таблицы являются названия тем. Названия должны быть уникальными, но вы можете создать неограниченное количество тем для виджетов одного типа.
Значениями хеш-таблицы являются объекты, которые представляют собой объекты токенов темы, которые в свою очередь состоят из type - типа виджета для которого будет использована конкретная тема и data - объекта атрибутов виджета.
Темы поддерживаются в том числе и для пользовательских виджетов.
Чтобы тема корректно применялась к атрибутам виджета, свойство type должно содержать тэг пользовательского виджета, а не его базовый тип (Custom).
Инициализация темы
Создание и инициализация темы
Для работы с темами в Duit необходимо:
- Преобразовать JSON-данные темы в токенизированный объект
DuitThemeс помощью классаDuitThemePreprocessor - Инициализировать тему в реестре через метод
DuitRegistry.initialize()
// Пример: загрузка темы из JSON и инициализация
Future<void> initTheme() async {
// Вариант 1: тема из локального JSON
final themeData = jsonDecode(await rootBundle.loadString('assets/theme.json'));
// Вариант 2: тема с сервера
// final res = await dio.get('/theme');
// final themeData = res.data;
final preprocessor = const DuitThemePreprocessor();
final theme = preprocessor.tokenize(themeData);
await DuitRegistry.initialize(theme: theme);
}
После получения JSON-данных, которые содержат тему, следует использовать класс DuitThemePreprocessor для выполнения токенизации (преобразования MapEntry в экземпляры класса ThemeToken).
Токены темы
Токены - это специальные объекты, хранящие атрибуты виджета, которые будут использоваться при слиянии объектов атрибутов и атрибутами темы. Они используются для более гибкой настройки процесса валидации входящих параметров при обработке темы.
abstract class ThemeToken {
final Set<String> excludedFields;
final Map<String, dynamic> _data;
final String type;
Map<String, dynamic> get widgetTheme => _data;
const ThemeToken(
this.excludedFields,
this._data,
this.type,
);
}
Пристальное внимание следует обратить на свойство Set<String> excludedFields.
Это множество, содержащее названия полей, которые не должны быть включены в атрибуты темы виджета.
Механизм токенизации позволяет валидировать входящие атрибуты и убедиться, что они соответствуют ожидаемым.
В случае, если одно из свойств находится в сете excludedFields, будет выброшено исключение.
Встроенные типы токенов
DuitThemePreprocessor поддерживает множество встроенных типов токенов:
- TextThemeToken - для виджетов типа
Text - ImageThemeToken - для виджетов типа
Image - AttendedWidgetThemeToken - для виджетов с состоянием (Checkbox, Switch, TextField)
- AnimatedPropOwnerThemeToken - для виджетов, владеющих анимированными свойствами
- ImplicitAnimatableThemeToken - для виджетов с неявными анимациями
- ExcludeGestureCallbacksThemeToken - для виджетов с жестами (GestureDetector, InkWell)
- ExcludeChildThemeToken - для виджетов, исключающих дочерние элементы из темы (AppBar, Scaffold)
- DynamicChildHolderThemeToken - для виджетов с динамическими дочерними элементами (GridView, ListView)
- RadioThemeToken, RadioGroupContextThemeToken - для радиокнопок
- SliderThemeToken - для слайдеров
- RichTextThemeToken - для rich text
Для всех остальных типов виджетов используется DefaultThemeToken.
Расширение и переопределение поведения препроцессора
В рамках реализации препроцессора существуют два способа расширения поведения:
customWidgetTokenizer- добавление обработки нового типа виджета в препроцессор. Полезно в тех случаях, когда вы хотите добавить не реализованные ранее темы виджетов или использовать темы для пользовательских виджетов.overrideWidgetTokenizer- переопределение создания токена для конкретного типа виджета. Полезно в тех случаях, когда вы хотите изменить список исключенных полей для встроенного виджета.
В обоих случаях следует вернуть экземпляр класса ThemeToken или null, если вы не хотите переопредел ить поведение. Также вы можете создавать свои собственные классы ThemeToken для удобства.
// Создание токена для пользовательского виджета
final class CustomWidgetThemeToken extends ThemeToken {
CustomWidgetThemeToken(Map<String, dynamic> data)
: super(
const {}, // разрешить все поля
data,
"ExampleCustomWidget",
);
}
// Переопределение токена для встроенного виджета Text
final class OverrideTextThemeToken extends ThemeToken {
OverrideTextThemeToken(Map<String, dynamic> data)
: super(
const {"data"}, // запретить использование поля "data"
data,
"Text",
);
}
// Использование в препроцессоре
Future<void> initThemeWithCustomTokens() async {
final preprocessor = DuitThemePreprocessor(
// Добавляем обработку нового токена
customWidgetTokenizer: (type, themeData) {
switch (type) {
case "ExampleCustomWidget":
return CustomWidgetThemeToken(themeData);
}
return null;
},
// Переопределяем обработку встроенного виджета
overrideWidgetTokenizer: (type, themeData) {
switch (type) {
case "Text":
return OverrideTextThemeToken(themeData);
}
return null;
},
);
final theme = preprocessor.tokenize(themeData);
await DuitRegistry.initialize(theme: theme);
}
В приведенном выше примере кода мы создали препроцессор с настра иваемой логикой токенизации для пользовательских и встроенных виджетов.
Использование темы в DSL-библиотеке
Для многих виджетов, которые реализованы в DSL-библиотеках был добавлен ряд новых опциональных свойств, которые позволяют конфигурировать виджеты для использования тем. Эти свойства определены в интерфейсе ThemeConsumer.
type ThemeConsumer struct {
Theme string `json:"theme,omitempty"`
IgnoreTheme bool `json:"ignoreTheme,omitempty"`
OverrideRule ThemeOverrideRule `json:"overrideRule,omitempty"`
}
Свойства ThemeConsumer
theme- строковый ключ, уникальное название токена темы, который вы хотите применить к виджету.ignoreTheme- указывает, должен ли конкретный виджет игнорировать указанную тему. По умолчанию имеет значениеfalse.overrideRule- определяет способ слияния атрибутов темы и виджета.
Правила слияния (overrideRule)
Свойство overrideRule может принимать два значения:
themeOverlay(по умолчанию) - тема применяется поверх существующих свойств виджета, но не перезаписывает их. Если у виджета есть свойство, которое также есть в теме, значение виджета имеет приоритет.themePriority- тема имеет приоритет над свойствами виджета. Если у виджета есть свойство, которое также есть в теме, значение темы перезапишет значение виджета.
Пример использования
textWidget := &TextAttributes{
Data: "Hello World",
ThemeConsumer: &ThemeConsumer{
Theme: "text_primary",
OverrideRule: ThemeOverlay,
IgnoreTheme: false,
},
TextAlign: duit_props.TextAlignCenter,
}
В данном примере, если тема text_primary содержит fontSize: 18 и color: "#FF0000", виджет получит эти свойства, но его собственные свойства (например, textAlign: "center" и data: "Hello World") сохранятся.
Механизм слияния тем
При создании атрибутов виджета происходит слияние свойств виджета с атрибутами темы:
Алгоритм слияния
- Проверка флага
ignoreTheme: ЕслиignoreTheme = true, применение темы пропускается. - Получение токена темы: По ключу
themeизвлекается токен темы из регистраDuitRegistry. - Проверка типа: Токен применяется только если его тип совпадает с типом виджета.
- Слияние атрибутов в зависимости от
overrideRule:themeOverlay:{...token.widgetTheme, ...widgetAttributes}- свойства виджета перезаписывают свойства темыthemePriority:widgetAttributes.addAll(token.widgetTheme)- свойства темы перезаписывают свойства виджета
Примеры
Рассмотрим пример работы механизма слияния:
{
"text_primary": {
"type": "Text",
"data": {
"fontSize": 18.0,
"fontWeight": 700,
"color": "#FF0000"
}
}
}
Пример 1: themeOverlay (по умолчанию)
Виджет:
{
"type": "Text",
"data": "Hello",
"theme": "text_primary",
"textAlign": "center"
}
Результат слияния:
{
"data": "Hello",
"textAlign": "center",
"fontSize": 18.0,
"fontWeight": 700,
"color": "#FF0000"
}
Пример 2: themePriority
Виджет:
{
"type": "Text",
"data": "Hello",
"theme": "text_primary",
"overrideRule": "themePriority",
"textAlign": "center"
}
Результат слияния:
{
"data": "Hello",
"textAlign": "center",
"fontSize": 18.0,
"fontWeight": 700,
"color": "#FF0000"
}