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

发布于 1周前 作者 wuwangju 来自 Flutter

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.colorSchemecontext.textTheme

多主题和暗光模式

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

Flutter ThemeData 的生成

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

对于文本样式,工作方式类似。这里也使用 sys 作为令牌的前缀部分。为了更好地组织令牌在Token Studio中的命名,我们决定拆分文本样式命名。因此,如果你想生成 displaySmall 文本样式,只需使用类型为 typographysys.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_builderlib/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插件来生成和使用设计令牌了。这有助于保持设计一致性并简化样式管理。

回到顶部