文章目录
背景
在用 flutter 编程时, 对于嵌套括号烦的一比, 复制挪动都不方便, 老是差一个括号啥的, 给个直观的例子:
dart
// ❌ 传统写法 - 嵌套地狱
Scaffold(
appBar: AppBar(title: Text('首页'), backgroundColor: Colors.blue),
body: Column(
children: [
Text('标题'),
Row(
children: [
...
Column(
children: [
...
Text(
'Hello World',
style: TextStyle(
...
),
),
Card(child: Text('内容')),
],
),
],
),
],
),
)
结尾一堆括号, 又不美观, 还影响阅读体验, 而且挪动一下都容易出错, 因此我写了一个开源项目 dsl-flutter, 告别嵌套地狱,用缩进书写 Flutter!
示例如下, 像极了 yaml 编程:
dart
// ✅ DSL 写法 - 清晰简洁
Scaffold
appBar: AppBar
title: Text '首页'
backgroundColor: Colors.blue
body: Column
children: [
Text '标题'
Row
children: [
Icon Icons.star
Text '评分 4.8'
Column
children: [
ElevatedButton
onPressed: () => {}
child: Text '点击'
Text 'Hello World'
style: TextStyle
fontSize: 20
fontWeight: FontWeight.bold
color: Colors.blue
Card
child: Text '内容'
]
]
]
项目地址
github: https://github.com/myopz/dsl-flutter.git
gitee: https://gitee.com/myopz/dsl-flutter.git
本地安装
因没有谷歌账号, 与国内网络原因, 没办法推送到 pub.dev 仓库, 因此不能像其他依赖一样引用 (如果有条件的朋友可以帮忙推上去), 因此可以把 dsl-flutter 项目克隆到本地, 使用 --source path 指定本地路径安装
bash
dart pub global activate --source path /你的完整路径/dsl_flutter
或者,如果你当前就在 dsl_flutter 项目目录下:
bash
dart pub global activate --source path .
执行 dart pub global list 查看安装结果:
bash
$ dart pub global list
dsl_flutter 1.0.0 at path "/path/to/yourpath/dsl-flutter"
📚 CLI 命令
安装后就可以在控制台中执行 dsl_flutter 命令了, 主要命令如下:
bash
dsl_flutter --help # 显示帮助
dsl_flutter setup # 配置开发环境
dsl_flutter init # 初始化项目
dsl_flutter watch # 监听文件并自动转换
dsl_flutter build # 一次性构建所有文件
dsl_flutter check # 检查文件格式
示例项目
https://gitee.com/myopz/dsl-flutter/tree/main/example
本文拿example 项目 作演示, 后续你的新项目也是一样的操作, 结构主要如下:
example/
├── lib/
│ ├── main.dart
│ └── pages/
│ ├── home.dui ← DSL文件
│ ├── home.dsl.dart ← 生成文件(自动)
│ ├── test.dui ← DSL文件
│ └── test.dsl.dart ← 生成文件(自动)
├── pubspec.yaml
└── build.yaml
项目 pubspec.yaml 添加 dsl_flutter
pubspec.yaml
yaml
...
dev_dependencies:
build_runner: ^2.4.0
dsl_flutter:
# 用你本地 dsl-flutter 的路径
path: /你的完整路径/dsl-flutter
build.yaml
yaml
targets:
$default:
builders:
dsl_flutter|dslBuilder:
enabled: true
generate_for:
- lib/**/*.dui
获取依赖
bash
flutter pub get
创建 DSL 文件
DSL 文件是 *.dui 后缀, 用 DSL 语法(类似 yaml )来写页面, 创建 lib/pages/home.dui:
dart
// example/lib/pages/home.dui
import 'package:flutter/material.dart';
@Fragment('UserCard', ['name', 'email', 'avatar'], '''
Card(
child: Column(
children: [
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage(avatar),
),
SizedBox(height: 8),
Text(name, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Text(email, style: TextStyle(color: Colors.grey)),
],
),
)
''')
// ============ 页面 ============
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState(); // ← 加 ;
}
class _HomePageState extends State<HomePage> {
int _counter = 0;
final List<String> _items = ['Item 1', 'Item 2', 'Item 3'];
void _increment() {
setState(() {
_counter++;
});
}
void _addItem() {
setState(() {
_items.add('Item ${_items.length + 1}');
});
}
@override
Widget build(BuildContext context) {
return Scaffold
appBar: AppBar
title: Text 'DSL Flutter Demo'
backgroundColor: Theme.of(context).colorScheme.inversePrimary
elevation: 0
actions: [
IconButton
icon: Icon Icons.refresh
onPressed: () {
setState(() {
_items.clear(); // ← 加 ;
_items.addAll(['Item 1', 'Item 2', 'Item 3']); // ← 加 ;
});
}
]
body: Column
children: [
// 计数器区域
Container
padding: EdgeInsets.all(16)
color: Theme.of(context).colorScheme.primaryContainer
child: Row
mainAxisAlignment: MainAxisAlignment.spaceAround
children: [
Column
children: [
Text '点击次数'
style: TextStyle(fontSize: 14, color: Colors.grey)
Text '$_counter'
style: Theme.of(context).textTheme.headlineMedium
]
ElevatedButton
onPressed: _increment
child: Text '增加'
TextButton
onPressed: _addItem
child: Text '添加项目'
]
// 列表区域
Expanded
child: ListView.builder
itemCount: _items.length
itemBuilder: (context, index) {
return _buildItem(_items[index], index); // ← 加 ;
}
// 底部卡片(使用片段)
@UserCard
name: '张三'
email: 'zhangsan@example.com'
avatar: 'https://i.pravatar.cc/150?img=1'
]
floatingActionButton: FloatingActionButton
onPressed: _increment
child: Icon Icons.add;
}
Widget _buildItem(String title, int index) {
return Card
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 4)
child: ListTile
leading: CircleAvatar
child: Text '${index + 1}'
title: Text title
trailing: IconButton
icon: Icon Icons.delete
onPressed: () {
setState(() {
_items.removeAt(index); // ← 加 ;
});
}
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('点击了: $title'))
);
};
}
}
运行代码生成
执行下面命令, *.dui 文件就会转变成同名的*.dsl.dart标准的 dart 文件, 会把括号补全上, 这样用于后续代码的引用, 我们只要维护 *.dui 文件就行了, *.dsl.dart 是生成的, 每次生成都会被覆盖掉
bash
flutter pub run build_runner build --delete-conflicting-outputs
或者在本地安装 dsl_flutter 后 (dart pub global activate --source path /你的完整路径/dsl_flutter), 可以用更简洁命令:
dsl_flutter build # 一次性构建所有文件
使用生成的代码
dart
import 'pages/home.dsl.dart';
// 直接使用
HomePage()
效果展示
DSL 语法指南
传统写法 - 嵌套地狱
有一堆嵌套的括号,导致代码难以阅读和维护。
dart
// ❌ 传统写法 - 嵌套地狱
Scaffold(
appBar: AppBar(title: Text('首页'), backgroundColor: Colors.blue),
body: Column(
children: [
Text('标题'),
Row(
children: [
Icon(Icons.star),
Text('评分 4.8'),
Column(
children: [
ElevatedButton(onPressed: () => {}, child: Text('点击')),
Text(
'Hello World',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
Card(child: Text('内容')),
],
),
],
),
],
),
)
DSL 写法 - 清晰简洁
采用缩进表达 Widget 树结构,清晰明了。
dart
// ✅ DSL 写法 - 清晰简洁
Scaffold
appBar: AppBar
title: Text '首页'
backgroundColor: Colors.blue
body: Column
children: [
Text '标题'
Row
children: [
Icon Icons.star
Text '评分 4.8'
Column
children: [
ElevatedButton
onPressed: () => {}
child: Text '点击'
Text 'Hello World'
style: TextStyle
fontSize: 20
fontWeight: FontWeight.bold
color: Colors.blue
Card
child: Text '内容'
]
]
]
基础的 Widget
dart
Container
padding: EdgeInsets.all(16)
child: Text 'Hello'
带参数的 Widget
dart
Text 'Hello World'
style: TextStyle
fontSize: 20
fontWeight: FontWeight.bold
color: Colors.blue
自定义 Widget
dart
MyCustomWidget
title: '自定义'
onTap: _handleTap
child: Text '内容'
条件渲染
dart
Column
children: [
if (isLoggedIn){
Text '欢迎回来'
} else {
LoginButton()
}
for (var item in items)
ListTile
title: Text item.name
onTap: () => _handleTap(item)
]
高级特性-模板片段
定义片段
dart
@Fragment('UserCard', ['name', 'email', 'avatar'], '''
Card(
child: Column(
children: [
CircleAvatar(backgroundImage: NetworkImage(avatar)),
Text(name),
Text(email),
],
),
)
''')
前缀调用(无括号,但要有@)
dart
@UserCard // ← 无括号!但要有@
name: '张三'
email: 'zhangsan@example.com'
avatar: 'https://example.com/avatar.jpg'
🔧 配置
VS Code 配置
dsl_flutter setup 会自动创建以下配置, 没有就自己加上, 这样好看一点:
json
// .vscode/settings.json
{
"[dart]": {
"editor.formatOnSave": true
},
"[dui]": {
"editor.formatOnSave": false,
"editor.tabSize": 2,
"editor.insertSpaces": true
},
"files.associations": {
"*.dui": "dart"
}
}
