DSL-Flutter : 告别嵌套地狱,用缩进书写 Flutter!

文章目录

背景

在用 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"
  }
}


相关推荐
ZZZMMM.zip15 小时前
基于鸿蒙HarmonyOS NEXT开发AI股票分析应用:智能投资新体验与鸿蒙Flutter框架跨端实践
人工智能·flutter·华为·harmonyos·鸿蒙
韩曙亮21 小时前
【Flutter】Flutter 进程保活 ③ ( 屏幕常亮设置 | Flutter 禁止息屏、关闭自动锁屏、屏蔽系统屏保 )
android·flutter·ios·屏幕常亮·禁止息屏
心中有国也有家21 小时前
AtomGit Flutter 鸿蒙客户端:情绪日记的时间线实现
学习·flutter·华为·harmonyos
西西学代码1 天前
Flutter---Container
flutter
西西学代码1 天前
Flutter---底部导航栏(2)
开发语言·javascript·flutter
浮江雾1 天前
Flutter第二节----Dart中的常量与变量
开发语言·前端·javascript·flutter·入门
yuanlaile1 天前
前端转 Flutter 踩坑实录:一套可落地 Flutter 鸿蒙 App 完整学习路线,建议收藏!
flutter·harmonyos·flutter开发鸿蒙·跨平台开发app·flutter深入学习
ZZZMMM.zip1 天前
基于鸿蒙HarmonyOS NEXT开发AI天气助手应用:智能出行新体验与鸿蒙Flutter框架跨端实践
人工智能·flutter·华为·harmonyos·鸿蒙系统
心中有国也有家1 天前
AtomGit Flutter 鸿蒙客户端:一键记录情绪的极致体验
学习·flutter·华为·harmonyos