Flutter 如何将代码显示到界面上

news2024/11/24 19:16:22

前言

如何优雅的将项目中的代码,亦或是你的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);

  
  Widget build(BuildContext context) {
    return const CodePreview(className: 'Test');
  }
}
  • 使用效果:flutter_smart_dialog

image-20230429215042820

配置代码文件

因为原理是遍历资源文件,所以必须将需要展示的代码文件或者其文件夹路径,定义在assets下,这步操作,为大家提供了一个自动化的插件解决

强烈建议需要展示到界面的代码,都放在统一的文件夹里管理

  • 展示界面的代码需要在pugspec.yaml中的assets定义

image-20230422224011359

如果代码预览的文件夹,分级复杂,每次都需要定义路径实在麻烦

提供一个插件:Flutter Code Helper

  • 安装:Plugins中搜索Flutter Code Helper

image-20230422225244651

  • pugspec.yaml中定义下需要自动生成文件夹的路径,文件夹随便套娃,会自动帮你递归在assets下生成
    • 不需要自动生成,可:不写该配置,或者配置空数组(auto_folder: [])
code_helper:
  auto_folder: [ "assets/", "lib/widgets/" ]

Apr-09-2023 22-33-42

说明下:上面的插件是基于RayC的FlutterAssetsGenerator插件项目改的

  • 看了下RayC的插件代码和相关功能,和我预想的上面功能实现有一定出入,改动起来变动较大
  • 想试下插件项目的各种新配置,直接拉到最新
  • 后期如果想到需要什么功能,方便随时添加

所以没向其插件里面提pr,就单独新开了个插件项目

高级使用

主题

提供俩种代码样式主题

  • 日间模式
CodePreview.config = CodePreviewConfig(codeTheme: CodeTheme.light);

image-20230429215716043

  • 夜间模式
CodePreview.config = CodePreviewConfig(codeTheme: CodeTheme.dark);

image-20230429215545723

注释解析

  • 你可以使用如下的格式,在类上添加注释
    • 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);

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

  
  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文件

image-20230429172832212

  • 映射表,宿主可以通过db中的组件类名,从这里拿到demo效果实例

image-20230429175714400

总结

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

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

image-20230429205530817

优点

  • 侵入性非常低,不会像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);

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

  
  Widget build(BuildContext context) {
    return const CodePreview(className: 'CustomDialogAnimation');
  }
}
  • 一般来说,我是统一配置预览demo和className,这样比较好对照

image-20230429170007279

路径定义优化

本来是想在pubspec.yamlassets里面直接写通配符定义全路径,然后悲剧了,它不支持这种写法

flutter:
  assets:
    - lib/widgets/**/*.dart

GG,只能想其他办法了,想了很多方法都不行,只能从外部入手,用idea插件的形式,实现自动化扫描生成路径

  • 安装:Plugins中搜索Flutter Code Helper

image-20230422225244651

  • pugspec.yaml中定义下需要自动生成文件夹的目录,文件夹随便套娃,会自动帮你递归在assets下生成
    • 不需要自动生成,可:不写该配置,或者配置空数组(auto_folder: [])
code_helper:
  auto_folder: [ "assets/", "lib/widgets/" ]

Apr-09-2023 22-33-42

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平台,可以方便的体验各种组件的效果,结合文章中的代码预览方案,就可以更加快速的上手各种组件用法了~

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/490645.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C. Permutation Game(博弈 + 拓扑的思想)

Problem - C - Codeforces 经过漫长的一天, Aice和Bob决定玩一个小游戏。游戏棋盘由n个格子组成,在一条直线上,编号从1到n,每个格子包含一个数字4;,qy在1到n.之间,而且没有两个格子包含相同的数字。 一个棋子被放在其中一个格子里…

nestJS入门cli 创建项目以及集成swagger和mysql

nestJs 1. 简介 介绍 NestJS NestJS 是一个基于 TypeScript 的渐进式 Node.js 框架,它结合了 OOP、FP 和 FRP 的元素,以提供一种现代且可扩展的开发体验。NestJS 建立在 Express.js 之上,但是提供了更加抽象和模块化的方式来编写应用程序。…

蓝牙资讯|苹果与谷歌起草蓝牙定位追踪设备行业规范

苹果与谷歌于当地时间5月2日联合提交了一份行业规范草案,以帮助应对蓝牙定位追踪设备遭滥用的问题。目前已有包括三星在内的追踪设备制造厂商宣布支持该草案。 据了解,苹果与谷歌此次联合提交的行业规范草案将云熙蓝牙定位追踪设备兼容跨iOS以及Android平…

【LeetCode: 518. 零钱兑换 II | 暴力递归=>记忆化搜索=>动态规划 | 背包模型】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

十五周算法训练营——二叉搜索树(BST)

今天是十五周算法训练营的第五周,主要讲二叉搜索树专题,包含:验证二叉搜索树、不同的二叉搜索树、二叉树的最近公共祖先、二叉搜索树的最近公共祖先。(欢迎加入十五周算法训练营,与小伙伴一起卷算法) BST的…

在SQL中使用AI【EVA】

EVA 旨在支持使用深度学习模型对结构化数据(表格、特征向量)和非结构化数据(视频、播客、PDF 等)进行操作的数据库应用程序。 它使用一系列受久经考验的关系数据库系统启发的优化,包括函数缓存、采样和基于成本的谓词重…

mybatisPlus初识

文章目录 什么是mybatisplus依赖入门案例自动填充乐观锁悲观锁乐观锁 mybatisPlus实现乐观锁批量查询根据指定条件查询 什么是mybatisplus mybatisplus是mybatis的增强工具&#xff0c;支持多种类型的数据库。 依赖 <dependency><groupId>com.baomidou</group…

我的服务器被挖矿了,原因竟是。。。

「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 挖矿木马应急响应 一、什么是挖矿二、被挖矿主机现象三、挖矿木马处置思路1&#xff09;隔…

Git 使用教程:最详细、最正宗手把手教学(万字长文)

目录 一&#xff1a;Git二&#xff1a;SVN与Git的的区别三、安装Git四&#xff1a;常规操作五&#xff1a;远程仓库六&#xff1a;创建与合并分支七&#xff1a;bug分支八&#xff1a;多人协作九&#xff1a;git可视化工具 Git Git 是一种分布式版本控制系统&#xff0c;用于…

搭建免费的Plex媒体服务器 - 打造超级多媒体中心【异地远程连接】

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

科普, API 是这么演变而来的

API&#xff0c;全称为 Application Programming Interface&#xff0c;中文翻译为应用程序编程接口&#xff0c;是为了方便应用程序之间的数据和功能交互而设计的一些标准方法。API 的使用让开发者能够快速、高效地构建应用程序&#xff0c;从而加速了应用程序的开发速度。在 …

第二章物理层

1.物理层的基本概念 物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流。物理层为数据链路层屏蔽了各种传输媒体的差异&#xff0c;使数据链路层只需要考虑如何完成本层的协议和服务&#xff0c;而不必考虑网络具体的传输媒体是什么。 物理层协议的主要任务 机…

21.ThreadLocal有哪些内存泄漏问题?如何避免?

ThreadLocal有以下几个内存泄漏问题&#xff1a; 长期不清理&#xff1a;如果ThreadLocal对象被长期占用&#xff0c;且不及时清理&#xff0c;会导致内存泄漏&#xff1b;使用static ThreadLocal&#xff1a;如果将ThreadLocal定义为static变量&#xff0c;就会导致它的生命周…

Java程序设计-案例:自由落体

程序模拟物体从10000米高空掉落后的反弹行为。 球体每落地一次&#xff0c;就会反弹至原高度的一半。按用户输入的弹跳次数&#xff0c;计算球体每次弹跳的高度。 实现过程&#xff1a; 1. 新建项目&#xff1b; 2. 接收 用户输入的弹跳次数&#xff1a; &#xff08;1&#…

全国快递物流 API 实现快递单号自动识别的原理解析

概述 全国快递物流 API 是一种提供快递物流单号查询的接口&#xff0c;涵盖了包括申通、顺丰、圆通、韵达、中通、汇通等600快递公司的数据。该 API 的目标是为快递公司、电商、物流平台等提供便捷、快速、准确的快递物流信息查询服务。 数据采集和处理 全国快递物流 API 的…

[深度学习]Ring All-reduce的数学性质

分布式深度学习里的通信严重依赖于规则的集群通信诸如 all-reduce, reduce-scatter, all-gather 等&#xff0c;因此&#xff0c;实现高度优化的集群通信&#xff0c;以及根据任务特点和通信拓扑选择合适的集群通信算法至关重要。 本文以数据并行经常使用的 all-reduce 为例来…

PCIe物理层链路训练和初始化(详细)总结附图文解析-PCIe专题知识(三)

目录 前言一、简介1.1 链路过程总结 二、基本概念2.1 常用字符序列2.1.1 TS1 TS2序列2.1.2 Idle序列2.1.3 FTS(Fast training sequence)序列2.1.4 SKIP序列 2.2 链路训练相关知识 三、具体过程3.1 detect状态3.2 polling状态3.3 config状态3.4 L0状态3.5 Recovery状态3.5.1 Rec…

【Git】全面详细了解开发者必备工具Git(2.0)

✍️ 作者简介: 前端新手学习中。 &#x1f482; 作者主页: 作者主页查看更多前端教学 &#x1f393; 专栏分享&#xff1a;css重难点教学 Node.js教学 从头开始学习 ajax学习 文章目录 学习目标起步——关于版本的控制  文件的版本  版本控制软件  使用版本控制软件的好…

Ubuntu 23.04 安装 Conda

Ubuntu 23.04 安装 Conda 1. 下载 Conda 安装脚本2. 运行安装脚本3. 安装完成后&#xff0c;关闭当前终端并打开新终端&#xff0c;这将激活 Conda4. 更新 Conda 至最新版本5. 添加必要的 Conda 通道以获取更多软件包6. 测试是否安装成功 1. 下载 Conda 安装脚本 wget https:/…

[Gitops--10]微服务项目部署流水线编写

微服务项目部署流水线编写 1. 部署环境说明 序号管理地址作用1192.168.31.199GitLab2192.168.31.104Harbor3192.168.31.131kubesphere 1.1 GitLab 1.2 流水线 1.2.1 创建流水线 1.2.2 创建凭证 1.2.3 创建kubeconfig凭证 这里需要注意的是,config中如果使用的是域名,那么需…