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
<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)容:
- 布局規(guī)范:定義頁面布局和排版的規(guī)則,包括網(wǎng)格系統(tǒng)撮抓、排版間距妇斤、基準(zhǔn)線等。
- 樣式規(guī)范:定義顏色丹拯、字體站超、圖標(biāo)、按鈕等基本樣式的使用和規(guī)范乖酬,包括設(shè)計(jì)風(fēng)格死相、調(diào)色板、字體類型咬像、字號(hào)算撮、行高等。
- 組件規(guī)范:定義前端組件的設(shè)計(jì)和開發(fā)規(guī)則县昂,包括組件的命名肮柜、結(jié)構(gòu)、樣式倒彰、交互审洞、狀態(tài)管理等。
- 圖片和媒體規(guī)范:定義圖片和媒體資源的格式待讳、尺寸芒澜、優(yōu)化和加載等規(guī)則,以提高頁面性能和用戶體驗(yàn)创淡。
- 響應(yīng)式設(shè)計(jì)規(guī)范:定義響應(yīng)式設(shè)計(jì)的原則和規(guī)則痴晦,包括頁面布局、元素大小和位置琳彩、字體大小誊酌、圖片和媒體資源的顯示等部凑。
- 可訪問性規(guī)范:定義網(wǎng)站或應(yīng)用程序的可訪問性規(guī)則,包括鍵盤導(dǎo)航术辐、語義標(biāo)記砚尽、焦點(diǎn)指示、顏色對(duì)比度等辉词。
- 性能規(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ā)工作有以下好處:
- 提高開發(fā)效率:Flutter Widgetbook 可以讓前端開發(fā)人員在不需要啟動(dòng)完整應(yīng)用程序的情況下構(gòu)建和演示 Flutter 組件凸丸,快速迭代和測(cè)試組件的不同狀態(tài)和屬性,從而提高開發(fā)效率屎慢。
- 促進(jìn)組件復(fù)用:Flutter Widgetbook 可以讓前端開發(fā)人員在單獨(dú)的應(yīng)用程序中構(gòu)建和演示組件瞭稼,從而促進(jìn)組件的復(fù)用和共享,減少代碼重復(fù)和維護(hù)成本腻惠。
- 保持代碼一致性:Flutter Widgetbook 可以作為一個(gè)組件庫(kù)來使用,定義前端組件的設(shè)計(jì)和開發(fā)規(guī)則悔雹,從而保持代碼的一致性、可維護(hù)性绝页、可擴(kuò)展性和可重用性。
- 提高跨團(tuán)隊(duì)協(xié)作:Flutter Widgetbook 可以讓前端開發(fā)人員共享他們的組件庫(kù)寂恬,并讓其他人輕松地查看和測(cè)試他們的組件续誉,從而促進(jìn)跨團(tuán)隊(duì)協(xié)作和知識(shí)共享初肉。
- 提高用戶體驗(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ā)布