Flutter 中使用 Widgetbook 管理你的組件

Flutter 中使用 Widgetbook 管理你的組件

<img src="https://upload-images.jianshu.io/upload_images/4489342-250634832a1d49a5.png" style="width:90%;" />

前言

Flutter 界面開發(fā)中我們有幾個(gè)痛點(diǎn) :

  • 與設(shè)計(jì)師協(xié)作復(fù)用一套設(shè)計(jì)規(guī)范(figma)
  • 可視化的管理你的組件代碼(基礎(chǔ)組件、業(yè)務(wù)組件)
  • 不同設(shè)備尺寸測(cè)試你的組件
  • 實(shí)時(shí)修改你的測(cè)試組件參數(shù)

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

視頻

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

參考

Widgetbook

https://www.widgetbook.io/

<img src="https://upload-images.jianshu.io/upload_images/4489342-63efff66e8aa18ea.png" style="width:80%;" />

Flutter Widgetbook 是一個(gè)用于構(gòu)建和交互 Flutter 組件庫(kù)的工具耻涛。它允許您在單獨(dú)的應(yīng)用程序中構(gòu)建和演示您的 Flutter 組件,以便您可以在不運(yùn)行完整應(yīng)用程序的情況下進(jìn)行快速迭代和測(cè)試。

使用 Flutter Widgetbook掏湾,您可以:

  • 構(gòu)建和演示單個(gè)組件蛤育,而無需在完整應(yīng)用程序中運(yùn)行它們驱闷。
  • 以交互方式測(cè)試組件的不同狀態(tài)和屬性,以及不同平臺(tái)和設(shè)備的外觀和行為诚欠。
  • 共享您的組件庫(kù),并讓其他人輕松地查看和測(cè)試您的組件。

您可以在 Flutter 應(yīng)用程序中使用 Widgetbook轰绵,也可以將其作為獨(dú)立應(yīng)用程序使用粉寞。在 Widgetbook 中,您可以編寫 Dart 代碼來定義組件和演示它們的用法左腔。您可以使用 Flutter 提供的任何組件和庫(kù)唧垦,并使用 Widgetbook 提供的一些工具來組織和顯示您的組件。

設(shè)計(jì)規(guī)范

前端設(shè)計(jì)規(guī)范是一組定義前端設(shè)計(jì)和開發(fā)過程中所需遵守的準(zhǔn)則和規(guī)則的規(guī)范液样。它們旨在確保前端代碼的一致性振亮、可維護(hù)性、可擴(kuò)展性和可重用性鞭莽,并促進(jìn)團(tuán)隊(duì)間的協(xié)作坊秸。

前端設(shè)計(jì)規(guī)范主要包括以下內(nèi)容:

  1. 布局規(guī)范:定義頁面布局和排版的規(guī)則,包括網(wǎng)格系統(tǒng)撮抓、排版間距妇斤、基準(zhǔn)線等。
  2. 樣式規(guī)范:定義顏色丹拯、字體站超、圖標(biāo)、按鈕等基本樣式的使用和規(guī)范乖酬,包括設(shè)計(jì)風(fēng)格死相、調(diào)色板、字體類型咬像、字號(hào)算撮、行高等。
  3. 組件規(guī)范:定義前端組件的設(shè)計(jì)和開發(fā)規(guī)則县昂,包括組件的命名肮柜、結(jié)構(gòu)、樣式倒彰、交互审洞、狀態(tài)管理等。
  4. 圖片和媒體規(guī)范:定義圖片和媒體資源的格式待讳、尺寸芒澜、優(yōu)化和加載等規(guī)則,以提高頁面性能和用戶體驗(yàn)创淡。
  5. 響應(yīng)式設(shè)計(jì)規(guī)范:定義響應(yīng)式設(shè)計(jì)的原則和規(guī)則痴晦,包括頁面布局、元素大小和位置琳彩、字體大小誊酌、圖片和媒體資源的顯示等部凑。
  6. 可訪問性規(guī)范:定義網(wǎng)站或應(yīng)用程序的可訪問性規(guī)則,包括鍵盤導(dǎo)航术辐、語義標(biāo)記砚尽、焦點(diǎn)指示、顏色對(duì)比度等辉词。
  7. 性能規(guī)范:定義優(yōu)化前端性能的規(guī)則必孤,包括代碼壓縮、緩存控制瑞躺、資源加載敷搪、代碼分割等。

前端設(shè)計(jì)規(guī)范可以通過文檔幢哨、工具赡勘、模板和代碼庫(kù)等方式來實(shí)現(xiàn)和維護(hù)。它們可以幫助團(tuán)隊(duì)提高開發(fā)效率捞镰、降低維護(hù)成本闸与、保持代碼質(zhì)量和可維護(hù)性,并促進(jìn)設(shè)計(jì)和開發(fā)間的協(xié)作岸售。

Ant Design 設(shè)計(jì)規(guī)范參考

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

<img src="https://upload-images.jianshu.io/upload_images/4489342-ee62d541d19db10c.png" style="width:80%;" />

代碼

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 節(jié)點(diǎn)下面

編寫調(diào)試界面

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: [
      ],
    );
  }
}

運(yùn)行

<img src="https://upload-images.jianshu.io/upload_images/4489342-42fa91b60162063c.png" style="width:80%;" />

加入組件

準(zhǔn)備兩個(gè)組件代碼

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: "藍(lán)色色背景",
              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 - 業(yè)務(wù)組件

  WidgetbookCategory buildWidgetbookCategory2() {
    return WidgetbookCategory(
      name: '業(yè)務(wù)組件',
      children: [
        WidgetbookComponent(
          name: '系統(tǒng)常用',
          useCases: [
            WidgetbookUseCase(
              name: "登錄界面",
              builder: (BuildContext context) {
                return LoginForm(
                  title: context.knobs.text(
                    label: '標(biāo)題 [title]',
                    initialValue: '用戶登錄',
                  ),
                );
              },
            ),
            WidgetbookUseCase(
              name: "注冊(cè)界面",
              builder: (BuildContext context) {
                return LoginForm(
                  title: context.knobs.text(
                    label: '標(biāo)題 [title]',
                    initialValue: '用戶注冊(cè)',
                  ),
                );
              },
            ),
          ],
        ),
      ],
    );
  }

通過 knobs 的方式設(shè)置調(diào)試參數(shù)

其它參數(shù)類型

<img src="https://upload-images.jianshu.io/upload_images/4489342-af15d32b6049eb89.png" style="width:80%;" />

build 函數(shù)

  @override
  Widget build(BuildContext context) {
    return Widgetbook.material(
      addons: [
      ],
      directories: [
        // 基礎(chǔ)組件
        buildWidgetbookCategory(),

        // 業(yè)務(wù)組件
        buildWidgetbookCategory2(),
      ],
    );
  }

輸出

<img src="https://upload-images.jianshu.io/upload_images/4489342-5d60737a95ec8de1.png" style="width:80%;" />

設(shè)置選項(xiàng)

主題

  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.0, 1.25, 1.5, 1.75, 2]));
  }

build 函數(shù)

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

        // 字體大小
        buildTextScaleAddon(),
      ],
      directories: [
        // 基礎(chǔ)組件
        buildWidgetbookCategory(),

        // 業(yè)務(wù)組件
        buildWidgetbookCategory2(),
      ],
    );
  }

輸出

<img src="https://upload-images.jianshu.io/upload_images/4489342-a71f6f47164fddb6.png" style="width:80%;" />

其它 addon

<img src="https://upload-images.jianshu.io/upload_images/4489342-a2c65bd5def0434c.png" style="width:50%;" />

小結(jié)

Flutter Widgetbook 對(duì)前端開發(fā)工作有以下好處:

  1. 提高開發(fā)效率:Flutter Widgetbook 可以讓前端開發(fā)人員在不需要啟動(dòng)完整應(yīng)用程序的情況下構(gòu)建和演示 Flutter 組件凸丸,快速迭代和測(cè)試組件的不同狀態(tài)和屬性,從而提高開發(fā)效率屎慢。
  2. 促進(jìn)組件復(fù)用:Flutter Widgetbook 可以讓前端開發(fā)人員在單獨(dú)的應(yīng)用程序中構(gòu)建和演示組件瞭稼,從而促進(jìn)組件的復(fù)用和共享,減少代碼重復(fù)和維護(hù)成本腻惠。
  3. 保持代碼一致性:Flutter Widgetbook 可以作為一個(gè)組件庫(kù)來使用,定義前端組件的設(shè)計(jì)和開發(fā)規(guī)則悔雹,從而保持代碼的一致性、可維護(hù)性绝页、可擴(kuò)展性和可重用性。
  4. 提高跨團(tuán)隊(duì)協(xié)作:Flutter Widgetbook 可以讓前端開發(fā)人員共享他們的組件庫(kù)寂恬,并讓其他人輕松地查看和測(cè)試他們的組件续誉,從而促進(jìn)跨團(tuán)隊(duì)協(xié)作和知識(shí)共享初肉。
  5. 提高用戶體驗(yàn):Flutter Widgetbook 可以讓前端開發(fā)人員在不同平臺(tái)和設(shè)備上測(cè)試組件的外觀和行為,以確保它們能夠提供一致的用戶體驗(yàn)臼隔,從而提高用戶體驗(yàn)和用戶滿意度。

總之寄狼,F(xiàn)lutter Widgetbook 是一個(gè)有用的工具,可以幫助前端開發(fā)人員更輕松地構(gòu)建和測(cè)試 Flutter 組件泊愧,從而提高開發(fā)效率盛正、保持代碼質(zhì)量和可維護(hù)性,并促進(jìn)跨團(tuán)隊(duì)協(xié)作和知識(shí)共享痰滋,最終提高用戶體驗(yàn)和用戶滿意度续崖。


? 貓哥

ducafecat.com

end

本文由mdnice多平臺(tái)發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市聪富,隨后出現(xiàn)的幾起案子著蟹,更是在濱河造成了極大的恐慌,老刑警劉巖奸披,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異阵面,居然都是意外死亡洪鸭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門置鼻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜓竹,“玉大人储藐,你說我怎么就攤上這事嘶是。” “怎么了辖源?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵授帕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我彤路,道長(zhǎng)芥映,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任坞嘀,我火速辦了婚禮惊来,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘矢渊。我一直安慰自己枉证,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布毡鉴。 她就那樣靜靜地躺著秒赤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陈瘦。 梳的紋絲不亂的頭發(fā)上崎弃,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音线婚,去河邊找鬼盆均。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泪姨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播诀黍,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼眯勾,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼婆誓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起洋幻,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤文留,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后厂庇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡替蛉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年躲查,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了译柏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡典唇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恨胚,到底是詐尸還是另有隱情炎咖,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布升熊,位于F島的核電站绸栅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏粹胯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一渊抽、第九天 我趴在偏房一處隱蔽的房頂上張望议忽。 院中可真熱鬧,春花似錦愤估、人聲如沸速址。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽并炮。三九已至,卻和暖如春逃魄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邪锌。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赂蕴,地道東北人舶胀。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓碧注,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親轩端。 傳聞我的和親對(duì)象是個(gè)殘疾皇子逝变,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容