文章目录背景项目地址本地安装 CLI 命令示例项目获取依赖创建 DSL 文件运行代码生成使用生成的代码效果展示在这里插入图片描述DSL 语法指南传统写法 - 嵌套地狱DSL 写法 - 清晰简洁基础的 Widget带参数的 Widget自定义 Widget条件渲染高级特性-模板片段 配置VS Code 配置背景在用 flutter 编程时, 对于嵌套括号烦的一比, 复制挪动都不方便, 老是差一个括号啥的, 给个直观的例子:// ❌ 传统写法 - 嵌套地狱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 编程:// ✅ DSL 写法 - 清晰简洁ScaffoldappBar:AppBartitle:Text首页backgroundColor:Colors.blue body:Columnchildren:[Text标题Rowchildren:[IconIcons.starText评分 4.8Columnchildren:[ElevatedButtononPressed:(){}child:Text点击TextHello Worldstyle:TextStylefontSize:20fontWeight:FontWeight.bold color:Colors.blueCardchild:Text内容]]]项目地址github: https://github.com/myopz/dsl-flutter.gitgitee: https://gitee.com/myopz/dsl-flutter.git本地安装因没有谷歌账号, 与国内网络原因, 没办法推送到pub.dev仓库, 因此不能像其他依赖一样引用 (如果有条件的朋友可以帮忙推上去), 因此可以把dsl-flutter项目克隆到本地, 使用--source path指定本地路径安装dart pub global activate--sourcepath /你的完整路径/dsl_flutter或者如果你当前就在 dsl_flutter 项目目录下dart pub global activate--sourcepath.执行dart pub global list查看安装结果:$ dart pub global list dsl_flutter1.0.0 at path/path/to/yourpath/dsl-flutter CLI 命令安装后就可以在控制台中执行 dsl_flutter 命令了, 主要命令如下:dsl_flutter--help# 显示帮助dsl_flutter setup# 配置开发环境dsl_flutter init# 初始化项目dsl_flutterwatch# 监听文件并自动转换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_flutterpubspec.yaml...dev_dependencies:build_runner:^2.4.0dsl_flutter:# 用你本地 dsl-flutter 的路径path:/你的完整路径/dsl-flutterbuild.yamltargets:$default:builders:dsl_flutter|dslBuilder:enabled:truegenerate_for:-lib/**/*.dui获取依赖flutter pub get创建 DSL 文件DSL 文件是*.dui后缀, 用 DSL 语法(类似 yaml)来写页面, 创建lib/pages/home.dui// example/lib/pages/home.duiimportpackage: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)), ], ), ) )// 页面 classHomePageextendsStatefulWidget{constHomePage({super.key});overrideStateHomePagecreateState()_HomePageState();// ← 加 ;}class_HomePageStateextendsStateHomePage{int _counter0;finalListString_items[Item 1,Item 2,Item 3];void_increment(){setState((){_counter;});}void_addItem(){setState((){_items.add(Item${_items.length1});});}overrideWidgetbuild(BuildContextcontext){returnScaffoldappBar:AppBartitle:TextDSL Flutter DemobackgroundColor:Theme.of(context).colorScheme.inversePrimary elevation:0actions:[IconButtonicon:IconIcons.refresh onPressed:(){setState((){_items.clear();// ← 加 ;_items.addAll([Item 1,Item 2,Item 3]);// ← 加 ;});}]body:Columnchildren:[// 计数器区域Containerpadding:EdgeInsets.all(16)color:Theme.of(context).colorScheme.primaryContainer child:RowmainAxisAlignment:MainAxisAlignment.spaceAround children:[Columnchildren:[Text点击次数style:TextStyle(fontSize:14,color:Colors.grey)Text$_counterstyle:Theme.of(context).textTheme.headlineMedium]ElevatedButtononPressed:_increment child:Text增加TextButtononPressed:_addItem child:Text添加项目]// 列表区域Expandedchild:ListView.builder itemCount:_items.length itemBuilder:(context,index){return_buildItem(_items[index],index);// ← 加 ;}// 底部卡片使用片段UserCardname:张三email:zhangsanexample.comavatar:https://i.pravatar.cc/150?img1]floatingActionButton:FloatingActionButtononPressed:_increment child:IconIcons.add;}Widget_buildItem(Stringtitle,int index){returnCardmargin:EdgeInsets.symmetric(horizontal:16,vertical:4)child:ListTileleading:CircleAvatarchild:Text${index1}title:Texttitle trailing:IconButtonicon:IconIcons.delete onPressed:(){setState((){_items.removeAt(index);// ← 加 ;});}onTap:(){ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:Text(点击了:$title)));};}}运行代码生成执行下面命令, *.dui 文件就会转变成同名的*.dsl.dart标准的 dart 文件, 会把括号补全上, 这样用于后续代码的引用, 我们只要维护*.dui文件就行了,*.dsl.dart是生成的, 每次生成都会被覆盖掉flutter pub run build_runner build --delete-conflicting-outputs或者在本地安装 dsl_flutter 后 (dart pub global activate --source path /你的完整路径/dsl_flutter), 可以用更简洁命令:dsl_flutter build # 一次性构建所有文件使用生成的代码importpages/home.dsl.dart;// 直接使用HomePage()效果展示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(内容)),],),],),],),)DSL 写法 - 清晰简洁采用缩进表达 Widget 树结构清晰明了。// ✅ DSL 写法 - 清晰简洁ScaffoldappBar:AppBartitle:Text首页backgroundColor:Colors.blue body:Columnchildren:[Text标题Rowchildren:[IconIcons.starText评分 4.8Columnchildren:[ElevatedButtononPressed:(){}child:Text点击TextHello Worldstyle:TextStylefontSize:20fontWeight:FontWeight.bold color:Colors.blueCardchild:Text内容]]]基础的 WidgetContainerpadding:EdgeInsets.all(16)child:TextHello带参数的 WidgetTextHello Worldstyle:TextStylefontSize:20fontWeight:FontWeight.bold color:Colors.blue自定义 WidgetMyCustomWidgettitle:自定义onTap:_handleTap child:Text内容条件渲染Columnchildren:[if(isLoggedIn){Text欢迎回来}else{LoginButton()}for(variteminitems)ListTiletitle:Textitem.name onTap:()_handleTap(item)]高级特性-模板片段定义片段Fragment(UserCard,[name,email,avatar], Card( child: Column( children: [ CircleAvatar(backgroundImage: NetworkImage(avatar)), Text(name), Text(email), ], ), ) )前缀调用无括号,但要有UserCard// ← 无括号但要有name:张三email:zhangsanexample.comavatar:https://example.com/avatar.jpg 配置VS Code 配置dsl_flutter setup会自动创建以下配置, 没有就自己加上, 这样好看一点// .vscode/settings.json{[dart]:{editor.formatOnSave:true},[dui]:{editor.formatOnSave:false,editor.tabSize:2,editor.insertSpaces:true},files.associations:{*.dui:dart}}