[Flutter翻译]Storybook + Flutter = Storybook_Flutter

1,239 阅读5分钟

原文地址:medium.com/flutter-com…

原文作者:ookamikb.medium.com/

发布时间:4月15日-4分钟阅读

image.png

这篇文章是关于推广我的Flutter库,用于展示孤立的小部件和屏幕。就像React世界里的Storybook一样。实际上,它甚至被命名为storybook_flutter

为什么你需要它?

首先,为了加快UI开发速度。是的,Flutter有一个 "热重载",可以让UI开发非常流畅,但如果你要开发的widget在应用屏幕深处的某个地方呢?如果只有在某些特定条件下才能看到呢?除此之外,热重载也不是在所有情况下都能用。因此,隔离小部件,使其成为一个单独的故事,并与这个故事一起工作的可能性是非常有用的。

第二,展示你的widget和屏幕。例如,我们为Flutter实现了自己的设计库,我们希望在文档中集成一个带有widget的交互式沙盒。尤其是现在,Flutter for Web已经进入稳定通道。

第三,从一开始就有一个功能要求:用每一个可能的widget参数组合自动生成黄金测试。我觉得这个想法挺有意思的,我希望将来能增加这个功能。

你不能直接使用一些现有的库吗?

也许可以,是的。但是,目前在社区库中还没有明确的领导者。另外,你也不能把NIH综合症从柜台上拿下来:) 除此以外,我希望能够尽快添加我们需要的功能。

它是什么样子的呢?

有点像这样。

image.png

是的,不是艺术作品,但这不是现在的首要任务,而且我的设计能力也不高... 除此之外,我还在试验按钮和工具栏的位置,所以目前没有必要去打磨视觉风格。

你能用它做什么?

  • 故事导航,可选择按章节分组。
  • 小工具的旋钮(可自定义参数)。
  • 明暗主题切换。
  • 故事的全屏模式,没有任何UI元素--可以用于网络,例如,将故事嵌入到iframe中。
  • 自定义。
  • 设备框架(感谢device_frame包)。
  • 插件支持。

如何使用它?

将依赖关系添加到 pubspec.yaml 文件中。

storybook_flutter: ^0.5.0

创建故事 一个最简单的例子是

import 'package:flutter/material.dart';
import 'package:storybook_flutter/storybook_flutter.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) => Storybook(
        children: [
          Story.simple(
            name: 'Button',
            child: ElevatedButton(
              onPressed: () {},
              child: const Text('Push me'),
            ),
          ),
        ],
      );
}

运行项目,然后就可以了

image.png

让我们添加一些旋钮 我们需要做的就是用一个默认的简单构造函数(simpleconstructor)代替,并且使用 builderinstead 而不是 child。

再次运行构建程序 现在更好了。

Story(
  name: 'Button',
  builder: (context, k) => ElevatedButton(
    onPressed:
        k.boolean(label: 'Enabled', initial: true) ? () {} : null,
    child: Text(k.text(label: 'Text', initial: 'Push me')),
  ),
),

按章节对故事进行分组,就像添加章节参数一样简单。

1.gif

所有具有相同章节值的故事都会被自动分组。

Story(
  name: 'Button',
  section: 'Buttons',
  builder: (context, k) => ElevatedButton(
    onPressed:
        k.boolean(label: 'Enabled', initial: true) ? () {} : null,
    child: Text(k.text(label: 'Text', initial: 'Push me')),
  ),
),

如何自定义?

每个故事都有padding和颜色参数,控制故事的padding和背景颜色。

Story(
  name: 'Button',
  section: 'Buttons',
  padding: const EdgeInsets.all(8),
  background: Colors.red,
  builder: (context, k) => ElevatedButton(
    onPressed:
        k.boolean(label: 'Enabled', initial: true) ? () {} : null,
    child: Text(k.text(label: 'Text', initial: 'Push me')),
  ),
),

但这太简单了 更多有趣的事情可以通过wrapperBuilder参数来完成。它允许用一个自定义的widget来包装故事。

Story(
  name: 'Button',
  section: 'Buttons',
  wrapperBuilder: (context, story, child) => Container(
    decoration: BoxDecoration(border: Border.all()),
    margin: const EdgeInsets.all(16),
    child: Center(child: child),
  ),
  builder: (context, k) => ElevatedButton(
    onPressed:
        k.boolean(label: 'Enabled', initial: true) ? () {} : null,
    child: Text(k.text(label: 'Text', initial: 'Push me')),
  ),
),

这个构件可以作为storybook中storyWrapperBuilder参数的一个值传递给我们。在这种情况下,每个故事都会被这个widget包装(当然,你仍然可以用一个故事的wrapperBuilder单独覆盖它)。

我们需要更多的自定义功能!

如果构建器、包装器和参数还不够,你可以拿CustomStorybook来自己做一切。

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final decoration = BoxDecoration(
      border: Border(
        right: BorderSide(color: Theme.of(context).dividerColor),
        left: BorderSide(color: Theme.of(context).dividerColor),
      ),
      color: Theme.of(context).cardColor,
    );
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: CustomStorybook(
          builder: (context) => Row(
            children: [
              Container(
                width: 200,
                decoration: decoration,
                child: const Contents(),
              ),
              const Expanded(child: CurrentStory()),
              Container(
                width: 200,
                decoration: decoration,
                child: const KnobPanel(),
              ),
            ],
          ),
          children: [
            Story(
              name: 'Button',
              builder: (context, k) => ElevatedButton(
                onPressed:
                    k.boolean(label: 'Enabled', initial: true) ? () {} : null,
                child: Text(k.text(label: 'Text', initial: 'Push me')),
              ),
            )
          ],
        ),
      ),
    );
  }
}

你仍然可以使用内置的widget: Contents, CurrentStory和KnobPanel (我打赌你能猜到它们的作用). 我们将在这里得到一些最小化的结果。

image.png

CustomStorybook的一个可能的用例是这个插件,它将Storybook添加到另一个包中,device_preview,支持内容和旋钮面板。有了它,你可以实现这样的东西。

image.png

那插件呢?

最新版本增加了对插件和第一方插件DeviceFramePlugin的支持。

image.png

插件允许重写故事的渲染方式,也可以在面板上添加自定义设置。不过创建插件是另一篇文章的主题。

支持哪些平台?

好吧,引擎盖下没有特殊的魔法,所以理论上它应该可以在Flutter支持的所有平台上工作。我已经在Android、iOS、web和macOS上测试过了。

有什么路线图吗?

首先,稳定插件API,思考应该提供哪些开箱即用的插件(当然也要开发)。

之后,我想我会像开头提到的那样,研究一下黄金测试生成。

就这样吧。欢迎大家提出任何意见、功能要求和bug报告(以及点赞和星星)。


原文发表于:developers.mews.com 2021年4月15日。


www.deepl.com 翻译