Flutter设计令牌生成插件design_tokens_builder的使用

Flutter设计令牌生成插件design_tokens_builder的使用


Cover image

design_tokens_builder on Pub

一个用于从导出的设计令牌JSON文件生成Flutter ThemeData 的包。它旨在基于Material 3创建一个Flutter Theme,而不是为每个令牌创建大量自定义小部件。

特性

当前支持生成/解析以下内容:

  • ColorScheme
  • TextStyle
    • 字体族映射(从Figma字体名到Flutter字体名)
    • 字体粗细
    • 行高
    • 字号
    • 字间距
    • 文本装饰
  • 主题扩展
    • 边框
    • 圆角
    • 阴影
    • 颜色
    • 尺寸(带px)
    • 字体族
    • 字体粗细
    • 行高(仅百分比)
    • 数值
    • 不透明度(带%)
    • 内边距
    • 文本样式
    • 文本装饰
    • 文本风格
  • 通过 BuildContext 扩展暴露主题和扩展

入门指南

  1. 添加一个包含描述应用设计的令牌的 .json 文件。
  2. 在你的 lib 目录中添加一个 tokenbuilder.yaml 配置文件(查看示例项目):
    • 在配置文件中添加指向JSON文件的路径
    • 在配置文件中映射Figma字体名到Flutter字体族名
  3. 在 build.yaml 中添加此构建器:
    targets:
      $default:
        builders:
          design_tokens_builder:design_tokens_builder:
            enabled: true
    

使用方法

在可以使用令牌之前,必须启动构建运行器,执行 flutter pub run build_runner build。

之后,确保在你的 ThemeWidget 中使用正确的 GeneratedTokenSet。

return Theme(
  data: GeneratedTokenSet.general.data.dark,
  child: Container(),
);

通过监听亮度变化,你可以轻松切换主题。你也可以轻松更改令牌集。

此包还通过构建 BuildContext 扩展来暴露生成的主题扩展。你可以使用快捷方式,如 context.yourExtension。我们还提供了与主题相关的属性的快捷方式,例如 context.colorScheme 和 context.textTheme。

多主题和暗光模式

该包可以根据多个令牌数据JSON中的令牌集生成主题。每个集合将通过 GeneratedTokenSet 可用。这个枚举包含了所有集合对和 ThemeData 的亮光/暗光主题。要指定集合的亮度,使用 Light/Dark 作为后缀在设计令牌中(例如 yourSetLight, yourSetDark)。你可能有一个全局或核心集合,其中的通用令牌不会根据选择的亮度改变。在这种情况下,确保将其命名为 global。它将被构建器识别,并在创建 GeneratedTokenSets 时忽略。

Flutter ThemeData 的生成

该包允许直接生成Flutter的 ThemeData。为此,你需要为设计令牌设置一定的结构。由于这是一个与Material 3相关的系统令牌,用于生成Flutter的 ThemeData 的令牌应始终以 sys 开头。如果你想要在Flutter的 ColorScheme 中设置主颜色,你必须在包含所有令牌的JSON文件中添加一个名为 sys.primary 的令牌,类型为 color。primary 是 ColorScheme 的 primary 字段的名称。记得写字段名称为驼峰式命名,以便包可以正确识别它们。

对于文本样式,工作方式类似。这里也使用 sys 作为令牌的前缀部分。为了更好地组织令牌在Token Studio中的命名,我们决定拆分文本样式命名。因此,如果你想生成 displaySmall 文本样式,只需使用类型为 typography 的 sys.display.small 令牌。

示例令牌JSON:

{
  "light": {
    // <- Token set
    "sys": {
      // <- Token group sys - Used for Flutter ThemeData
      "primary": {
        // <- Token for primary color in ColorScheme
        "value": "#0000FF",
        "type": "color"
      },
      "background": {
        "value": "#FFFFFF",
        "type": "color"
      },
      "onBackground": {
        "value": "#000000",
        "type": "color"
      }
      ...
    }
  },
  "$themes": [],
  "$metadata": {
    "tokenSetOrder": [
      "light"
    ]
  }
}

在令牌中使用数学运算和别名

该包支持在令牌中使用数学运算和别名。

Tokens Studio for Figma功能对等性

请参阅 Tokens Studio for figma文档。

扩展表格

组 可解析 通过扩展公开
尺寸 ✅ ✅
间距 ✅ ✅
颜色 ✅ ✅
圆角 ✅ ✅
边框宽度 ✅ ✅
阴影 ✅ ✅
不透明度 ✅ ✅
字体族 ✅ ✅
字体粗细 ✅ ✅
字号 ✅ ✅
行高 ✅ ✅
字间距 ✅ ✅
段落间距 ✅ ✅
文本样式 ✅ ✅
资源 ❌ ❌
组合 ❌ ❌
尺寸 ✅ ✅
边框 ✅ ✅

Flutter主题化

下表展示了该包可以生成的主题属性。

扩展表格

属性 支持
colorScheme ✅
iconTheme ❌
textTheme ✅
… …

未来功能

我们希望扩展该包,使其能够生成/解析更多材料主题,如 ButtonTheme 等。

如果您发现缺少某些功能,请通过创建问题或积极贡献来告诉我们!

贡献

通过这个开源仓库,我们希望创建一个工具,帮助Flutter集成更多工具,通过利用Flutter的API简化并简化设计交接体验。由于这不仅是我们面临的问题,我们希望通过共享和协作此软件来回馈社区。任何贡献都是受欢迎的!

请参阅我们的贡献指南了解更多信息。


完整示例Demo

示例代码

import 'package:example/tokens.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  GeneratedTokenSet selectedSet = GeneratedTokenSet.general;
  Brightness brightness = Brightness.light;

  /// 返回基于所选亮度和令牌集的正确主题数据。
  ThemeData get themeData {
    if (brightness == Brightness.light) {
      return selectedSet.data.light.themeData;
    } else {
      return selectedSet.data.dark.themeData;
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Design token builder Demo',
      theme: themeData,
      home: Builder(builder: (context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: context.colorScheme.primary,
            leading: IconButton(
              icon: Icon(
                brightness == Brightness.light
                    ? Icons.light_mode_rounded
                    : Icons.dark_mode_rounded,
                color: context.colorScheme.onPrimary,
              ),
              onPressed: () {
                final newBrightness = brightness == Brightness.light
                    ? Brightness.dark
                    : Brightness.light;
                setState(() {
                  brightness = newBrightness;
                });
              },
            ),
            actions: [
              Padding(
                padding: const EdgeInsets.only(right: 8.0),
                child: DropdownButton<GeneratedTokenSet>(
                  selectedItemBuilder: (context) =>
                      GeneratedTokenSet.values
                          .map(
                            (e) => Center(
                              child: Text(
                                e.name,
                                style: context.textTheme.bodyMedium!.copyWith(
                                  color: context.colorScheme.onPrimary,
                                ),
                              ),
                            ),
                          )
                          .toList(),
                  value: selectedSet,
                  items: GeneratedTokenSet.values
                      .map(
                        (e) => DropdownMenuItem<GeneratedTokenSet>(
                          value: e,
                          child: Text(
                            e.name,
                            style: context.textTheme.bodyMedium!.copyWith(
                              color: context.colorScheme.onBackground,
                            ),
                          ),
                        ),
                      )
                      .toList(),
                  onChanged: (value) {
                    setState(() {
                      selectedSet = value!;
                    });
                  },
                ),
              ),
            ],
            title: Text(
              'Demo',
              style: context.textTheme.titleMedium!.copyWith(
                color: context.colorScheme.onPrimary,
              ),
            ),
          ),
          body: ColoredBox(
            color: context.colorScheme.background,
            child: ListView.builder(
              itemCount: textStyles(context).length,
              itemBuilder: (context, index) {
                final textStyle = textStyles(context)[index];
                return Text(
                  textStyle.debugLabel!.split(' ')[1],
                  style: textStyle.copyWith(
                      color: context.colorScheme.onBackground),
                );
              },
            ),
          ),
        );
      }),
    );
  }
}

List<TextStyle> textStyles(BuildContext context) => [
      context.textTheme.displayLarge!,
      context.textTheme.displayMedium!,
      context.textTheme.displaySmall!,
      context.textTheme.titleLarge!,
      context.textTheme.titleMedium!,
      context.textTheme.titleSmall!,
      context.textTheme.bodyLarge!,
      context.textTheme.bodyMedium!,
      context.textTheme.bodySmall!,
      context.textTheme.labelLarge!,
      context.textTheme.labelMedium!,
      context.textTheme.labelSmall!,
    ];

更多关于Flutter设计令牌生成插件design_tokens_builder的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter设计令牌生成插件design_tokens_builder的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用design_tokens_builder插件来生成和设计令牌的示例代码案例。design_tokens_builder是一个用于从设计系统中生成Flutter令牌的插件,可以帮助开发者在Flutter应用中保持设计一致性。

首先,确保你已经在pubspec.yaml文件中添加了design_tokens_builder依赖:

dependencies:
  flutter:
    sdk: flutter
  design_tokens_builder: ^最新版本号  # 请替换为实际的最新版本号

dev_dependencies:
  build_runner: ^最新版本号  # 用于运行构建脚本

然后,运行以下命令来获取依赖项:

flutter pub get

接下来,你需要创建一个JSON文件来定义你的设计令牌。例如,创建一个名为design_tokens.json的文件,内容如下:

{
  "colors": {
    "primary": "#3498db",
    "secondary": "#2ecc71",
    "background": "#ecf0f1",
    "text": "#2c3e50"
  },
  "spacing": {
    "small": "8px",
    "medium": "16px",
    "large": "32px"
  },
  "fonts": {
    "primary": {
      "family": "Roboto",
      "weight": 400,
      "size": "16px"
    },
    "secondary": {
      "family": "Roboto",
      "weight": 700,
      "size": "14px"
    }
  }
}

接下来,在你的Flutter项目中创建一个build.yaml文件来配置design_tokens_builder:

targets:
  $default:
    builders:
      design_tokens_builder:
        generate_for:
          - "lib/design_tokens.json"
        options:
          output_extension: ".dart"

这个配置告诉design_tokens_builder从lib/design_tokens.json文件生成Dart文件,并将生成的文件扩展名设置为.dart。

现在,你可以运行构建脚本来生成Dart文件:

flutter pub run build_runner build

运行上述命令后,你应该会在lib目录下看到一个名为design_tokens.dart的文件,内容类似于:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'design_tokens.dart';

class DesignTokens {
  static const Color primaryColor = Color(0xff3498db);
  static const Color secondaryColor = Color(0xff2ecc71);
  static const Color backgroundColor = Color(0xffecf0f1);
  static const Color textColor = Color(0xff2c3e50);

  static const double smallSpacing = 8.0;
  static const double mediumSpacing = 16.0;
  static const double largeSpacing = 32.0;

  static final TextStyle primaryTextStyle = TextStyle(
    fontFamily: 'Roboto',
    fontWeight: FontWeight.w400,
    fontSize: 16.0,
  );

  static final TextStyle secondaryTextStyle = TextStyle(
    fontFamily: 'Roboto',
    fontWeight: FontWeight.w700,
    fontSize: 14.0,
  );
}

最后,在你的Flutter应用中使用这些生成的令牌。例如:

import 'package:flutter/material.dart';
import 'design_tokens.dart'; // 导入生成的令牌文件

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Design Tokens Example', style: DesignTokens.primaryTextStyle),
          backgroundColor: DesignTokens.primaryColor,
        ),
        body: Center(
          child: Text(
            'Hello, Flutter!',
            style: TextStyle(color: DesignTokens.textColor),
          ),
        ),
      ),
    );
  }
}

这样,你就成功地在Flutter项目中使用design_tokens_builder插件来生成和使用设计令牌了。这有助于保持设计一致性并简化样式管理。

回到顶部