GUI 框架基础需求、设计和实现 - 4 具体组件、模块设计
以下皆为建议性内容,根据需求制定方案。
这里列出 GUI 开发中的 通用性 的 需求、问题的解法和方案 等,为了能够搭建出复杂 GUI 显示和交互逻辑,满足交互设计需求。
文章所在 Github 仓库 Staok/GUI-Framework-Study: GUI 框架基础需求、设计和实现文章 会保持最新,其它地方的不会跟进。Github 上的 MarkDown 显示更完善,建议去原仓库看或者下载下来看。
涉及到本文没有的小节,请参考同专栏其它文章。
多字体 / 多语言 以及 高度自动化
多字体
了解 UI 框架 的字体支持。
需求:支持 ttf 字体文件;可方便切换字体、字号。
UI 最好使用 字体文件 来显示文字,字符全、容易添加 和 裁剪 等。
准备语言字体方法:
字体文件准备,最好使用开源字体,比如 通过 google font 下载一个目标语言的字体文件(寻找选择 Noto Sans 字体类型 https://fonts.google.com/noto/fonts,下载后,通常寻找里面的 Regular 字号的字体文件)。
如果字体里面语言或者符号不全,可以使用 开源软件 FontForge 软件 将 目标语言 字符全部复制 / 或者 通过 合并字体,现在 合并为一个 字体文件。
fontForge 教程 https://blog.csdn.net/liweizhao/article/details/135540737,https://zhuanlan.zhihu.com/p/617260598。多个教程结合着看。
多语言切换基本原理和实现
需求:label 设置如 _("xxx") 或 ts("xxx") 来翻译;文案中使用 %1、%2... 等来替换成变量;多语言数据管理;(延申项)多元数据可动态更新(从网络下载(加密通讯和下载的)更新到本地并热更新)。
多语言的处理可以高度自动化。
-
多语言切换基本原理和实现。(对于 qt 这种 UI 框架本身支持在线切换语言则省心,但对于其它框架若没有原生支持则需要自己定制开发)
在程序中 使用宏 如
ts()或_()标记多语言的文案字符串(一般用英文en,即作为默认语言,这个程序中的字符串也默认作为 key,多语言管理平台的数据结构一般为key-src-transed,即 key-默认语言字符串-其它语言翻译后的字符串),用于脚本提取所有多语言的字符串原文,以及返回指定语言翻译后的字符串。提取文案字符串 -> 进行翻译 -> 翻译后的多语言数据文件 -> 程序读取、存入内存 -> 在程序中的
_("hello")获取 "hello" 的指定语言的翻译后的字符串。注意 翻译后的多语言数据文件,一般是作为程序外面的文件,而不必成为程序内的数组之类的,这样容易固定死、不能热更新多语言。
提取标记的文案字符串和取得指定语言的字符串,这个可以自己实现,也可以使用库
gettext,即整个多语言的提取和解析都用这个库提供的工具,也比较通用。官网 gettext - GNU Project - Free Software Foundation。参考教程 使用gettext进行多语言国际化 - 知乎。gettext安装及使用-CSDN博客。尽量做到 运行时切换多语言。也可考虑每次切换多语言需要重启。看需求。
对于前者------运行时切换多语言:对于静态固定文本则比如
label_set(_("hello"));这种则脚本扫描程序中所有这种语句都提取出来到一个函数,切换语言后再调一下来刷新。对于动态创建的 UI 组件里面的 文本,则创建时候 添加到 某个 map(unordered_map 或 hash),里面存储 UI 文本对象 obj 和 字符串原文,UI 组件销毁时候则从 map 中对应删掉,再切换语言后,遍历这个 map 所有 文本 obj 并 执行label_set(_("hello"));来刷新显示即可。 -
提取所有多语言原文文本后,可以使用多语言管理平台,Localazy 和 Crowdin 等,推荐后者,功能更丰富。
在多语言管理平台中进行各语言的翻译,多人协作,日常维护文案。
多语言管理平台(如 Localazy 或 Crowdin) 的使用 以及 文案规范 等,见后面。
-
联网设备 OTA 更新多语言文本。
可以在产品发出后,还可以继续更新和优化多语言并可及时更新到用户设备上。
如果是自己实现多语言数据的解析,则基本原理如下:多语言数据的解析也可以用库
gettext,即整个多语言的提取和解析都用这个库提供的工具,也比较通用。以 localzy 为例,即从云端拉取 和 更新 多语言 如 json 文件,参考 localzy 的
Multilingual JSON格式:{ "en": { "Left": "Left", ... }, "fr": { ... } }程序里面把这个解析为 json,再 把这个 json 里面 当前语言 code 下的 key value 对 存储到 程序里面的 unordered_map 里面,设 label 的时候,传入 英文原文的 key,得到翻译后的文本。
-
多语言处理的一般步骤:
-
第一类:尽量多的程序自动化处理
-
多语言的 en 的 key 一般直接添加到程序里面,过长的则分段,key 应符合后文的规则规范。
-
程序中实现动态切换多语言。或者至少切语言后自动重启屏幕即可切换。
-
使用扫描工具提取出所有多语言字符串 en 的 key,自动上传 多语言平台。多语言平台上进行处理和翻译。翻译可以默认都先使用 机器翻译 或 AI 大模型翻译 完成,而后再由人工翻译,这样可以快速先翻译完之后在产品上先看看效果。
-
使用工具下载多语言(一般选择 json 格式)的数据文件,并自动合入程序,或者在 编译封包 的时候自动下载和并入编译步骤。
-
(可选)程序启动时候从云端拉取一次多语言数据文件,并使用。开机时候先用本地的多语言文件,然后从云端拉取最新的,如果拉取失败则仍用本地的,拉取成功则自动切用最新的。
-
-
第二类:上传多语言手动逐个加
-
与上面第一步的区别在于,很多多语言文案比较长且符号较多,就手写一个符合规则规范的 Key 然后 手动上传 这个 key 和 对应的 en 字符串(可以写个工具上传,查新等)。
-
除了上面第三步扫描提取,其它与上面第一类的一样。
-
-
-
(可选机制)多语言 根据 翻译后的字符串 反查 key:
-
首先创建个 多语言的 key value 对 的反向的 unordered_map / hash(一般存为 pair 数据,其 first 使用 翻译后的字符串,second 使用 原 map 的 iter),即就可以根据 翻译后的 找 翻译前的。
-
这样就可以:
- 解决一些需要根据翻译后的寻找翻译前的场景。
-
缺陷:
-
不能检查 中间有 变量的 比如带有
%1的 字符串。 -
可能有多个 key 对应一个 value,从而导致根据 value 寻找 key 找错,要使用上述机制,就得尽量避免这种情况。
-
-
-
(可选机制)多语言检查是否漏加或没翻译:
程序运行时打印没有的、未翻译的多语言词条,用于找出遗漏的,后面补充,热更新。
程序运行时,在从 map 中 根据 key 寻找 翻译后的字符串 的时候:
-
寻找漏加翻译标识的(key 没在 map 里面的)。
-
寻找添加了翻译标识在 map 里面但是没有翻译的(key 有,但是 key 和 value 相同,或者 value 为空的)。
-
上传多语言管理平台的文案的规范和注意事项
上传多语言管理平台的文案需要尽量符合以下规范和注意事项
这里以 localazy 为例(后面有 Localazy 使用规范),以下大部分规则规范为通用的,可能其它平台稍有出入,需要多实践和测试。
-
确定性。一段文案尽量一次确定好,可多方咨询最后确定定稿后,再同步给 UI开发 去做。localazy (2024 某个时期之后)改了机制,相同 key 的字符串上传会直接覆盖,所以直接改 localazy 的一个文案的 en 无效,因此,在以 key 和 en 一样的规范前提下,改动文案就需要同步改动 ui、后端程序、squareLine 等多个地方,代价大。
但是,一些需求会随着上机看效果和使用等,逐渐清晰和成熟,会再有改动,开发还是会改的,注意是留好时间。
-
有限字体大小。为规范和兼容性,以及美观性,可尽量只用有限的几种大小的字号,如 16、24、30、36。
-
同时 提供英文 和 中文(多语言管理中,程序中的 英文字符串 作为这条文案的 key 和 英文翻译(尽量),其它语言以此为准)。
-
短句为主,而且得是整句。有换行,多行句号且意思不一样的,尽量拆分成一个一个短句,UI上面也分成多个 label;不是整句的,多语言翻译中,其它语言 会出现 语法错误 或者 没法翻译。
-
管理标点符号。
-
一段英文原文,如果是句子,结尾加
.,结尾尽量不要有冒号:,结尾不要留空格(可能会被工具链上某个工具优化掉),文本中的标识 "或者" 意思 的 斜杠 均用/而不要出现\。英文文案里面不要出现中文的标点符号如""''等等。 -
摄氏度符号统一使用
℃,unicode 编码为 U+2103。 -
→这种比较少见的特殊的符号等非必要不使用,也不一定好看。 -
一段话中有回车符号
\n,则在回车符号后面加一个空格,如... screws\n and ensure ...,在(使用手动复制这句话 或者 使用 CLI 工具)上传 localazy 上面后,可以分辨回车在哪里。 -
段话最好不要有回车,有回车则酌情考虑分为多端文本单独放。
-
-
一个多语言文案的 Key(多语言管理平台范畴的 Key,确定一段文案的 Index)不允许有 回车符号
\n(因为有的多语言管理平台也许不支持)。因此,要么所有 label 里面保证 没有回车(并使用脚本扫描程序提取所有文案并上传平台高度自动化),要么所有新增文案均手动上传多语言管理平台,并手写一个符合规则的 Key(就不使用脚本扫描程序提取文案)。或者 使用工具 处理所有的 Key 以符合规则,规则如下:
Key 规则:
-
不能出现 回车符
\n(替换为-)、反斜杠\、双引号"、以及 非 ASICII 字符 等。 -
可以为 source(一般为 en 英文)去掉上述符号后的字符串。
-
为 纯 ASICII 的。
-
-
管理大小写,管理名称。一个事物,其名称尽量从一开始就确定,避免一个事物在不同地方、不同时间有多个名字。
-
避免花活。适当可以有一段文字内部切换颜色、大小等,需要看 UI 框架的支持情况,可交互设计来改善。比如 lvgl 支持 但是考虑要多语言(不同语言变颜色的位置不一样,翻译时候需要额外注意,除非是数字部分变颜色,文本中间变大小就目前直接不支持)等。
-
文案内部尽量避免变量或动态变化的。可以用
XXX 的值为:xx这种句式,程序中可以很好的分开写,保持前面XXX 的值为:为一整段语句。如果一定要一整段话 中间 有 变量 要显示的,即一个多语言字符串中间要带有变量来显示的,统一用%1、%2..来代替,程序里会查找替换,(约等于继承自 qt 的写法,C++ 端用字符串替换即可),而且不要把一句话分成多段------多语言翻译时候是个灾难! -
文案尽量合并相似的条目,减少总条目------减少管理的心智负担,而且人工翻译要钱。
-
翻译相关:
-
比较长的语言的翻译,酌情选择短的方式来翻译------显示方便、减少阅读时间。
-
UI 屏幕里面的所有文案,由 交互设计 人员给到 开发,开发写到程序中后,随着功能实现 不定期会 将 英文原文 文案 上传到 多语言管理平台。平台可配置为先用机翻或者 AI 大模型翻译一遍,开发快速拿到结果看看效果。并且同时要及时联系各个语言翻译人员进行翻译。
-
非开发人员可以 直接修改 localazy 里面 除了英文语言 外 的所有词条。(因为上传 localazy 会 将相同 key 的 文案直接覆盖,开发 只会上传 英文文案 进行更新,所以非开发人员不应该直接修改英文文案,如果要修改,要交给 开发 来更新程序并更新 localazy(更新 key 和 en,并勾选 Existing translations need to be updated,来让其它所有语言对这个文案都重新翻译))
-
Localazy 使用规范
多语言管理平台。
-
基础操作:创建工程,创建语言,上传文案,下载文案。
-
创建工程,创建语言:对于自己提取文本和解析多语言数据,经过实践经验,可以选择
Multilingual JSON格式作为多语言存储文件格式,其形式如(用于上传和下载文案):这个格式比较方便构造和被程序解析。{ "en": { "hello": "hello", "day": "day" }, "fr": { "hello": "Bonjour", "day": "jour" } }基础语言选择英语。然后点
ADD LANGUAGE添加其它语言。 -
上传文案:
-
法1,手动逐条加:点开 English,再右上角点
ADD NEW KEY,来添加 key 和 en。 -
法2,手动上传文案:在 Tools -> File management 里面,在多语言存储文件 file.json 右边点击上传按键,要求上传
Multilingual JSON格式 的 文案文件,会与 当前 file.json 文件合并。 -
法3, CLI 工具 上传:如下所说 CLI 工具用法。
-
-
下载文案:
同上 法2 和 法3。
CLI 工具用法 可看 localazy 官方文档,每个 option 都有介绍。一个例子:
writeKey和readKey从 localazy 工程的 Project settings 里面找到。!!严重注意!!:在第一次上传文案之前,要先做一些试验,上传的字符串是否会覆盖云端的(相同的 key 的 value 的覆盖,因为 localazy 上面,实际多人项目中,确实难以保证 en 语言的 key 和 value 始终保持一致),还是跳过(实测按照下面的还是会覆盖,也找不到其它相关的 option,现在是 先下载最新的,再上传,会新增和覆盖,然后再用前面下载的再上传,因此又覆盖回原样)。并且在每次上传之前,要先下载最新的作为备份,然后才可放心上传一次。
{ "writeKey": "xxx", "readKey": "xxx", "upload": { "type": "json", "features": ["multilingual"], "files": "en.json", "importAsNew": true, "skipFailedAutodetection": true, "deprecate": "none", "filterSource": true }, "download": { "files": "file.json" } } -
文案处理基本:
-
参考上面
上传多语言管理平台的文案需要尽量符合以下规范和注意事项一段 里面的内容。 -
基础语言一般为 en,添加新文案,一般 key 与 en 相同,除非特殊用途。
-
文案中尽量不要带回车
\n;不要有\\,都用/。如果非要一个文案中有回车的,则,不要在
edit key里面手动按回车来造一个(这个下载下来后还会保持\n,但是用 本地文件 来批量上传文案的时候,会有问题,尤其是不同的格式如 yaml / ts / json,对这个回车的处理不同...有的可以正确识别,有的不能),这样下载下来后,会变成\\n,因此下载下来后,需要做如下按顺序的三步处理:可用脚本做-
\\\\全部替换为\\。 -
\\\全部替换为\。 -
\\n全部替换为\n。(程序中的\"上传再下载后可能变为\\\",用上面第二步处理就正常了,因此不用特殊处理\\\")
-
-
-
多人合作:
-
第一次上传很多文案,可以用上述的脚本或文件来统一上传,后面就不建议这么用,因为 localazy 某一次改版,导致 相同 key 的 会用 脚本或文件上传的 en 覆盖 老的 en,若 localazy 里面有 key 和 en 不符的情况,则这么做很危险。
-
所以之后上传文件,推荐手动挨个随着功能开发而添加,除非确认没有重复的 key。
-
赶时间则可以用 机翻 或 AI 大模型 翻译的,应把所有机翻的文案均设置为
need improvement状态,这样导出会带着,单单设置review状态导出不会带着。 -
不确定翻译 而 手动添加的 并翻译的,也应设置为 为
need improvement状态,以便 交互设计 等人员 可以筛选出来再进行文案优化。 -
不使用的文案,设为
Deprecated状态,这个不会导出,Hidden状态只是翻译人员看不见但是仍可以导出(所以不用的文案设为Deprecated更好)。
-
各语言 Unicode 编码相关
常用语言的 Uincode 范围
// LanguageType and strings
// 缩写 UI 注
// "en", English 英语(English)
// "fr", Français 法语(French)
// "de", Deutsch 德语(German)
// "es", Español 西班牙语(Spanish)
// "sv", Svenska 瑞典语(Swedish)
// "nl", Nederlands 荷兰语(Dutch)
// "ja", 日本語 日语(Japanese)
// "zh-cn", 中文 中文(Chinese (China, Simplified)) same as "zh-CN-Hans" used for localazy and lv_i18n
// "zh-tw", 中文繁體 中文繁体(Chinese (Taiwan, Traditional)), same as "zh-TW-Hant"
// "pt_BR" Português 巴西葡萄牙语(Brazilian Portuguese)
// "hu" Magyar nyelv 匈牙利语(Hungarian)
// "it" Italiano 意大利语(Italian)
// "ko" 한국인 韩语(Korean)
// "pl" Język polski 波兰语(Polish)
结论:
字符集范围选择(英文、中文(简繁体)、荷兰语、法语 / 德语、西班牙语、瑞典语、日语):
0x20-0xFF, 0x4E00-0x9FBF, 0x3040-0x309F, 0x30A0-0x30FF
小语种零碎的(已经去重,已经去掉字体里找不到的字符 0x2B3, 0x2E2, 0x1D48-0x1D49, 0x1F1, 0x1F3):
0x150-0x153, 0x170-0x171, 0x104-0x107, 0x118-0x119, 0x141-0x144, 0x15A-0x15B, 0x178-0x17C
韩语:一些字体中没有韩语,可以另找一个韩语字体然后把其内的所有字符合并到 当前字体里面做
0x3008-0x3015, 0xAC00-0xD7A3
俄语:
400-44F-4FF,451,500-52F,2010-201E,2026,2030,20AC
-
0x3400-0x4DBF,不常用中文字符,看情况加
-
手动补充基本符号
...•;---„々→℃ÄäÖöẞßÜüÀàÂâÇçÉéÈèÊêËëÎîÏïÔôÛûÙùÜüŸÿÁáChchÉéÍíÑñÚúÜüÅåÄäÖöÓó,。!?""、:()【】《》''¿¡°「」¡¿?!<<>>''‚'『』
-
常用字手动补充
亦尤上視析起展另市府種你式多我容士過間括注營助二重或機世極信伊下要安樣部圍司敢隨定覺逐均尺族望則處腦反政嗯元明校跟樂裡境近績響遠電歲象僅單企擔利能否兒據控再並使意思女獲維叫以頗是無友訓談類記媒充那為聲舉各至易業具必彼說吃啦坐於採少需史勵即計資港官的甚畫如簡對媽底令專入和求支長終喜迎把果濟織樹隻值姐始認免日謝台主輕套他快愈影費天態民被劃走皆形牠針高歐系自其它告配兩成銀增最束通推斷麗創來源看玩查剛子參交內產排講方選才買科而灣製領每車策三管故立錯許真節都球出死適還聽關商了特請站量土擁幾拉際努西新克全己仍理目讓常只界顯東八楚候麼往夠打五卻從又哪進聞行革念授實先減表加不且在小格某改概肯軟非國經童尚些委素陸賽基提切頭公制已清您例取歡書此發水怕色網呀論務治構性名得正共路化服料文等座包附中域塊直力習嗎錢依臺訴備與何事到教話報代設育就願北續申興慧漸生強庭步然戰引現結將外低次階社般什知範因找喝豐師健驗沒她辦變張情四誰消臉列一壓規笑永嘛運決之六太由未保分們根爸示雖越醫音朋觀妳乏精法團整個識員母趣帶既工希寫合滿足供蘇空投山動造義像調總聯該傳組程茶住手應份謂價藝若質件準放積建月約雙心品很便效句課負會護時也透命追練眾議洲研功歷相屬曾想地察拿早久老存七央啊著受字期須神語言可乎道爭除缺責較父原係同擇況做條感施比黨回訊狀人富去前有用家後身男居給當接慮半解親術院確問繼究開指溝怎者統口持康角活任別更愛似大婦深向學片十揮體執項完考園旁技智連超難協但香年標嚴鼓作邊及美物京廠達題善環所面段區好本點討導位吧見呢這避殊度孩場
各语言的 Unicode 编码范围
语言代码表 http://www.lingoes.net/zh/translator/langcode.htm
下面各个语言的 unicode 范围,一些语言自己的特殊符号就不记了
-
英文 English en
0x20-0xFF
包括基本英文字符、数字、标点符号等
-
中文 + 中文繁体台湾
Chinese (China, Simplified) zh_CN#Hans
Chinese (Taiwan, Traditional) zh_TW#Hant
包括了常用汉字、繁体汉字、部分生僻字以及一些汉字的变体:CJK Unified Ideographs ------ 0x4E00-0x9FA5
扩展,不常用的字:CJK Unified Ideographs Extension A ------ 0x3400-0x4DBF
-
荷兰语 Dutch nl
同英文字母
-
法语 French fr / 德语 German de
同英文字母范围,00C0-00FF
法语新增 152-153,178,2B3,2E2,1D48-1D49
德语 同英文字母范围
字母上面有2点者为德文, 字母上面有重音符号者为法文
-
西班牙语 Spanish es
同英文字母范围
-
瑞典语 Swedish sv
同英文字母范围
-
日本語 Japanese ja
Hiragana, 日文平假名 ------ 0x3040-0x309F
Katakana, 日文片假名 ------ 0x30A0-0x30FF
-
匈牙利语 Hungarian hu
同英文字母,新增 150-151,170-171,1F1,1F3
-
巴西葡萄牙语 Brazilian Portuguese pt_BR
同英文字母
-
意大利 Italian It
同英文字母范围
-
波兰 Polish pl
同英文字母范围,新增 104-107,118-119,141-144,15A-15B,179-17C
-
韩语 Korean ko
3008-3015,AC00-D7A3
-
俄语 Russian ru-RU(俄语-俄国)
同英文字母,新增 400-44F-4FF,451,2010-201E,2026,2030,20AC,500-52F
Unicode 相关参考
Unicode 官网 https://home.unicode.org
各国语言字符 Uincode 编码 查询 https://character-table.netlify.app
Unicode table to pick letters: https://unicode-table.com
Unicode 划分 参考:
字体信息在线查看 https://fontdrop.info
多机型 / 功能宏 / 素材资产管理
可以一套工程 里面 使用 功能宏(依据不同机型) 来做。功能宏二维表格:横轴各个机型、纵轴各个功能宏,中间勾选,根据表格导出对应机型的功能宏到程序中,程序中根据这些功能宏判断是否编译相关代码。
资产管理。多语言文本,以及 图片 和 视频 等 的管理。均为表格管理,根据机型判断每个资产是否包含,进行导出。
图片素材,按照公用和机型分个大类(分文件夹),图片命名规则,如 机型-页面-具体名词。程序里面使用 素材占位标识(比如某个变量,然后根据 占位标识---图片素材 的对应表格,生成各个图片的文件地址赋值给对应占位符)来标识这个地方使用对应的图片来显示。
使用 csv 或 其它文件格式的 表格来管理 各产品型号 以及 该产品下的 功能宏 / 资产 差异,如下:竖着看,只打包使用某个产品型号下所有标有 y 的。
产品型号 A B C
功能宏a y n y
功能宏b y y n
功能宏c y n n
文案key1 y n n
文案key2 y y n
文案key3 y y y
图片占位符1 图片素材1 图片素材2 图片素材3
图片占位符2 n 图片素材4 n
图片占位符3 n n n
由脚本解析表格:
-
对于功能宏,将 要编译的机型的 标有 y 的 功能宏 做成 宏 通过 cmake(可以生成一个导入这些宏到程序的 cmakelist 文件) 导入到程序里面(如
#define 功能宏a),这样就可愉快的在程序里面用。 -
对于文案,将当前编译的机器下标注 y 的文案 key 的 文本和翻译都包含到多语言数据,其它需要清除掉(因为多语言管理平台一般都是全量的)。
-
对于图片,根据 当前编译的机型,只把 标了 y 的按照程序识别的格式作为文件 通过 cmake 来 install 到指定目录,程序里面读取即可。
-
如果 UI 是 脚本语言 等 描述文件,则需要 写一些自动化脚本,根据机型,选择性使用 UI 页面 的描述文件。
页面管理
基本思路:
-
各个页面用 树结构 从上到下关联起来,根节点是个虚拟页面没有信息,根节点下面的所有一级节点是各个主页面,往下是各种子页面的纵深。公共页面(独立页面,为信息提示或者运行中不让进行其它操作,比如条款页面、升级页面等)也属于根节点下的一级节点。除了根节点,每个树的节点 对应一个具体的页面。
-
留出 注册新页面的函数(给页面名称、父页面等信息),删除页面节点(根据页面名称),切页函数(形参:目标页面、返回方法(返回上一级页面(默认),返回上一次页面)),执行返回,注册页面切换回调函数(返回源和目标页面,以及返回方法),获取当前页面 等 实用函数。
-
信息:通过 注册新页面的函数 构造各个页面节点的树结构,每个节点包含信息:页面名称、父节点(父级页面),另维护一个结构体信息:当前页面节点、上一次页面节点(从哪个切过来的)、当前页面返回方法(返回父页面,或 返回 上一次页面)。
一些比较多页面且为连续序列类型的功能(比如引导页,基本只有上一步和下一步的线性多页面序列),可以用程序来控制,写个通用框架,然后实例化为具体业务。
多分辨率
(可选)
UI 分辨率可以随意设置(其内部的各个 obj 的 固定 px 尺寸会随着变)。UI 尺寸可随意变化然后 obj 会合理伸缩并排版。
GUI 前端图形,设计为可多分辨率的,即只改动长宽,里面元素可以跟着变动。需要 交互设计 在设计 GUI 时候 就考虑分辨率的变化情况。
多套主题
(可选)
可以至少提供 light 和 dark 这 亮暗 两种风格;增加多种预设(可一键切换);可以想想增加灵活度,可以细节自定义,可以背景图片选择等,丰富选择,让每个用户屏幕都不一样更有个性。
埋点
概念和需求
下面是笼统的概念介绍
-
https://blog.csdn.net/weixin_39614276/article/details/111437770
-
https://www.xiaohongshu.com/explore/63bbe67c000000002203688c
前端埋点类型:
1.页面埋点:发生在页面上,在页面加载完成后触发或在页面退出时触发的埋点事件。
2.模块埋点:发生在单个页面的某个模块上点击和曝光的埋点事件,例如:按钮、图片、弹窗、浮层等。
3.组件埋点:发生在某个模块上且该模块被多个系统页面重复使用,营销大转盘。
4.其他埋点:发生在更多其他类型事件模版,例如:视频播放结束事件、扫码事件、蓝牙事件等。
变量命名规范
1)统一遵循小驼峰的命名规范:除第一 个单词之外,其他单词首字母大写;
2)统一格式为{业务}_ {组件|页面}_ {具体元素}, 比如finance_ .qualityHomePage_ repay;
触发时机说明
1)浏览/pageview:采集用户浏览页面相关的数据,用户打开该页面即会被记录一次;
2)点击/click: 采集用户的点击行为相关的数据,用户点击页面上某个按钮会被记录一次;
3)曝光/expose:采集某个页面/模块被看到的数据,用户有效浏览页面/模块会被记录一次;
-
前端埋点方式 https://blog.csdn.net/LuckyWinty/article/details/130939489
最直接有效的方式就是了解用户的行为,了解用户在网站中做了什么,呆了多久。
所谓'埋点'是数据采集领域(尤其是用户行为数据采集领域)的术语,指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。比如用户某个icon点击次数、观看某个视频的时长等等。
基于此我们可以知道埋点是实际上是对特定事件或者行为的数据监控和上报。
-
前端数据打点(埋点) https://blog.csdn.net/m0_70190404/article/details/128477172
-
前端埋点实现方案 https://huaweicloud.csdn.net/639fe937dacf622b8df8faa8.html
埋点就是在应用中特定的流程收集一些信息,用来跟踪应用使用的状况,后续用来进一步优化产品或是提供运营的数据支撑,包括访问数(Visits),访客数(Visitor),停留时长(Time On Site),页面浏览数(Page Views)和跳出率(Bounce Rate)。这样的信息收集可以大致分为两种:页面统计(track this virtual page view),统计操作行为(track this button by an event)。
两方面上报: 1.事件上报(目前只有点击事件埋点),2.停留时间上报
-
事件上报:通过给元素绑定自定义指令的方式实现(减少对原有代码的侵入),将信息存储在缓存池中定时上报,上报之后清空之前的上报信息。
-
停留时间上报:需要重新封装路由,创建路由拦截在跳转之前记录来源,以及上一个页面的停留时间,当拦截器捕获成功之后,如果发现停留时间大于xx秒进行上报。
-
-
背景,需求
-
用户行为统计,收集用户习惯,比如点击顺序或者点击热点。
-
目前设计比较复杂,找出用户不常用的,看是不是设计有问题。
-
打埋点的地方,同时也输出到调试日志。
-
埋点基本要求和流程:
-
交互人员给出埋点需求。
-
开发与云端数据人员指定上报信息协议,开发 (基本为 UI 上 按键、弹窗按键、开关、选择框、下拉框、进入页面(页面曝光)等要素的点击事件上报,且有的交互会需求再附加一些额外信息,不要有高频上报,控制数据量,产生数据可以先在内存或者 FLASH(可以分多个等大小的文件) 里面积累到一定程度再压缩 和加密再上传一次,以及 每个一段时间比如 6h 上传一次,并预留立即传送一次的接口用于测试)。
埋点机制尽量设置成可以方便的加减的形式,并且一些特殊事件需要手写的埋点代码也进行都放到绑定端(前后端可以留回调函数)之类的,而不是侵入、散落到各个地方。
-
云端数据形成看板进行展示。
-
数据看板给相关人员分析 ,制定下一步优化策略,改进并上架,收集反馈,迭代,等等。
埋点机制的开发,注意,埋点是观测,应该是非侵入的,不应该修改太多源程序的业务逻辑相关的等,尽量做到这种地步。
埋点数据基本需求
UI 埋点 基本需求:
必要上报信息:
-
控件名称 Name(保持全局唯一,可以用所在页面、所在功能/模块 等拼成)。
-
控件类型 Type:值如下
-
按键 Btn、导航按键 NavBtn、开关 Switch、选择框 CheckBox、选择框 ChooseItem、弹窗 PopDialog、弹窗按键 DialogBtn 等 点击事件。
-
页面切换 PageSwitch(从哪个页面进入哪个页面,页面曝光),(可选)页面停留时间。
-
语言切换 LangSwitch。
-
-
所在页面 page 名称,(可选)页面层级(如一级、二级,独立页面算一级)。
-
当前产品所处状态(如待机 Idle、工作中 Working、暂停中 Pause 等,工作中 还可增加细分当前具体任务 task 等)。
-
所在语言 lang,值为 语言的 code,如 en、zh-cn 等。
-
额外信息:如下 json,已经包含在
extra info里面-
对于 开关、复选框:上报信息 增加 选择后的状态,勾 或 没勾。
-
对于 下拉框:上报信息 增加 选择了哪一项(index(不推荐,因为不知道对应项是什么) 或 所在项的文本(英文原文,不要获取各种语言的)) 字段。
-
对于 弹窗 或 弹窗按键:上报信息 增加 所在弹窗 id 编号(或字符串等) 字段、弹窗类型(提示、警告、错误 等) 等;对于弹窗按键,控件名称 Name 可以只是弹窗按键的名字而不必全局唯一如 ok、close 等。
-
对于 切页:上报信息 增加 目标页面。
-
对于 切语言:上报信息 增加 目标语言。
-
-
设备基本信息:
-
机型。(如果序列号里面包含机型区分信息,则这个不用赘述)
-
设备序列号 SN。
-
当前时间,年月日时分秒+时区。
-
固件版本号。
-
硬件版本号。
-
用户 Id。(信息不得打印到 log,信息上网必须先加密)
json 例子:
{ "eventType" : "xxx_UITrack", "content" : { "Name" : "xxx", // widget name, dialogBtn name (for "popDialog") "Type" : "xxx", // Btn、NavBtn、Switch、CheckBox、ChooseItem、 // PopDialog、DialogBtn、PageSwitch、LangSwitch, or some "specific event name" ... "page" : "xxx", // the page now at. For "pageSwitch" itemType is the page that brfore switch "state" : "xxx", // I(idle)、R(running)、P(pause) ... "task" : "xxx", // the task now at, if "state" is "running" then have this "lang" : "xxx", // the language code now at // extra info: "isChecked" : "1" | "0", // for "Type" is "Switch"、"CheckBox" have this, the value shall not be like "true"/"checked"/1 .etc "chooseItem" : "xxx", // for "ChooseItem" to indicate the choosed item string(only en language) "dialogId" : "xxx", // for "PopDialog" and "DialogBtn", have this "dialogType" : "info" | "warn" | "err" ... // for "PopDialog" and "DialogBtn", have this "targetPage" : "xxx", // for "PageSwitch" have this "targetLang" : "xxx", // for "LangSwitch" have this, the target language code ... "specific event extra info" : "xxx", }, // device info ... } -
额外可选上报信息:
-
业务事件埋点 基本需求:如开关机、登陆和退登陆、开始升级和升级结果与升级时间等,以及很多具体产品业务相关的具体事件。
-
所在分组(用于分类)。
-
交互需求再附加一些额外信息:比如 设备中一些零配件的用户手动增减事件 等等。
-
一次机器从开始工作到结束工作之间,发生了哪些事情,汇总上报。
-
机器 coredump 埋点。每次每个模块进程 coredump 机器 均会尝试 重启这个 模块进程,但是这个事件我们要知道次数,并且还可以自动解析下 coredump 位置并放入埋点信息,好观察那一块出问题多、不稳定,更好针对性修复、优化。
注意点:
-
为了防止 key 填错,埋点函数 结构 均应用 枚举类 等 有限个选项 进行 选填,内部构造 json 时候再根据枚举再填充为具体的 str。
且要约束填入 valeu 的情况,不得无序增长,比如 isChecked 的 value 统一为 "1" | "0" 或 数字 1 | 0,而不应同时有 "1"、"true"、"checked" 等多种情况出现!
-
对于 switch、checkBox 的 附带信息 isChecked 是 变化后的 状态,若无特殊要求,计总数即可,统计分析时候,筛选出 isChecked 为 1 的 要相互对比的 switch 或 checkBox 即可,其之间的总数对比可反应倾向于使用的比例。
-
上报信息字符数量精简,上报信息不要重复,每一条精准,减少带宽,但也不要过于短而增加理解。
比如如果机器端只用枚举数字表示信息(比如 Type 中用 0 标识 Btn,以此类推)并且云端看板可以解析并显示更好。
还比如,机器在 idle 状态时候,上传信息 不必包含 task 任务类型 等,以此类推,举一反三。
压缩每条 json 长度,尽量短(减少 flash 寿命损耗,减少占用网络带宽,在 UI 按键频繁点击的情况下)。
-
应该埋 UI 的事件,若其它端的设置导致 UI 变化 不应该 埋点,UI 埋点 关注 用户操作这个 UI 的事件。对于功能使用埋点,则在后端某个公共的地方埋点更好。
-
埋点机制对系统影响测量:
-
对 UI 响应的延时进行测量。不加埋点和加埋点,按键等响应的时间差异测量。以不显著影响使用、用户无感 / 不会察觉为准,一般增加延时在 10ms 以内为宜,尽量小,在 小于 1ms ~ 5ms 为宜。
-
埋点数据写入文件系统的量的测量。如 高速操作屏幕时候 测量得到 埋点模块 每秒产生 1.5KB 数据 写入 文件系统,计算以这样速度连续写入 那么 EMMC 的寿命 可用多久;再长时间(如一周或更长)这样连续高频 模拟点击屏幕 产生 埋点信息 写入文件系统 测试。经验值,以 3Hz 点击 五天,对于 "每秒 增加 1.5KB 数据" 仅约为 800MB 左右,若用户每天点击 500次,要这样每天点击持续一年左右。
-
-
埋点数据正确性验证:
-
埋点数据正确性需要的特性:完整性(没有遗漏上报,也没有多上报,需要整条埋点产生、上报、提取、筛选的链路保证,需要整体前后(上下游)验证和测)、准确性(每个事件的上报信息正确,依靠编码程序保证)、时序性(信息上报的先后顺序准确,这个就通过上报信息带上时间来保证)。
-
数据完整性测试:
-
主动埋点产生:一台机器,以特定的 SN 序列号 等,在一段时间内(几个小时、一天 等),产生一定量的、确定的数据,然后在云端上看数据,是否与预期一致。
-
多端交叉验证:比如后端产生一个事件,后端进行埋点上报,同时前端 UI 也会根据这个后端事件进行埋点上报,针对这种 case,将二者的数据进行验证比较。
-
单纯从埋点数据来交叉验证:可以从按键必然的先后顺序来看,比如有的按键点击(按键点击事件埋点)后弹出某个特定弹窗(弹窗事件埋点)(这个弹窗只有ok按键,且只能点击ok来关闭弹窗,在此期间不能点别的),点击弹窗ok关闭弹窗(弹窗按键点击事件埋点),将这些数据进行对比分析。
-
-
-
可改进点:
-
积累一定数量/容量上传一次,或者使用 计数 字段 进行累计。
数据上报可不一条一条,而是固定时间间隔内统计所有埋点时间,合并相同的为计数数量,然后上传。时间间隔应该不用长,避免关机、断电等时候丢失过多数据。
如:针对某个事件,均会产生一摸一样的 一段 埋点信息 json(注意必须整个 埋点信息 json 一样才算,按键一样 但也许 机器所在的状态不一样 则也不算是同一个埋点事件),则不必每次都上传,先存起来 进行计数,增加 count 字段记录次数,当总数到一定数量(两方面,一种 埋点信息 的 count 到一定数量就单独传这一条,或者 各个 埋点信息 json 的 count 的 总数 达到某一个数量 则整体传一次)。
以及若中间无操作一定时间,才传一次埋点信息。
-
以固定时间间隔上传。埋点产生的数据可积累在 内存中 如 一分钟 或 十分钟,以固定时间间隔,然后上传云端一次,就免去写文件系统 对 EMMC 等 存储系统造成寿命影响。
-
发送给 埋点/log 管理的进程。该进程存储信息到某 log 文件里面,累积,到一定量后(只针对埋点等数据,用户已经同意数据收集协议的),执行高压缩,压缩文件名写好时间,立即上传云端,并留存本地,创建当前时间的存储信息的文件接着累积;当多个压缩包的数量到达一定时候,删除最老的。以此循环。
-
数据看板的组织形式
说明:
-
一个机型一个看板。
-
第二行 为多个下拉框筛选。task 在 state 不为 I 的时候才有。任何一个下拉筛选项均可选择 *,即这一项不筛选。
-
下拉框筛选 的 下面 为一行一行的筛选出来的 Name。对于不同的 Type 有不同的字段,以下每一行为一个区块进行独立排列,以不同的 key 的 value 计数 从大到小 排列(分别可选为总计数 和 按照 sn(机器序列号)去重计数(即每个 sn 只算一次)),如下。
| page ▼ | Type▼ | state▼ | task▼ | lang▼ | 计数 (从大到小排列) | 按照sn去重计数 (从大到小排列) | 时间间隔选择: | from xxx to xxx | |||
| Type: 以下每一行为一个区块进行独立排列 | |||||||||||
| Btn NavBtn (按 Name 计数排列) | Name1 Name2 ... | ||||||||||
| Switch CheckBox (按 Name 计数排列) | Name | isChecked | |||||||||
| ChooseItem (按 Name 计数排列) | Name | chooseItem | |||||||||
| PageSwitch (按 targetPage 计数排列) | Name | targetPage | |||||||||
| PopDialog (按 dialogId 计数排列) | Name | dialogType | dialogId | ||||||||
| DialogBtn (按 dialogId 计数排列) | Name | dialogType | dialogId | ||||||||
| LangSwitch (按 targetLang 计数排列) | Name | targetLang | |||||||||
| ... |
UI 测试 / 自动化测试
功能自动化测试基础
-
更新 UI 的 通讯接口均留出,可设计 系列 假数据 通过 通讯接口传入,可以写一些 case 来 自动化 按序列 执行,观察 UI 是否符合预期。case 可以 尽量考虑多的情况,各种组合等。
-
各组件压测,写测试代码,放在机器上,高并发的创建、调用各种 api,释放,循环此大量次,看是否有 内存泄漏、crash 等问题。
内存泄漏的一些常用检测 和 debug 手段,参考 Cpp-Learning/编程经验-规范, 调试、性能和内存检查工具集合.md at main · Staok/Cpp-Learning。
UI 测试的基础组件需求 - 一般都需要,都有用
-
显示或打印当前点击的坐标(用于知道 UI 上这个按键的点击的坐标点)。
-
模拟点击。可以通过通讯控制 ui 上是否显示 当前点击的坐标(ui 左下角显示)从而直到要点击的未知,然后可以 将这些坐标记录下来,通过 通讯控制 来 进行 一系列的 模拟点击。
-
monkey。随机生成点击坐标进行模拟点击;随机操作要做到尽量随机(比如每次开始的时候设置随机种子为当前时间),每次运行路径不同)。
-
截屏并编码为 jpeg 保存到文件系统。
-
开始测量 FPS,实时获取 当前、最小、最大、平均的 FPS 等。
(可选)额外获取 渲染时间、写屏时间等。
-
显示 CPU 占用。
-
显示内存占用。
屏幕测试
需要搞一个界面,用于生产线上的屏幕测试和筛选。每一项测试的进入可以用 mqtt 等命令,测试均有 测试成功、测试失败 的条件,要显示对应弹窗,弹窗上按键选择 是否通过。选择了不通过的机器会被标记从而从流水线上下来进行返修。
注意:这些测试项可以设计 机械结构(机械装置控制触摸屏,都是一些固定路径点击)和 视觉检测 都进行 自动化。
-
亮度调节测试。
屏幕上一拖动条,0~100% 控制亮度,由最大到最小再到最大拖动进行测试。
-
显示测试。
-
显示一个屏幕坏点测试图。
可以用程序生成一张画面在屏幕上显示,来凸显屏幕上的坏点。工厂测试需要。
图像:长宽与屏幕显示一致,图像的从头到尾每一个像素要落在屏幕的对应像素上;图像从左上开始第一行两种颜色交替,绿(RGB:0, 128, 0),红 (RGB:128, 0, 128),第二行第一个像素与第一行第一个像素再交替,以此类推,形成类似马赛克的模式。
-
多种颜色切换测试。
按照如下颜色切换,看屏幕是否有坏点。
"red","green","blue","black","white" 0xFF0000 0x00FF00 0x0000FF 0x000000 0xFFFFFF
-
-
触摸测试。
-
在屏幕上按照给定的一个 井字 图形,两横两竖 进行触摸画线,需要判断起始(按下)、中间(连续按压)和结束(抬起)的坐标是否都在合理范围内。
-
空白界面任意画线,加个清除按键。
-
跨平台化
把工程整成 在 Win上 或 Linux(如 Wsl2、 Ubuntu) 平台 上可直接一键编译运行。
优点:
-
可 快速 查看效果、UI 调试、迭代。
-
UI 与 后端 的 接口 分离清晰。
这样便于 给假数据进行高度自动化的测试,尽量多想 case,快速暴露大量问题和快速迭代解决。可快速集成一些自动化测试,给假数据较全面的覆盖测试,快速收敛稳定性。
-
交互等非开发人员可以方便的直接看效果,不用再部署到机器上才看到效果,或者整成 设计稿到部署机器 自动化 执行。
-
更好的自动化一些需要跨平台的流程,如 UI 设计到快速实机显示、编译打包、多机型、多语言、素材管理 等等。
需要,也是方案:
-
文件级别 的 平台差异的代码,对应 Linux 和 Win 都各一份,Win 的可以简单快速编写和给假数据,用 CMake 中的平台区分控制编译哪些。但是这种差异,自己写的时候应该避免,因为并没有很多差异大到整个文件都大不相同的地方,除了一些三方库。
-
代码层面 的 平台差异的代码,程序中用
_WIN32、__ linux __之类的平台区分宏来控制,同样 Win 的可以简单快速的编写和给假数据。 -
使用的不少库,Linux 平台可 Buildroot / apt + CMake + PkgConfig 等工具快速集成;Win 平台基本是 MSYS + CMake + PkgConfig。可源码级别的编译并链接库,尽量选用 Win 和 Linux 平台通用的库。
UI 硬件加速渲染
需要参考 具体 UI 框架的官方文档,了解支持情况。
比如 对于 LVGL(需要跟进最新官方支持情况):
渲染相关:
- lvgl 的 lv_conf.h 里面支持有 LV_USE_GPU_XXX 相关的宏,可试试。arm linux 端可用的看起来有 LV_USE_GPU_ARM2D、LV_USE_GPU_SDL。
解码显示相关:
-
解码图片这个耗时操作,对图片多做缓存,不必每次重复解码。在内存允许的情况下。
-
lvgl 调用 ffmpeg 的 API 来解码和显示图片和视频,ffmepg 可能会调用硬件加速,可试试。
Mem & CPU & Size 优化 & Debug
-
参考 具体 UI 框架 官方文档 的 测试/Debug/评估/优化 相关的章节。
-
基本的,编译去掉一些 Warnnig。
-
去掉无关内容、废弃内容。保持简洁。代码中无关的打印去掉或者注释掉。
-
遇到性能瓶颈,考虑优化 内存占用 和 cpu 占用。
-
一些占用内存却用不到的进行优化,比如加载大量文本时候在关闭时候释放掉,只显示一次的页面只在打开的时候进行各种配置而不必常驻内存,等等。
-
各进程的 cpu 占用、内存占用监控。UI渲染刷新 帧率监控。这些监控的频率可以不同,比如有的一秒、有的一分钟、一小时等。
若UI渲染刷新帧率小于某值,或者是两次刷新间隔大于某值,则打印信息告警。
打印 log 带上 距离上一次打印经过的毫秒时间,可以看出哪些地方运行严重拖慢。
-
参考 编程经验-规范, 调试、性能和内存检查工具集合 其中的 易错注意 和 使用工具进行静、动态检查 相关的章节,优化程序健壮性,优化 cpu占用、内存占用。
-
参考 编程经验-规范, 调试、性能和内存检查工具集合 其中 的 优化 相关章节。
-
-
节省 编译后 bin 大小 的一些想法:视情况而定
-
字体用字体文件,显示字体用 freeType 库。
-
图片就用本地存储的图片文件,解码显示,并应用缓存机制防止多次重复解码,图片要经过压缩优化大小(自动化批处理)并且不明显损失质量。
-
代码规范
首先 大的工程结构 和 架构 定好,要有足够的 可快速开发迭代、易扩展 等 特性。
然后代码规范,分为 代码格式 和 各部分实现套路 两个层面。
代码格式:制定 和 按照基本规范来约束。可 clang-format 工具整理代码格式(每个工程有一份 clang-format 配置文件)。
各部分实现套路:分具体软件工程中各个层次(分好层次),后端数据结构、通讯、绑定端、前端 UI 等等。具体清空具体搭建,怎么方便怎么来。
参考 编程经验-规范, 调试、性能和内存检查工具集合 其中的代码规范部分。