每日一博 | Flutter 如何将代码显示到界面上
前言
如何优雅的将项目中的代码,亦或是你的demo代码展示到界面上?本文对使用简单、便于维护且通用的解决方案,进行相关的对比和探究
为了节省大家的时间,把最终解决方案的相关接入和用法写在前面
预览代码
快速开始
dependencies: code_preview: ^0.1.5
- 用法:CodePreview,提供需要预览的className,可自动匹配该类对应的代码文件
- 本来想把写法简化成传入对象,但是因为一些原因无奈放弃,改成了
className
- 具体可以参考下面
Flutter Web中的问题
模块的说明
- 本来想把写法简化成传入对象,但是因为一些原因无奈放弃,改成了
import 'package:code_preview/code_preview.dart'; import 'package:flutter/material.dart'; class Test extends StatelessWidget { const Test({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const CodePreview(className: 'Test'); } }
- 使用效果:flutter_smart_dialog
配置代码文件
因为原理是遍历资源文件,所以必须将需要展示的代码文件或者其文件夹路径,定义在assets下,这步操作,为大家提供了一个自动化的插件解决
强烈建议需要展示到界面的代码,都放在统一的文件夹里管理
- 展示界面的代码需要在pugspec.yaml中的assets定义
如果代码预览的文件夹,分级复杂,每次都需要定义路径实在麻烦
提供一个插件:Flutter Code Helper
- 安装:Plugins中搜索
Flutter Code Helper
- pugspec.yaml中定义下需要自动生成文件夹的路径,文件夹随便套娃,会自动帮你递归在assets下生成
- 不需要自动生成,可:不写该配置,或者配置空数组(auto_folder: [])
code_helper: auto_folder: [ "assets/", "lib/widgets/" ]
说明下:上面的插件是基于RayC的FlutterAssetsGenerator插件项目改的
- 看了下RayC的插件代码和相关功能,和我预想的上面功能实现有一定出入,改动起来变动较大
- 想试下插件项目的各种新配置,直接拉到最新
- 后期如果想到需要什么功能,方便随时添加
所以没向其插件里面提pr,就单独新开了个插件项目
高级使用
主题
提供俩种代码样式主题
- 日间模式
CodePreview.config = CodePreviewConfig(codeTheme: CodeTheme.light);
- 夜间模式
CodePreview.config = CodePreviewConfig(codeTheme: CodeTheme.dark);
注释解析
- 你可以使用如下的格式,在类上添加注释
/// @title: /// - test title one /// - test title two /// @content: test content /// @description: test description class OneWidget extends StatelessWidget { const OneWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const Placeholder(); } }
- 然后可以从
customBuilder
的回调获取param参数,param中拥有parseParam参数- 获取取得上面注释的数据:param.parseParam['title']或者param.parseParam['***']
- 获取的value的类型是List<String>,可兼容多行value的类型
customBuilder
的用法codeWidget
内置的代码预览布局,如果你想定义自己预览代码的布局,那就可以不使用codeWidget
- 一般来说,可以根据注释获取的数据,结合
codeWidget
嵌套来自定义符合要求的布局 param
中含有多个有用内容,可自行查看
import 'package:code_preview/code_preview.dart'; import 'package:flutter/material.dart'; class Test extends StatelessWidget { const Test({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return CodePreview( className: 'OneWidget', customBuilder: (Widget codeWidget, CustomParam? param) { debugPrint(param?.parseParam['title'].toString()); debugPrint(param?.parseParam['content'].toString()); debugPrint(param?.parseParam['description'].toString()); return codeWidget; }, ); } }
- 目前内部预览的布局,会自动去掉类上的注释,如果想保留注释,可自行匹配下
CodePreview.config = CodePreviewConfig(removeParseComment: false);
几种代码预览方案
FlutterUnit方案
FlutterUnit项目也是自带代码预览方案,这套方案是比较特殊方案
- 大概看了下,整个FlutterUnit的数据都是基于
flutter.db
,该文件里面就有相关demo的文本信息 - 所有的demo也是单独存在一个叫
widgets
的项目中 - 所以大概可以猜测出
- 应该会有个db的辅助工具,会去扫描
widgets
的项目中的demo代码 - 将他们的文本信息都扫描出来,然后解析上面的注释等相关信息,分类存储到数据库中,最后生成db文件
- 应该会有个db的辅助工具,会去扫描
- 映射表,宿主可以通过db中的组件类名,从这里拿到demo效果实例
总结
整套流程看下来,实现起来的工作量还是有点大的
- db辅助工具的编写
- 文本注释相关解析规则
- 如何便捷的维护db文件(辅助工具是否支持,生成后自动覆盖宿主db文件)
- 不同平台db文件的读取和相关适配
优点
- 因为扫描工具不依赖Flutter相关库,预览方案可以快速的移植到其它编程语言(compose,SwiftUI等)
- 具备高度自定义,因为是完全独立的第三方扫描工具,可以随性所欲的定制化
缺点
- 最明显的缺点,应该就是稍微改下demo代码,就需要三方工具重新生成db文件(如果三方工具实现的是cli工具,可以将扫描生成命令和push等命令集成一起,应该可以比较好的避免该问题)
build_runner方案
build_runner是个强大代码自动生成工具,根据ast语法树+自定义注解信息,可以生成很多强大的附属代码信息,例如 json_serializable
等库
所以,也能利用这点自定义类注解,获取到对应的整个类的代码信息,在对应附属的xx.g.dart
文件中,将获取的代码内容转换成字符串,然后直接将xx.g.dart
文件的代码字符串信息,展示到界面就行了
优点
- 可以通过生成命令,全自动的生成代码,甚至将整个预览demo的映射表都可以自动配置完成
- 可以规范的通过注解配置多个参数
缺点
- 因为
build_runner
需要解析整个ast语法树,一旦项目很大之后,解析生成的时间会非常非常的长! - 因为现在很多的这类库都是依赖
build_runner
,所以跑自动生成命令,会导致巨多xx.g.dart
文件被改动,极大的增加cr工作量
资源文件方案
这应该最常用的一种方案
- 在
pubspec.yaml
中的assets
中定义下我们代码文件路径
flutter: assets: - lib/widgets/show/
- 然后用loadString获取文件内容
final code = await rootBundle.loadString('lib/widgets/show/custome_dialog_animation.dart');
优点
- 侵入性非常低,不会像
build_runnner
方案那样影响到其它模块 - 便于维护,如果demo预览代码被改变了,打包的时候,资源文件也会生成对应改变后的代码文件
缺点
- 使用麻烦,使用的时候需要传入具体的文件路径,才能找到想要的代码资源文件
- 需要反复的在
pubspec.yaml
中的assets
里面定义文件路径
资源文件方案优化
上面的三种方案各有优缺点,明确当前的诉求
-
目前是想写个简单的,通用的,仅在Flutter中实现代码预览方案
-
要求使用简单,高效
-
维护简单,多人开发的时候不会有很大成本
FlutterUnit方案:实现起来成本较大,且多人开发对单个db文件的维护很可能会有点问题,例如:更新代码的时候,db文件忘记更新
build_runner方案:生成时间是个问题,还有很对其他类型xx.g.dart
文件产生影响也比较麻烦
资源文件方案:整体是符合预期的,但是使用时候,需要传入路径和pubspec.yaml
中反复定义文件路径,这是俩个很大痛点
结合实现成本和诉求,选择资源文件方案
,下面对其痛点进行优化
使用优化
Flutter的编译产物中,有个相当有用的文件:AssetManifest.json
AssetManifest.json文件里面,有所有的资源文件的路径,然后就简单了,我们只需要读取该文件内容
final manifestContent = await rootBundle.loadString('AssetManifest.json');
获取到所有的路径之后,再结合传入的类名,读取所有路径的文件内容,然后和传入的类名做正则匹配就行了
稍微优化
- 将传入的类名,转换为下划线名称和所有路径名称做匹配,如果能匹配上,再进行内容匹配,匹配成功后就返回该文件的代码内容
- 如果上述匹配失败,就进行兜底的全量匹配
优化前
import 'package:code_preview/code_preview.dart'; import 'package:flutter/material.dart'; class Test extends StatelessWidget { const Test({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const CodePreview(path: 'lib/widgets/show/custome_dialog_animation.dart'); } }
优化后
import 'package:code_preview/code_preview.dart'; import 'package:flutter/material.dart'; class Test extends StatelessWidget { const Test({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const CodePreview(className: 'CustomDialogAnimation'); } }
- 一般来说,我是统一配置预览demo和className,这样比较好对照
路径定义优化
本来是想在pubspec.yaml
的assets
里面直接写通配符定义全路径,然后悲剧了,它不支持这种写法
flutter: assets: - lib/widgets/**/*.dart
GG,只能想其他办法了,想了很多方法都不行,只能从外部入手,用idea插件的形式,实现自动化扫描生成路径
- 安装:Plugins中搜索
Flutter Code Helper
- pugspec.yaml中定义下需要自动生成文件夹的目录,文件夹随便套娃,会自动帮你递归在assets下生成
- 不需要自动生成,可:不写该配置,或者配置空数组(auto_folder: [])
code_helper: auto_folder: [ "assets/", "lib/widgets/" ]
Flutter Web中的问题
魔幻的runtimeType
flutter web的release模式中
- dart2js 会压缩 JS,这样会使得类型名被改变
- 例如:dart中的
TestWidgetFunction
类的runtimeType,可能会变成minified:Ah
,而不是TestWidgetFunction
!
为啥需要压缩呢?压缩名称可以使得编译器将 JavaScript体积缩小 3 倍+;精确等效语义和性能/代码大小之间的权衡,Dart明显是选择了后者
这种情况只会在Flutter Web的release模式下发生,其他平台和Flutter web的Debug | Profile模式都不会有这种问题;所以说Xxx.runtimeType.toString
,并不一定会得到预期内的数据。。。
具体讨论可参考
解决思路
- 将压缩类型
minified:Ah
恢复成Test
- 将获取的
Test
字符串使用相同算法压缩成minified:Ah
如有知道如何实现的,务必告诉鄙人
下面从压缩级别调整的角度,探究是否可解决该问题
dart2js压缩说明
注:flutter build web默认的是O4优化级别
- O0: 禁用许多优化。
- O1: 启用默认优化(仅是dart2js该命令的默认级别)
- O2: 在O1优化基础上,尊重语言语义且对所有程序安全的其他优化(例如缩小)
- 备注:使用-O2,使用开发JavaScript编译器编译时,类型的字符串表示不再与Dart VM中的字符串表示相同
- O3: 在O2优化基础上,并省略隐式类型检查。
- 注意:省略类型检查可能会导致应用程序因类型错误而崩溃
- O4: 在O3优化基础上,启用更积极的优化
- 注意:O4优化容易受到输入数据变化的影响,在依赖O4之前,需测试用户输入中的边缘情况
下面是flutter新建项目,未做任何改动,不同压缩级别的js产物体积
# main.dart.js: 7.379MB flutter build web --dart2js-optimization O0 # main.dart.js: 5.073MB flutter build web --dart2js-optimization O1 # main.dart.js: 1.776MB flutter build web --dart2js-optimization O2 # main.dart.js: 1.716MB flutter build web --dart2js-optimization O3 # main.dart.js: 1.687MB flutter build web --dart2js-optimization O4
总结
- 预期用法
- 为什么想使用对象?因为当对象名称改变时,对应使用的地方,可以便捷观察到需要改变
- 可以使用传入的对象实例,在内部使用runtimeType获取类型名,再进行相关匹配
CodePreview(code: Test());
但是
综上可知,使用flutter build web --dart2js-optimization O1
编译的flutter web release产物,能够使得runtimeType的语义和Dart VM中字符串保持一致
但是该压缩级别下的,js体积过于夸张,务必会对加载速度产生极大影响,可想而知,在复杂项目中的体积增涨肯定更加离谱
对于想要用法更加简单,使用低级别压缩命令打包的想法需要舍弃
- 用法不得已做妥协
CodePreview(className: "Test");
这是个让我非常纠结的思路历程
最后
到这里也结束了,自我感觉,对大家应该能有一些帮助
一般来说,大部分团队,都会有个自己的内部组件库,因为Flutter强大的跨平台特性,所以就能很轻松的发布到web平台,可以方便的体验各种组件的效果,结合文章中的代码预览方案,就可以更加快速的上手各种组件用法了~
好了,下次再见了,靓仔们!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Spring Tools 4.18.1 发布,Spring 开发工具
Spring Tools 4 是由 Spring 团队打造的 Spring 开发工具,从零开始构建,融合了现代技术和开发者工具架构。它在单独的进程中运行,从构建之初就考虑到了性能问题,并且支持最新的 Spring 技术,为开发基于 Spring 的企业应用提供世界级支持。同时,全新版本的 Spring Tools 与 IDE 无关,可在各种编码环境中使用,支持 Eclipse、Visual Studio Code 与 Theia。 最新发布的版本是4.18.1。此版本修复了部分 bug,以及改进功能: 支持在检查 @Value 注解时导航到 Spring 属性文件 在属性导航功能中支持@ConditionalOnProperty 在为查询方法应用内容辅助建议时自动为类型添加导入 [spring-data-support] 考虑域类型的类型层次结构 [spring-data-support] 从提案列表中删除纯 findBy 方法补全 修复运行 Spring Boot 3 应用程序时缺少 live hover 的问题 修复 application.properties/yaml 自动补全...
- 下一篇
AMD 将使用开源固件处理库 openSIL 全面取代 AGESA
在布拉格的 OCP 开放计算机项目峰会演讲期间,AMD 方面表示将使用开源的 AMD openSIL 芯片初始化库取代原有的 AGESA 库,简化服务器和消费者平台的 UEFI 固件创建流程。该改动将在 2026 年左右应用到所有 AMD 处理器 —— 包括服务器和消费级系列。 AMD 通用封装软件架构(AMD Generic Encapsulated Software Architecture,缩写 AGESA)是 AMD64 架构的主板上初始化系统设备的启动协议。 AMD 主板 BIOS 中的 AGESA 软件负责 cpu 核心、内存和 HyperTransport 控制器的初始化, AGESA 会经常更新以支持新硬件并解决错误,这导致它很容易被攻击。 今年 3 月份 AMD 推出了新的 AMD openSIL ,作为 AMD 新固件计划的一部分。AMDopenSIL 将AMD 芯片初始化固件的开发、架构和验证过程全部开源,相比 AGESA 更轻量级、简单、透明和安全,且可以轻松扩展。 AMDopenSIL 并不是要取代统一可扩展固件接口 (UEFI),而是与其他主机固件集成,例如...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- 2048小游戏-低调大师作品
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS关闭SELinux安全模块