Flutter 中使用 Widgetbook 管理你的组件

news2024/11/16 12:36:21

Flutter 中使用 Widgetbook 管理你的组件

前言

Flutter 界面开发中我们有几个痛点 :

  • 与设计师协作复用一套设计规范(figma)
  • 可视化的管理你的组件代码(基础组件、业务组件)
  • 不同设备尺寸测试你的组件
  • 实时修改你的测试组件参数

原文 https://ducafecat.com/blog/flutter-uses-a-widgetbook-to-manage-your-components

视频

https://www.bilibili.com/video/BV1qM4y1b7WL/

参考

  • https://docs.widgetbook.io
  • https://ant.design/docs/spec/introduce-cn

Widgetbook

https://www.widgetbook.io/

Flutter Widgetbook 是一个用于构建和交互 Flutter 组件库的工具。它允许您在单独的应用程序中构建和演示您的 Flutter 组件,以便您可以在不运行完整应用程序的情况下进行快速迭代和测试。

使用 Flutter Widgetbook,您可以:

  • 构建和演示单个组件,而无需在完整应用程序中运行它们。
  • 以交互方式测试组件的不同状态和属性,以及不同平台和设备的外观和行为。
  • 共享您的组件库,并让其他人轻松地查看和测试您的组件。

您可以在 Flutter 应用程序中使用 Widgetbook,也可以将其作为独立应用程序使用。在 Widgetbook 中,您可以编写 Dart 代码来定义组件和演示它们的用法。您可以使用 Flutter 提供的任何组件和库,并使用 Widgetbook 提供的一些工具来组织和显示您的组件。

设计规范

前端设计规范是一组定义前端设计和开发过程中所需遵守的准则和规则的规范。它们旨在确保前端代码的一致性、可维护性、可扩展性和可重用性,并促进团队间的协作。

前端设计规范主要包括以下内容:

  1. 布局规范:定义页面布局和排版的规则,包括网格系统、排版间距、基准线等。
  2. 样式规范:定义颜色、字体、图标、按钮等基本样式的使用和规范,包括设计风格、调色板、字体类型、字号、行高等。
  3. 组件规范:定义前端组件的设计和开发规则,包括组件的命名、结构、样式、交互、状态管理等。
  4. 图片和媒体规范:定义图片和媒体资源的格式、尺寸、优化和加载等规则,以提高页面性能和用户体验。
  5. 响应式设计规范:定义响应式设计的原则和规则,包括页面布局、元素大小和位置、字体大小、图片和媒体资源的显示等。
  6. 可访问性规范:定义网站或应用程序的可访问性规则,包括键盘导航、语义标记、焦点指示、颜色对比度等。
  7. 性能规范:定义优化前端性能的规则,包括代码压缩、缓存控制、资源加载、代码分割等。

前端设计规范可以通过文档、工具、模板和代码库等方式来实现和维护。它们可以帮助团队提高开发效率、降低维护成本、保持代码质量和可维护性,并促进设计和开发间的协作。

Ant Design 设计规范参考

https://ant.design/docs/spec/introduce-cn

代码

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter-widgetbook

步骤

安装 widgetbook 组件

pubspec.yaml

dev_dependencies:
  flutter_test:
    sdk: flutter

  ...

  widgetbook: ^3.0.0-beta.14

注意是放在 dev_dependencies 节点下面

编写调试界面

lib/app.widgetbook.dart

// ignore_for_file: depend_on_referenced_packages

import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_in_flutter_course/widgets/button.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return Widgetbook.material(
      addons: [
      ],
      directories: [
      ],
    );
  }
}

运行

加入组件

准备两个组件代码

lib/widgets/button.dart

import 'package:flutter/material.dart';

class MyElevatedButton extends StatelessWidget {
  final VoidCallback? onPressed;
  final String? text;
  final IconData? icon;
  final Color? textColor;
  final Color? buttonColor;
  final double? borderRadius;
  final double? height;
  final double? width;

  const MyElevatedButton({
    Key? key,
    this.onPressed,
    this.text,
    this.icon,
    this.textColor,
    this.buttonColor,
    this.borderRadius,
    this.height,
    this.width,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: height ?? 48.0,
      width: width,
      child: ElevatedButton(
        onPressed: onPressed,
        style: ElevatedButton.styleFrom(
          primary: buttonColor ?? Theme.of(context).primaryColor,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(borderRadius ?? 8.0),
          ),
        ),
        child: icon == null
            ? Text(
                text!,
                style: TextStyle(color: textColor ?? Colors.white),
              )
            : Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(icon, color: textColor ?? Colors.white),
                  SizedBox(width: 8.0),
                  Text(
                    text!,
                    style: TextStyle(color: textColor ?? Colors.white),
                  ),
                ],
              ),
      ),
    );
  }
}

lib/components/login.dart

import 'package:flutter/material.dart';

class LoginForm extends StatefulWidget {
  const LoginForm({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm{
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  bool _isObscured = true;
  bool _isLoading = false;

  void _toggleObscure() {
    setState(() {
      _isObscured = !_isObscured;
    });
  }

  void _submit() async {
    if (_formKey.currentState!.validate()) {
      setState(() {
        _isLoading = true;
      });

      // Simulate a login request
      await Future.delayed(const Duration(seconds: 2));

      setState(() {
        _isLoading = false;
      });

      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Logged in successfully!'),
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Form(
        key: _formKey,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            TextFormField(
              controller: _emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(
                labelText: 'Email',
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter your email';
                }
                if (!value.contains('@')) {
                  return 'Please enter a valid email address';
                }
                return null;
              },
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _passwordController,
              obscureText: _isObscured,
              decoration: InputDecoration(
                labelText: 'Password',
                suffixIcon: IconButton(
                  icon: Icon(
                    _isObscured ? Icons.visibility : Icons.visibility_off,
                  ),
                  onPressed: _toggleObscure,
                ),
              ),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter your password';
                }
                if (value.length < 6) {
                  return 'Password must be at least 6 characters long';
                }
                return null;
              },
            ),
            const SizedBox(height: 32),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _isLoading ? null : _submit,
                child: _isLoading
                    ? const CircularProgressIndicator()
                    : const Text('Log in'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

分类1 - 公共组件

  WidgetbookCategory buildWidgetbookCategory() {
    return WidgetbookCategory(
      name: '公共组件',
      children: [
        WidgetbookComponent(
          name: '按钮',
          useCases: [
            WidgetbookUseCase.center(
              name: "红色背景",
              child: MyElevatedButton(
                onPressed: () => print("Button pressed"),
                text: "Click me",
                icon: Icons.arrow_forward,
                buttonColor: Colors.red,
                borderRadius: 16.0,
                height: 60.0,
                width: double.infinity,
              ),
            ),
            WidgetbookUseCase.center(
              name: "蓝色色背景",
              child: MyElevatedButton(
                onPressed: () => print("Button pressed"),
                text: "Click me",
                icon: Icons.arrow_forward,
                buttonColor: Colors.blue,
                borderRadius: 16.0,
                height: 60.0,
                width: double.infinity,
              ),
            )
          ],
        ),
      ],
    );
  }

分类2 - 业务组件

  WidgetbookCategory buildWidgetbookCategory2() {
    return WidgetbookCategory(
      name: '业务组件',
      children: [
        WidgetbookComponent(
          name: '系统常用',
          useCases: [
            WidgetbookUseCase(
              name: "登录界面",
              builder: (BuildContext context) {
                return LoginForm(
                  title: context.knobs.text(
                    label: '标题 [title]',
                    initialValue: '用户登录',
                  ),
                );
              },
            ),
            WidgetbookUseCase(
              name: "注册界面",
              builder: (BuildContext context) {
                return LoginForm(
                  title: context.knobs.text(
                    label: '标题 [title]',
                    initialValue: '用户注册',
                  ),
                );
              },
            ),
          ],
        ),
      ],
    );
  }

通过 knobs 的方式设置调试参数

其它参数类型

build 函数

  @override
  Widget build(BuildContext context) {
    return Widgetbook.material(
      addons: [
      ],
      directories: [
        // 基础组件
        buildWidgetbookCategory(),

        // 业务组件
        buildWidgetbookCategory2(),
      ],
    );
  }

输出

设置选项

主题

  MaterialThemeAddon buildMaterialThemeAddon() {
    return MaterialThemeAddon(
        setting: MaterialThemeSetting.firstAsSelected(themes: [
      WidgetbookTheme(name: "dark", data: ThemeData.dark()),
      WidgetbookTheme(name: "light", data: ThemeData.light()),
    ]));
  }

字体尺寸

  TextScaleAddon buildTextScaleAddon() {
    return TextScaleAddon(
        setting: TextScaleSetting.firstAsSelected(
            textScales: [1.01.251.51.752]));
  }

build 函数

  @override
  Widget build(BuildContext context) {
    return Widgetbook.material(
      addons: [
        // 主题
        buildMaterialThemeAddon(),

        // 字体大小
        buildTextScaleAddon(),
      ],
      directories: [
        // 基础组件
        buildWidgetbookCategory(),

        // 业务组件
        buildWidgetbookCategory2(),
      ],
    );
  }

输出

其它 addon

小结

Flutter Widgetbook 对前端开发工作有以下好处:

  1. 提高开发效率:Flutter Widgetbook 可以让前端开发人员在不需要启动完整应用程序的情况下构建和演示 Flutter 组件,快速迭代和测试组件的不同状态和属性,从而提高开发效率。
  2. 促进组件复用:Flutter Widgetbook 可以让前端开发人员在单独的应用程序中构建和演示组件,从而促进组件的复用和共享,减少代码重复和维护成本。
  3. 保持代码一致性:Flutter Widgetbook 可以作为一个组件库来使用,定义前端组件的设计和开发规则,从而保持代码的一致性、可维护性、可扩展性和可重用性。
  4. 提高跨团队协作:Flutter Widgetbook 可以让前端开发人员共享他们的组件库,并让其他人轻松地查看和测试他们的组件,从而促进跨团队协作和知识共享。
  5. 提高用户体验:Flutter Widgetbook 可以让前端开发人员在不同平台和设备上测试组件的外观和行为,以确保它们能够提供一致的用户体验,从而提高用户体验和用户满意度。

总之,Flutter Widgetbook 是一个有用的工具,可以帮助前端开发人员更轻松地构建和测试 Flutter 组件,从而提高开发效率、保持代码质量和可维护性,并促进跨团队协作和知识共享,最终提高用户体验和用户满意度。


© 猫哥

ducafecat.com

end

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/493489.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【事务】在spring中事务不生效的场景总结

在开发过程中会遇到事务失效的问题&#xff0c;所以在开发中要特别注意&#xff0c;下面我自己总结了事务不生效的场景&#xff0c;提醒自己。 一般出现问题分为几大类&#xff1a; 配置问题spring aop代理问题底层数据库不支持事务问题Transactional 配置错误开发过程中使用错…

Go有序map:orderedmap

有序映射 与传统的无序映射&#xff08;Map&#xff09;不同&#xff0c;orderedmap包中的有序映射&#xff08;OrderedMap&#xff09;可以记录键值对的插入顺序。orderedmap提供了一些有用的API&#xff0c;用来存储、删除、查询和遍历键值对。 获取OrderedMap 你可以通过Ord…

地面分割--Fast Segmentation of 3D Point Clouds for Ground Vehicles论文阅读与源码分析

文章目录 1写在前面的话2点云投影分块3地面点云分割4核心代码阅读投影分块直线拟合代码分割地面点云 5实验效果参考 1写在前面的话 这篇文章属于地面分割领域非常经典的一篇论文&#xff0c;论文具有速度快&#xff0c;在一定程度能适应有坡度的地形&#xff0c;文章主要分为两…

学习使用ansible自动化运维工具

目录 一、虚拟机环境 二、yum方式部署 三、ansible使用 &#xff08;一&#xff09;将ansible服务器上文件分发给各节点 1. 创建一个要复制的文件&#xff0c;并复制到Ansible管理主机上 2.编辑Ansible的playbook文件&#xff0c;将copy模块添加到任务列表中 3. 运行play…

【c++迭代器模拟实现】

目录&#xff1a; 前言一、STL初始二、六大组件之迭代器迭代器初始迭代器的模拟实现&#xff08;1&#xff09;victor正向迭代器反向迭代器1反向迭代器2反向迭代器3 &#xff08;2&#xff09;list正向迭代器反向迭代器 总结 前言 打怪升级&#xff1a;第52天 一、STL初始 什…

和chatgpt一样的大模型LLaMA可以运行在pc上?

未来已来,大模型依据压缩模型的方式,可以在普通的PC上运行. LLaMA Facebook的LLaMA 模型和Georgi Gerganov 的llama.cpp的结合。 LLaMA&#xff0c;这是一组包含 7B 到 65B 参数的基础语言模型。我们在数万亿个令牌上训练我们的模型&#xff0c;并表明可以仅使用公开可用的数…

【Android入门到项目实战-- 9.1】—— 传感器的使用教程

目录 传感器的定义 三大类型传感器 1、运动传感器 2、环境传感器 3、位置传感器 传感器开发框架 1、SensorManager 2、Sensor 3、SensorEvent 4、SensorEventListener 一、使用传感器开发步骤 1、获取传感器信息 1)、获取传感器管理器 2)、获取设备的传感器对象列…

Java红黑树

概述 红黑树是一种自平衡的二叉查找树&#xff0c;是计算机科学中用到的一种数据结构。1972年出现的&#xff0c;当时被称之为平衡二叉B树。在1978年被修改为红黑树。红黑树是一种特殊的二叉查找树&#xff0c;红黑树上的每一个节点都有存储位表示节点的颜色。每一个节点可以是…

Java枚举:为什么它是单例模式的最佳选择?

前言 单例模式&#xff0c;是工作中比较常见的一种设计模式&#xff0c;通常有两种实现方式&#xff0c;懒汉式和饿汉式。但是这两种实现方式存在一些问题。懒汉式需要在多线程环境下使用同步锁机制来保证只有一个实例被创建&#xff0c;这会影响程序的性能。而饿汉式在类加载时…

《发展心理学——儿童与青少年》读书笔记

这是我读的第一本关于育儿教育类的书&#xff0c;该书的作者是David R. Shaffer&#xff0c;由北京师范大学博士生导师邹泓审校&#xff0c;由其底下的博士生们翻译。我看的是中文第九版。下面是我在阅读此书时做的关键摘录和部分感想&#xff1a; 第1章 导论:发展心理学及其研…

Java基础(二十一):集合源码

Java基础系列文章 Java基础(一)&#xff1a;语言概述 Java基础(二)&#xff1a;原码、反码、补码及进制之间的运算 Java基础(三)&#xff1a;数据类型与进制 Java基础(四)&#xff1a;逻辑运算符和位运算符 Java基础(五)&#xff1a;流程控制语句 Java基础(六)&#xff1…

耗时2.5h含泪打造windows10家庭版docker安装

文章目录 一、事出有因二、安装流程Problem1Problem2Problem3 三、胜利的曙光 一、事出有因 由于最近需要跑通github上的一个代码&#xff0c;那个github上的代码需要通过docker部署到本地&#xff0c;但是我的电脑上并没有docker,真的是含泪历时2.5h才把docker在我的windows电…

【Python成长之路】基于Flask-admin库,结合html+vue,实现前后端数据传递

一、前言 前面已经做了Flask-admin库的基本介绍和几个库常用功能如何使用&#xff0c;若不了解请移步到以下博客&#xff1a; 1、?《【Python成长之路】基于Flask-admin库&#xff0c;编写个人工作平台代码详述》 2、?《【Python成长之路】基于Flask-admin库&#xff0c;编…

DP练习题

1.减操作(ACWING) 若有 a b c d e f g 几个数&#xff0c; 先对位置d操作 变成 a b c d - e f g 再对c操作 变成 a b c - (d-e) f g 仔细分析后得出结论&#xff1a;对于第一个数如a, 它一定为正数&#xff0c;第二个数b,一定为负数&#…

Java并发(四)----线程运行原理

1、线程运行原理 1.1 栈与栈帧   Java Virtual Machine Stacks &#xff08;Java 虚拟机栈 JVM&#xff09; 我们都知道 JVM 中由堆、栈、方法区所组成&#xff0c;其中栈内存是给谁用的呢&#xff1f;其实就是线程&#xff0c;每个线程启动后&#xff0c;虚拟机就会为其分…

java 解密springboot的WEB端口是谁启动的之内嵌tomcat

找到项目的 pom.xml 看到下面的spring-boot-starter-web 我们按住 Ctrl 点击进去 里面就有一个 tomcat 简单说 我们的程序能启动起tomcat端口 就是靠的这个东西 简单说 就是在程序中嵌了一个tomcat服务器 这里 可能就有小伙伴蒙了 不是把程序放在服务器上运行吗&#xff1f…

Linux Driver 和Device匹配过程分析(2)

Linux Driver 和Device匹配过程分析&#xff08;2&#xff09; 1 device注册流程2&#xff0c;driver注册匹配过程&#xff1a;2.1 pci_register_driver2.1.1 nvme_init2.1.2 pci_register_driver2.1.3 __pci_register_driver2.1.4 driver_register2.1.5 bus_add_driver2.1.6 d…

读书笔记——《2001太空漫游》

阿瑟克拉克神作&#xff0c;任何一个科幻迷都绕不开的一部作品。很早就听说过其大名&#xff0c;因为之前看过电影版的&#xff0c;总感觉少了点新鲜感&#xff0c;这本书就一直在书架上没有拿出来看。但是看过这本书后&#xff0c;我可以很负责任的说&#xff0c;全书都充满新…

【递推专题】常见的递推“模型”总结

目录 1.斐波那契数列分析&#xff1a;代码&#xff1a; 2.平面分割问题分析&#xff1a; 3.汉诺塔问题分析&#xff1a; 4.卡特兰数分析&#xff1a; 5.第二类斯特林数总结&#xff1a; 1.斐波那契数列 分析&#xff1a; 斐波那契数列又称兔子数列&#xff0c;其原理来源于兔子…

dangerousRemoteUrlIpcAccess

问题描述&#xff1a; 在使用Tauri窗口加载外部链接时&#xff0c;需要也能继续使用Tauri API与Rust交互。按照官方发布通告中的代码添加配置&#xff1a; "security": {"dangerousRemoteUrlIpcAccess": [ { "windows": ["main", &qu…