乐趣区

关于android:Flutter-如何将代码显示到界面上

前言

如何优雅的将我的项目中的代码,亦或是你的 demo 代码展现到界面上?本文对应用简略、便于保护且通用的解决方案,进行相干的比照和探索

为了节俭大家的工夫,把最终解决方案的相干接入和用法写在后面

预览代码

疾速开始

  • 接入:pub,github
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 如果须要换行,换行的文案前必须加 中划线
/// @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 计划

  • https://github.com/toly1994328/FlutterUnit

FlutterUnit 我的项目也是自带代码预览计划,这套计划是比拟非凡计划

  • 大略看了下,整个 FlutterUnit 的数据都是基于flutter.db,该文件外面就有相干 demo 的文本信息
  • 所有的 demo 也是独自存在一个叫 widgets 的我的项目中
  • 所以大略能够猜测出

    • 应该会有个 db 的辅助工具,会去扫描 widgets 的我的项目中的 demo 代码
    • 将他们的文本信息都扫描进去,而后解析下面的正文等相干信息,分类存储到数据库中,最初生成 db 文件
  • 映射表,宿主能够通过 db 中的组件类名,从这里拿到 demo 成果实例

总结

整套流程看下来,实现起来的工作量还是有点大的

  • db 辅助工具的编写
  • 文本正文相干解析规定
  • 如何便捷的保护 db 文件(辅助工具是否反对,生成后主动笼罩宿主 db 文件)
  • 不同平台 db 文件的读取和相干适配

长处

  • 因为扫描工具不依赖 Flutter 相干库,预览计划能够疾速的移植到其它编程语言(compose,SwiftUI 等)
  • 具备高度自定义,因为是齐全独立的第三方扫描工具,能够随性所欲的定制化

毛病

  • 最显著的毛病,应该就是略微改下 demo 代码,就须要三方工具从新生成 db 文件(如果三方工具实现的是 cli 工具,能够将扫描生成命令和 push 等命令集成一起,应该能够比拟好的防止该问题)

build_runner 计划

  • https://pub.dev/packages/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.yamlassets外面间接写通配符定义全门路,而后喜剧了,它不反对这种写法

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,并不一定会失去预期内的数据。。。

具体探讨可参考

  • https://github.com/dart-lang/sdk/issues/35052
  • https://github.com/flutter/flutter/issues/78041

解决思路

  • 将压缩类型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 平台,能够不便的体验各种组件的成果,联合文章中的代码预览计划,就能够更加疾速的上手各种组件用法了~

好了,下次再见了,靓仔们!

退出移动版