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);

注释解析
- 你可以使用如下的格式,在类上添加注释
- key的前面必须加
@,举例(@title,@xxx) - key与value的之间,必须使用
分号分割,举例(@xxx: xxx) - value如果需要换行,换行的文案前必须加
中划线
- key的前面必须加
/// @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,可兼容多行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平台,可以方便的体验各种组件的效果,结合文章中的代码预览方案,就可以更加快速的上手各种组件用法了~
好了,下次再见了,靓仔们!
Flutter 如何将代码显示到界面上的更多相关文章
- 10_Android中通过HttpUrlConnection访问网络,Handler和多线程使用,读取网络html代码并显示在界面上,ScrollView组件的使用
编写如下项目: 2 编写Android清单文件 <?xml version="1.0" encoding="utf-8"?> <mani ...
- android 使用系统照相程序照相并存储、显示在界面上
大部分业务可以通过调用系统的相机程序来拍照. 界面如下: <?xml version="1.0" encoding="utf-8"?> <Li ...
- app开发历程————Android程序解析服务器端的JSON格式数据,显示在界面上
上一篇文章写的是服务器端利用Servlet 返回JSON字符串,本文主要是利用android客户端访问服务器端链接,解析JSON格式数据,放到相应的位置上. 首先,android程序的布局文件main ...
- 判断activity是否显示在界面上
boolean result = false; ActivityManager am = (ActivityManager) context .getSystemService(Context.ACT ...
- 推断View是否显示在界面上
我们都知道ViewController有viewWillAppear和viewDidAppear等关于页面生命周期的方法,用来对视图做一些管理,比方页面出现时怎么样,页面消失时怎么样.. 可是对于Vi ...
- Unity代码里的Position和界面上的Position
代码里的Position = 世界坐标 this.gameObject.transform.position 界面上的Position = localPosition
- 【WPF学习笔记】之如何把数据库里的值读取出来然后显示在页面上:动画系列之(六)(评论处有学习资料及源码)
(应博友们的需要,在文章评论处有源码链接地址,以及WPF学习资料.工具等,希望对大家有所帮助) ...... 承接系列五 上一节讲了,已经把数据保存到数据库并且删除数据,本讲是把已经存在的数据从数据库 ...
- 使用 jquery 的 上传文件插件 uploadify 3.1 配合 java 来做一个简单的文件上次功能。并且在界面上有radio 的选择内容也要上传
使用 jquery 的 上传文件插件 uploadify 3.1 配合 java 来做一个简单的文件上次功能.并且在界面上有radio 的选择内容也要上传 uploadify 插件的 下载和文档地址 ...
- Bootstrap系列 -- 8. 代码显示
一. Bootstrap中的代码块 代码块一般在博客中使用的较多,比较博客园中提供的贴代码. 在Bootstrap中提供了三种形式的代码显示 1. 使用<code></code> ...
- thinkphp从数据库里的html代码显示页面不解析
首先,这个问题不应该出现在这里,因为以前在用ThinkPHP3.1.2的时候,利用富文本编辑器保存文本后,直接从数据库里面取出的数据都能正常显示,改用ThinkPHP3.2.3之后,thinkphp从 ...
随机推荐
- [代码片段] 获取分辨率DPI和像素、毫米、英寸互相转换
private static float DEFAULT_DPI_X = 0; private static float DEFAULT_DPI_Y = 0; /// <summary>获 ...
- jquery中,某些写法后来更新导致版本不支持的替代方法
等号后面的书写替换前面老的书写方法 $.browser.mozilla = /firefox/.test(navigator.userAgent.toLowerCase());$.browser.we ...
- 遇到 https://packetlife.net/
"I have never let my schooling interfere with my education." --Mark Twain
- Matlab:读取、写入(.txt)(.xlsx)
写入txt a=[1,2,3;4,5,6]; save C:\Users\Administrator\Desktop\a.txt -ascii a 参考:https://blog.csdn.net/h ...
- Python库之os库和logging库的基本使用说明
使用os库操作目录及文件 使用os.sep() 方法获取系统分隔符 print(os.sep) 使用os.name()方法获取操作系统的平台类型 print(os.name) 使用os.getcwd( ...
- 使用 GVM 搭建可维护的 Golang 开发环境
当你想完成 Golang 开发环境的便捷安装以及随时更新和保障多个版本的 Golang 共存的时候,就需要使用到 Golang 的专门版本管理工具 --gvm 本篇随笔记录了在 Ubuntu 下安装使 ...
- 【JS基础】ES6模块系统
export export 导出方式有两种,命名导出和默认导出. 命名导出还是默认导出都是都导出模块中内容的一种方式,可以混合使用. 个人理解:默认导出其实是导出了default别名变量. 一个模块只 ...
- ks.cfg 怎么读取光盘 (cdrom) 上的文件并执行对应的脚本
ks.cfg 文件怎么实现读取光盘 (CDROM) 上的内容并执行自定义脚本我们知道 linux 系统安装过程中,要想实现自动化安装,一般都是利用 Kickstart 这个工具实现,最重要的就是其配置 ...
- Vue基础语法整理
vue基础用法&基础原理整理 1. vue基础知识和原理 1.1 初识Vue 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象 demo容器里的代码依然符合html规范,只不过混 ...
- nginx的location与proxy_pass配置超详细讲解及其有无斜杠( / )结尾的区别
本文所使用的环境信息如下: windows11 (主机系统) virtual-box-7.0环境下的ubuntu-18.04 nginx-1.22.1 (linux) 斜杠结尾之争 实践中,nginx ...