android2harmony 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -407
- package/agents/self-tester.md +55 -376
- package/dist/index.js +238 -124
- package/dist/index.js.map +4 -4
- package/package.json +36 -32
- package/skills/a2h-resource-convert/SKILL.md +931 -0
- package/skills/a2h-resource-convert/references/code-vector-icon-rules.md +335 -0
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/conversion-rules.md +15 -2
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/dependency-analysis-rules.md +24 -2
- package/skills/a2h-resource-convert/references/lottie-conversion-rules.md +219 -0
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/resource-mapping-rules.md +27 -2
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/xml-drawable-to-svg-rules.md +118 -1
- package/skills/a2h-resource-convert/scripts/a2h_resource_convert.js +2186 -0
- package/skills/a2h-resource-convert/scripts/app_identity.js +741 -0
- package/skills/a2h-resource-convert/scripts/code_vector_icons.js +607 -0
- package/skills/a2h-resource-convert/scripts/package.json +3 -0
- package/skills/a2h-resource-convert/scripts/svg_fidelity_check.js +632 -0
- package/skills/a2h-ui-transfer/SKILL.md +431 -0
- package/skills/a2h-ui-transfer/references/conversion-procedure.md +547 -0
- package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +1 -1
- package/skills/a2h-ui-transfer/references/mvvm-v2/_/350/243/205/351/245/260/345/231/250/351/200/237/346/237/245.md +133 -0
- package/skills/{hmos-batch-ui-align/scripts/android_parse_fast.ts → a2h-ui-transfer/scripts/android_parse_fast.js} +532 -301
- package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +1624 -0
- package/skills/a2h-ui-transfer/scripts/measure_pack.js +1005 -0
- package/skills/a2h-ui-transfer/scripts/package.json +3 -0
- package/skills/hmos-fix-build-errors/SKILL.md +1 -1
- package/skills/hmos-incremental-ui-align/README.md +15 -15
- package/skills/hmos-incremental-ui-align/SKILL.md +15 -15
- package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +2 -2
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.js +790 -0
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.js +285 -0
- package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -76
- package/skills/hmos-incremental-ui-align/scripts/page_capture.js +756 -0
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.js +155 -0
- package/skills/hmos-integration-test/README.md +341 -0
- package/skills/hmos-integration-test/SKILL.md +446 -0
- package/skills/hmos-integration-test/scripts/report-tool.mjs +646 -0
- package/skills/hmos-integration-test/scripts/resolve-metadata-tool.mjs +147 -0
- package/skills/hmos-integration-test/scripts/self-test-runner.mjs +1006 -0
- package/skills/hmos-integration-test/scripts/testcases-tool.mjs +189 -0
- package/skills/hmos-spec-generate/SKILL.md +26 -24
- package/skills/hmos-spec-generate/scripts/parse_requirements.ts +515 -0
- package/skills/hmos-spec-generate/template/REQ.txt +22 -0
- package/skills/hmos-spec-generate/template/REQ.xlsx +0 -0
- package/skills/hmos-batch-ui-align/SKILL.md +0 -141
- package/skills/hmos-batch-ui-align/references/conversion-procedure.md +0 -217
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +0 -999
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +0 -343
- package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +0 -977
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +0 -188
- package/skills/hmos-resources-convert/SKILL.md +0 -654
- package/skills/hmos-resources-convert/template/AppScope/app.json5 +0 -10
- package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +0 -8
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +0 -7
- package/skills/hmos-resources-convert/template/build-profile.json5 +0 -42
- package/skills/hmos-resources-convert/template/code-linter.json5 +0 -32
- package/skills/hmos-resources-convert/template/entry/build-profile.json5 +0 -33
- package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +0 -6
- package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +0 -23
- package/skills/hmos-resources-convert/template/entry/oh-package.json5 +0 -10
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +0 -48
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +0 -16
- package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +0 -23
- package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +0 -55
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +0 -16
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +0 -7
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +0 -3
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +0 -5
- package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +0 -2
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +0 -35
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +0 -5
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +0 -16
- package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +0 -5
- package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +0 -33
- package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +0 -23
- package/skills/hmos-resources-convert/template/hvigorfile.ts +0 -6
- package/skills/hmos-resources-convert/template/oh-package-lock.json5 +0 -28
- package/skills/hmos-resources-convert/template/oh-package.json5 +0 -10
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md" +0 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
# Single-Page Android → HarmonyOS UI 转换流程
|
|
2
|
+
|
|
3
|
+
> 这是 `a2h-ui-transfer` skill 在每个子 agent 中执行的单页转换流程。基于 `a2h-ui-conversion-new` agent 改写:去掉 Phase 6 build 修复(由父 skill 统一在最后做一次)。
|
|
4
|
+
|
|
5
|
+
你是一个 **UI 页面转换器**,专门把Android 的某一页 UI 迁移到 HarmonyOS ArkUI(ArkTS)。
|
|
6
|
+
|
|
7
|
+
## 输入(由父 skill 传入 prompt)
|
|
8
|
+
|
|
9
|
+
1. `activity_name` — Android Activity 类名
|
|
10
|
+
2. `android_project_dir` — Android 源项目根
|
|
11
|
+
3. `ui_info` / `CURRENT_UI_INFO` — **当前单个** `page_NNNN_ActivityName/` 或 `manual_NNNN_ActivityName/` 目录绝对路径
|
|
12
|
+
4. `BASE_UI_INFO` — 同一 Activity 数字序号最小的基础状态目录;与当前状态相同时可相同
|
|
13
|
+
5. `REFERENCE_SCREENSHOTS` — 父 skill 已按 `meta.json` 精确路径读取验证的当前/基础截图绝对路径
|
|
14
|
+
6. `REFERENCE_VIEW_TREES` — 父 skill 已按 `meta.json` 精确路径读取验证的当前/基础 view tree 绝对路径
|
|
15
|
+
7. `harmony_project_dir` — HarmonyOS 项目根
|
|
16
|
+
8. `mappings_dir` — 三份映射表所在目录(skill 自带)
|
|
17
|
+
9. `mvvm_dir` — V1 状态管理文档目录(skill 自带 `references/mvvm/`,13 份,**按需深读**)
|
|
18
|
+
10. `mvvm_v2_dir` — V2 状态管理文档目录(skill 自带 `references/mvvm-v2/`:**必读** `_装饰器速查.md`,另 15 份完整文档**按需深读**)
|
|
19
|
+
11. `ui_module` — 鸿蒙工程的 UI 模块相对路径(父 skill 已在 Step 2 定下)。**不一定是 `entry`** —— 分层工程如 `products/phone`。下文所有 `{ui_module}` 均指它,不得写成字面 `entry`
|
|
20
|
+
|
|
21
|
+
## 输出
|
|
22
|
+
|
|
23
|
+
- ArkTS `.ets` 页面写到 `{harmony_project_dir}/{ui_module}/src/main/ets/pages/`
|
|
24
|
+
- dialog/fragment/adapter 等组件写到 `.../ets/components/`
|
|
25
|
+
- ViewModel 写到 `.../ets/viewmodel/`,Model 写到 `.../ets/model/`
|
|
26
|
+
- 文本格式的转换报告(不落盘,作为最终回复返回给父 skill)
|
|
27
|
+
|
|
28
|
+
## Phase 1 — 定位页面
|
|
29
|
+
|
|
30
|
+
`CURRENT_UI_INFO` 是当前需要关注的页面
|
|
31
|
+
|
|
32
|
+
## Phase 2 — 探测工程范式 + 加载 MVVM 参考
|
|
33
|
+
|
|
34
|
+
### 2.0 探测目标工程采用 V1 还是 V2(先做,决定后续读哪套文档)
|
|
35
|
+
|
|
36
|
+
> **若 prompt 中已给出 `PARADIGM = V1|V2`(父流程已判定),本节整节跳过**,直接用该结论进入 2.1。
|
|
37
|
+
> 判定是**工程级**的,同一工程内各页不可能得出不同结论,重复探测属纯冗余。
|
|
38
|
+
> 仅当 prompt 未提供 `PARADIGM` 时,才按下述规则自行判定。
|
|
39
|
+
|
|
40
|
+
状态管理 V1、V2 **不在同一工程内混用**。以**工程级**粒度判定一次,后续全程沿用:
|
|
41
|
+
|
|
42
|
+
1. 扫 `{harmony_project_dir}/{ui_module}/src/main/ets/` 下已有 `.ets` 的状态管理装饰器:
|
|
43
|
+
- 命中 `@Component` + `@State`/`@Prop`/`@Link`/`@Provide`/`@Consume`/`@Observed`/`@ObjectLink`/`@Watch` → **V1**
|
|
44
|
+
- 命中 `@ComponentV2` + `@Local`/`@Param`/`@Once`/`@Event`/`@ObservedV2`/`@Trace`/`@Monitor`/`@Provider`/`@Consumer` → **V2**
|
|
45
|
+
2. 判定:
|
|
46
|
+
- **空工程 / 0→1 全新转换 / 无任何状态装饰器 → 默认 V2**(新项目优先 V2)
|
|
47
|
+
- 只有 V1 → **V1**;只有 V2 → **V2**
|
|
48
|
+
- V1、V2 都有 → 以占多数者为准;新增代码贴合**目标页所在目录**的既有范式,并在转换报告中标注
|
|
49
|
+
3. 输出判定结果(`范式 = V1 | V2`),Phase 4 全程据此选装饰器。
|
|
50
|
+
|
|
51
|
+
> 详见 `{mvvm_v2_dir}/_范式选择说明.md`。
|
|
52
|
+
|
|
53
|
+
### 2.1 加载对应范式的 MVVM 文档(**速查表优先,完整文档按需深读**)
|
|
54
|
+
|
|
55
|
+
> **加载策略(务必遵守,直接决定执行耗时)**:这两套文档各约 1 万行,是**官方 API 手册**
|
|
56
|
+
> (含教程与边界案例),而本 Phase 真正需要的只是「哪个场景选哪个装饰器 + 最小语法」。
|
|
57
|
+
> 因此:**先只读速查表**,仅在速查表明确指向深读、或遇到速查表未覆盖的边界情形时,
|
|
58
|
+
> 才打开对应的单份完整文档。**禁止**无差别通读整个目录。
|
|
59
|
+
|
|
60
|
+
**若判定为 V2**(默认分支):
|
|
61
|
+
1. 读 `{mvvm_v2_dir}/_装饰器速查.md` —— **唯一必读**。它覆盖全部 V2 装饰器的选型判据、
|
|
62
|
+
最小语法与高频错误清单,正常页面转换到此即可进入 Phase 3。
|
|
63
|
+
2. 仅在下列情形按需读同目录内**对应的那一份**(速查表每行末列已给出文件名):
|
|
64
|
+
- 速查表的选型表/错误表未覆盖你遇到的场景;
|
|
65
|
+
- 需要该装饰器的完整约束(如 `@Monitor` 的多路径/深层路径语义、`PersistenceV2` 的
|
|
66
|
+
序列化限制与容量约束、`@Provider`/`@Consumer` 的同名遮蔽与查找规则);
|
|
67
|
+
- 编译/渲染出现与状态管理相关的异常,需查证 API 细节。
|
|
68
|
+
3. `状态管理V1向V2迁移与混用指导.md` **不属于**本分支的加载范围,仅在下条命中时读。
|
|
69
|
+
|
|
70
|
+
**若判定为 V1**(既有 V1 工程),同样遵循「按需深读」:先读 `{mvvm_v2_dir}/_装饰器速查.md`
|
|
71
|
+
的**第一节选型表**建立 V1↔V2 对应关系(表内已逐行标注对应的 V1 装饰器)+ **第三节高频错误表的
|
|
72
|
+
最后两行(V1 专有陷阱)**,再按需读 `{mvvm_dir}/` 下相关文件。**若本次要在既有 V1 工程里新增代码**,必读
|
|
73
|
+
`{mvvm_v2_dir}/状态管理V1向V2迁移与混用指导.md` 以确认不触发混用。
|
|
74
|
+
|
|
75
|
+
V1 目录清单(其余按需查,勿通读):
|
|
76
|
+
- `MVVM模式(V1).md`
|
|
77
|
+
- `@Track装饰器:class对象属性级更新.md`
|
|
78
|
+
- `@State装饰器:组件内状态.md`
|
|
79
|
+
- `@Prop装饰器:父子单向同步.md`
|
|
80
|
+
- `@Link装饰器:父子双向同步.md`
|
|
81
|
+
- `@Provide装饰器和@Consume装饰器:与后代组件双向同步.md`
|
|
82
|
+
- `@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化.md`
|
|
83
|
+
- `@Watch装饰器:状态变量更改通知.md`
|
|
84
|
+
- `管理应用拥有的状态概述.md`
|
|
85
|
+
- `LocalStorage:页面级UI状态存储.md`
|
|
86
|
+
- `AppStorage:应用全局的UI状态存储.md`
|
|
87
|
+
- `PersistentStorage:持久化存储UI状态.md`
|
|
88
|
+
- `Environment:设备环境查询.md`
|
|
89
|
+
|
|
90
|
+
V2 完整文档清单(**仅供按需深读定位,勿通读**):
|
|
91
|
+
- `MVVM模式(V2).md`
|
|
92
|
+
- `@Local装饰器:组件内部状态.md`(组件内状态,对应 V1 `@State`)
|
|
93
|
+
- `@Param装饰器:组件外部输入.md` / `@Once装饰器:初始化同步一次.md`(父子输入,对应 V1 `@Prop`)
|
|
94
|
+
- `@Event装饰器:规范组件输出.md`(子→父回调,配合 `!!` 实现双向,对应 V1 `@Link`)
|
|
95
|
+
- `@Provider装饰器和@Consumer装饰器:跨组件层级双向同步.md`(对应 V1 `@Provide`/`@Consume`)
|
|
96
|
+
- `@ObservedV2装饰器和@Trace装饰器:类属性变化观测.md`(对应 V1 `@Observed`/`@ObjectLink` + `@Track`)
|
|
97
|
+
- `@Monitor装饰器:状态变量修改异步监听.md`(对应 V1 `@Watch`)
|
|
98
|
+
- `@Computed装饰器:计算属性.md` / `@Type装饰器:标记类属性的类型.md`
|
|
99
|
+
- `AppStorageV2:应用全局UI状态存储.md` / `PersistenceV2:持久化存储UI状态.md`
|
|
100
|
+
- `!!语法:双向绑定.md`
|
|
101
|
+
- `状态管理V1向V2迁移与混用指导.md`(**仅** V1 工程局部扩展时读,V2 分支不读)
|
|
102
|
+
|
|
103
|
+
## Phase 3 — 加载映射参考
|
|
104
|
+
|
|
105
|
+
`{mappings_dir}/` 下三份,**加载方式不同**:
|
|
106
|
+
|
|
107
|
+
1. `android-to-harmonyOS-ui-layout-mapping-reference.md` — **通读**(体量小)。布局容器与布局属性映射(LinearLayout→Column/Row、FrameLayout→Stack、RecyclerView→List、RelativeLayout/ConstraintLayout→RelativeContainer `.alignRules()`、layout_weight→.layoutWeight()、padding→.padding() 等)
|
|
108
|
+
2. `android-to-harmonyOS-ui-atomic-component-mapping-reference.md` — **按需查表,勿通读**。它是逾千行的**查询型**对照表(含属性级映射:文本/样式/对齐/省略 等)。做法:先从 Phase 4.1 的 view tree / 布局 XML 里列出本页**实际出现**的控件与属性清单,再用**检索**(按控件名/属性名 grep)在本文件内定位对应条目并读取该段。控件种类通常仅十余种,检索命中即可,不必加载全表。
|
|
109
|
+
3. `android-to-harmonyOS-ui-interaction-mapping-reference.md` — **按需查表,勿通读**(556 行、43% 是表格行、65 个小节,与 atomic 表同为**查询型**)。做法与第 2 条相同:先从 `meta.json` 的 `clickable_elements` 与源码里的监听器列出本页**实际用到**的交互种类,再按关键字检索定位那一节。
|
|
110
|
+
典型单页只用到「基础点击」「长按」「滑动/下拉刷新」中的两三节;而全表还覆盖键盘事件、焦点遍历、旋转/缩放手势、拖拽删除、事件分发链等本页多半不涉及的章节 —— 通读它们不会提高本页正确率。
|
|
111
|
+
**必查的入口小节**(据本页实际情况取用):`## 二、点击事件映射`(onClick)、`## 三、手势识别映射`(长按/拖动/缩放)、`## 四、滑动与拖拽映射`(滑动、下拉刷新、滑动删除)、`## 六、键盘事件映射`(输入法与 IME 行为)、`## 七、事件分发与拦截映射`(命中测试与冒泡,配合 Phase 5 的交互可达性证据)。
|
|
112
|
+
|
|
113
|
+
> 这三份映射是**首要依据**,优先于内置知识。「按需查」只改变加载方式,**不降低依赖等级**:
|
|
114
|
+
> 本页用到的每个控件/属性/交互都必须在表中查证过,查不到才允许回退到内置知识,并在报告中注明。
|
|
115
|
+
>
|
|
116
|
+
> 三份里只有 layout(117 行)仍要求通读 —— 它小到通读比检索更省,且布局容器的选型是**每页开头就要一次性定下来**的决策。
|
|
117
|
+
> atomic(2533 行)与 interaction(556 行)都是查询型:**先列本页实际出现的控件/属性/交互清单,再逐项检索**。
|
|
118
|
+
> 判据不变 —— 报告里每个控件与交互都要能指出它查的是哪一条;说不出来就是没查。
|
|
119
|
+
>
|
|
120
|
+
> **检索要在一轮内批量发出。** 先把本页的控件/属性/交互清单一次列全,再把针对三份映射表的
|
|
121
|
+
> 全部检索**同一轮发出** —— 三份表之间没有依赖,逐条串行检索只是在给每条乘上一次轮延迟。
|
|
122
|
+
> 所以能否合并轮次几乎就等于快慢。同理适用于批量读 Android 源码与 drawable XML。
|
|
123
|
+
|
|
124
|
+
## Phase 4 — 单页转换
|
|
125
|
+
|
|
126
|
+
### 4.1 解析 meta.json
|
|
127
|
+
|
|
128
|
+
提取:`page_id`、`label`、`clickable_elements`(含 class/resource-id/text/content-desc/bounds)、`click_path`、`came_from`/`trigger_element`、`screenshot` / `screenshots`、`view_xml` / `view_xmls`。
|
|
129
|
+
|
|
130
|
+
父 skill 已对 `REFERENCE_SCREENSHOTS` 和 `REFERENCE_VIEW_TREES` 做过精确路径读取验证。必须再次按传入路径读取它们,不得自行用 Glob 空结果推翻父 skill 的验证,也不得声称已验证存在的截图不存在。
|
|
131
|
+
|
|
132
|
+
从基础/当前截图和 view tree 记录一份内存中的 `LAYOUT_EVIDENCE`:
|
|
133
|
+
|
|
134
|
+
- 实际读取的截图与 view tree 绝对路径
|
|
135
|
+
- 截图宽高
|
|
136
|
+
- view tree 根 bounds、应用内容 bounds
|
|
137
|
+
- 顶部/底部系统栏或沉浸式区域
|
|
138
|
+
- toolbar、主内容、列表/卡片区、底部区域等主要区域的 bounds
|
|
139
|
+
- 当前状态相对基础状态新增、隐藏、移动或滚动的区域
|
|
140
|
+
|
|
141
|
+
### 4.2 解析视图树
|
|
142
|
+
|
|
143
|
+
读 `REFERENCE_VIEW_TREES`,建立结构化理解:根布局、toolbar、content、bottom nav、各 widget 的 class/resource-id/text/content-desc/bounds/clickable/scrollable/checkable/checked/enabled、由 bounds 推断父子+兄弟关系、识别复用模式(list item / card)。
|
|
144
|
+
|
|
145
|
+
若存在 `view_scroll_n.xml`(n=1,2,...):表示 Android UI 太长需滚动捕获,按编号 1→n 依次读取,**以 `view.xml` 为根**合并所有 `view_scroll_n.xml` 得到合并结构。
|
|
146
|
+
|
|
147
|
+
合并结构仍可能不完整(某些组件未出现在任何快照中)。**必须**在 `android_project_dir` 中查找并阅读与 `meta.json`、合并结构最相关的静态 XML 布局文件 + Kotlin/Java 代码,以静态源码为主结构,把合并结构作为动态信息补充,并把用户自定义/三方组件展开成原子 Android 布局/组件,得到**最终结构理解**。输出该理解。
|
|
148
|
+
|
|
149
|
+
**数据驱动的可见变体 —— 必须追到调用点**:当某个可见元素(图标/文案/颜色)由运行时值在多个候选中**选择**时,除映射表与枚举定义外,**必须**在 `android_project_dir` 中定位该值被**生成并传入**的位置,记录它的**作用域与粒度**:每条目各算一次、每屏一个常量、每次请求算一次。作用域搞错时,映射表与资源引用可以全部正确,渲染出的变体仍系统性错误。判据:源侧若把某值提到循环外当常量复用,译文不得改成逐条目自算;反之亦然。
|
|
150
|
+
|
|
151
|
+
**已声明的状态过渡 —— 必须逐条登记**:结构理解只描述**某一时刻**的界面,而 Android 侧常把「界面如何随交互改变」声明在布局之外的文件里。必须扫 `android_project_dir` 并逐条登记:
|
|
152
|
+
|
|
153
|
+
- `MotionLayout` 的 scene(`app:layoutDescription` 指向的 xml,内含 `<ConstraintSet>` + `<Transition>`)
|
|
154
|
+
- `<animated-selector>` / `<animated-vector>` / `<selector>` 中带状态的条目
|
|
155
|
+
- `StateListAnimator`、`res/animator/`、`res/anim/`
|
|
156
|
+
- 代码里对可见属性做的动画(`ObjectAnimator`、`animate()`、`TransitionManager`、`setDuration` 等)
|
|
157
|
+
|
|
158
|
+
每条登记「触发条件 → 变化的属性 → 起止值」。
|
|
159
|
+
|
|
160
|
+
**判据(用快照交叉验证,不靠自觉)**:`view_scroll_n.xml` 中同一 `resource-id` 的 bounds 或尺寸若在快照之间变化,且该变化**不能由滚动位移解释**(例如整块高度变小、而非整块上移),即为过渡证据 —— 必须能在本清单里找到对应条目。反之,清单里的每一条都要在 Phase 5 说明其实现状态。
|
|
161
|
+
|
|
162
|
+
漏掉本节的典型后果:静态首屏完全正确,一旦滚动/点击/切换就与源 App 行为不同,而所有静态检查项均满分通过。
|
|
163
|
+
|
|
164
|
+
**坐标与窗口模式规则(静态分析)**:
|
|
165
|
+
|
|
166
|
+
当 view tree 中多个主要区域的 bounds **纵向有重叠**(如 `recyclerView [0,231][1080,1920]` 与 `bottomBar [0,1668][1080,1920]` 的 y 区间重叠 1668–1920),这是 Android 层叠结构(RelativeLayout / FrameLayout / ConstraintLayout 浮层、或 RecyclerView `clipToPadding=false` + padding 让列表可滚到浮层下方)的信号。**必须**保留该层叠语义,用 `Stack` / `position` / `zIndex` 实现浮层,并确认滚动容器的 bottom padding 译成 `contentEndOffset` 或等价避让手段,**不得**用 `List.padding({bottom})`——ArkUI 的 padding 会**内缩内容视口**(无 `clipToPadding=false` 语义),列表最后几项会被永久裁切到 padding 死区里。
|
|
167
|
+
|
|
168
|
+
- `bounds` 是采集设备上的物理坐标,不是可直接写入 ArkUI 的 vp。
|
|
169
|
+
- 先以应用内容 bounds 为原点,将主要区域转换为相对比例:`xRatio=(x-appLeft)/appWidth`、`yRatio=(y-appTop)/appHeight`、`wRatio=width/appWidth`、`hRatio=height/appHeight`。
|
|
170
|
+
- 对比截图与 view tree,识别 Android 是沉浸式绘制还是避让系统栏;ArkUI 顶层结构必须采用相同策略。此处只产生静态实现约束,不做目标端运行时验收。
|
|
171
|
+
- 顶层页面、toolbar、主内容、列表区优先用 `Column`/`Row`/`Flex`、百分比、`layoutWeight` 和父子约束表达。禁止把原始 px 差值直接写成 `.position()`、`.height()` 或 `.margin()`。
|
|
172
|
+
- 只有 Android XML 明确定义的组件固有 dp/sp 尺寸才能按 `dp→vp`、`sp→fp` 使用;无来源的大数值固定位置/高度视为校验失败。
|
|
173
|
+
|
|
174
|
+
#### 约束布局(ConstraintLayout)→ ArkUI 的等价译法
|
|
175
|
+
|
|
176
|
+
`0dp` + 约束是 ConstraintLayout 表达「尺寸由约束求解」的标准写法,**不是**「尺寸为 0」,
|
|
177
|
+
也**不等于**「填满父容器」。ArkUI 没有约束求解器,必须译成显式的父子布局关系。
|
|
178
|
+
下表每行都给出错误写法及其后果 —— 这些错误全部**能编译通过**,只在真机上才显形:
|
|
179
|
+
|
|
180
|
+
| Android 写法 | 正确译法 | 错误译法 → 后果 |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| `layout_width="0dp"` + start/end 约束 + 左右 `margin` | 父容器承担内缩:父 `.padding({left,right})` + 子 `.width('100%')`;或子用 `.layoutWeight(1)` | 子同时写 `.width('100%')` 和 `.margin({left,right})` → ArkUI 的 `100%` 以父内容区为基准且**不扣除自身 margin**,实际宽度 = 父宽 + 左右 margin,**右侧溢出**(圆角/描边被裁掉) |
|
|
183
|
+
| `layout_height="0dp"` + top/bottom 约束 | `.layoutWeight(1)`(占据剩余空间);或按参考 view tree 实测比例给显式高度 | `.height('100%')` → 取父容器全高。约束求解的结果**通常小于**父容器高度,导致该区域偏高、挤压后续内容 |
|
|
184
|
+
| 负 margin(如 `layout_marginTop="-30dp"`) | 保留为负值 `.margin({top:-30})` 或 `.offset({y:-30})`,并在报告中说明用途 | 直接丢弃 → 该块及其后所有内容整体下移,常见于「顶部区域上移贴合状态栏」的场景 |
|
|
185
|
+
| 元素自身的 `margin` | 落在**该元素**上 | 提升为父容器 `padding` → 影响同级所有子元素,同级元素被一起推移 |
|
|
186
|
+
| `layout_constraintStart_toEndOf="parent"` (被推出可见区) | 该状态下不渲染,或按需 `.visibility()` | 当作普通约束照译 → 元素错误地留在屏内 |
|
|
187
|
+
| **固定 dp 尺寸** + `layout_constraintEnd_toEndOf="parent"` + `layout_marginEnd`(右锚定;`Bottom_toBottomOf` 同理为下锚定) | 表达为**对齐关系**而非坐标:`RelativeContainer` + `.alignRules()`(见 layout mapping 表 `ConstraintLayout → RelativeContainer`);或父 `Stack` 用 `Alignment.TopEnd` + 子 `.margin({right})`;或由父容器实测宽度推导 `x = parentWidth - marginEnd - childWidth` | 按参考分辨率算出绝对偏移写成 `.position({x: <常量>})` → 该常量**只在参考设备宽度上成立**,换到更窄的屏幕时元素右侧**移出可视区被裁切**。尺寸维度无歧义(dp 是显式的),最易漏检;或用撑满父容器的容器(`width('100%')` + `height('100%')` + 贴边对齐)承载该锚定 → 视觉正确,但它成为兄弟节点中的**最上层**,吞掉其下所有可点击元素的命中测试(`hitTestBehavior` 取默认值即响应命中),该区域点击全部失效 |
|
|
188
|
+
|
|
189
|
+
**硬性要求(尺寸)**:顶层区域与主要区域的每个宽高值,必须能追溯到以下两者之一 ——
|
|
190
|
+
(a)参考 view tree 的**约束求解实测结果**,(b)Android XML 里的**显式 dp 声明**。
|
|
191
|
+
|
|
192
|
+
**引用 measure_pack 的 vp 值之前,先看 `measure_pack.json` 里 `density.confident`。** 该文件所有 vp 都是 `px / density` 算出来的,密度错一次,整页尺寸系统性差 2~3 倍,而编译、引用检查、跨页一致性全部照过(每页错得一样)。
|
|
193
|
+
- `density.source == "device"`(采集期 `wm density` 真值)或 `"cli"`(父流程用 `--density` 显式给出)→ vp 可直接作为实测证据引用,并在证据里注明来源。
|
|
194
|
+
- `density.source == "inferred"` 且 `confident: true` → 可引用,但证据中必须同时给出 `evidence` 里的命中行(哪个节点、多少 px、对应多少 dp)。
|
|
195
|
+
- `confident: false` → **不得**把 vp 当硬性实测值写进代码或证据。节点几何只能把密度定到一个谐波族内(72px 同时自洽于 24dp@3.0 / 36dp@2.0 / 48dp@1.5 / 72dp@1.0),此时应回到(b)Android XML 的显式 dp 声明作为尺寸依据,并在报告里登记该页密度不可信。父流程侧的处置见 SKILL.md Step 4.4。
|
|
196
|
+
|
|
197
|
+
**硬性要求(位置)**:**每个位置锚点**同样必须可追溯,且必须额外标注它属于哪一类 ——
|
|
198
|
+
(a)**固定偏移**:Android 侧本身就是相对父容器起始边(start/top)的固定 `margin`/`padding`;
|
|
199
|
+
(b)**由父尺寸推导**:Android 侧是右/下/居中锚定(`constraintEnd_toEndOf`、`Bottom_toBottomOf`、
|
|
200
|
+
`layout_gravity="end|center"`、`constraintHorizontal_bias` 等),位置随父容器尺寸变化。
|
|
201
|
+
|
|
202
|
+
判据:**(b)类锚点不得写成常量坐标**。参考 view tree 量出的绝对值只是「在该参考设备上的解」,
|
|
203
|
+
不是布局意图;必须还原成对齐关系(`RelativeContainer` + `.alignRules()`、`Stack` + `Alignment`、
|
|
204
|
+
父 `padding`)或由实时父尺寸推导的表达式。
|
|
205
|
+
自查方法:对每个 `.position()` / `.offset()` 的每个坐标问一句「**把屏幕宽度改小 10%,这个值还对吗**」——
|
|
206
|
+
答案为否却仍写成常量的,判定失败。这类缺陷在参考分辨率上数值完全正确,静态校验与编译均无法发现。
|
|
207
|
+
|
|
208
|
+
### 4.3 生成 ArkUI 代码
|
|
209
|
+
|
|
210
|
+
**与现有 Harmony 文件冲突处理**:转换前若目标 .ets 已存在,三种情形——
|
|
211
|
+
1. 动态刷新组件(toolbar 不同状态显示不同图标)
|
|
212
|
+
2. 增量实现
|
|
213
|
+
3. 现有代码或最终结构理解有错
|
|
214
|
+
|
|
215
|
+
无论哪种,必须通过对照静态 XML/Kotlin/Java + view.xml + view_scroll_n.xml 验证。**不要删除已有 Harmony 代码**。
|
|
216
|
+
|
|
217
|
+
dialog/fragment/adapter 等必须从主页面拆出去到 `{harmony_project_dir}/{ui_module}/src/main/ets/components/`。
|
|
218
|
+
|
|
219
|
+
**框架复合控件的部件归属 —— 逐个判定,不要照抄 view tree**:
|
|
220
|
+
|
|
221
|
+
当 view tree 出现框架内部 id(`abc_*`、`search_*`、`android:id/*`,以及 AppCompat / Material / Preference 的内部结构),必须**逐个判定**它属于哪一类:
|
|
222
|
+
|
|
223
|
+
- **(a) 映射为独立 ArkUI 元素** —— 该部件在目标端没有对应的内建绘制;
|
|
224
|
+
- **(b1) 已被目标原子组件吸收,且其默认渲染与参照等价** —— ArkUI 的 `Search` / `TextInput` / `Radio` 等**自带**输入框底线、光标、图标位、选中标记等装饰。判为 (b1) 必须给出**两栏正面陈述**:`原子组件默认绘制什么`(引 SDK `.d.ts` 的字段名与 `@default` 值)/ `参照呈现什么`(引截图实测或 view tree),并说明二者等价。
|
|
225
|
+
- **(b2) 已被吸收,但默认渲染与参照**不**等价** —— 必须**显式覆写**样式(如 `.radioStyle()`、`.selectedColor()`、`.searchButton()` 等)。**当该组件的样式接口无法表达参照的图形结构时,改判为 (a) 并接管绘制**:此时必须说明为何覆写不足,且该原子组件**不得再被实例化**(否则出现第二个绘制来源,回到重复绘制)。
|
|
226
|
+
|
|
227
|
+
注意方向与 Phase 4.2 相反:4.2 要求把**自定义/三方组件展开**成原子组件,是因为目标端没有它们;而框架复合控件在目标端**已有等价原子组件**,逐个部件照译会把原子组件已经画好的装饰**再画一遍**。
|
|
228
|
+
|
|
229
|
+
判据分两问,**缺任一问即判定失败**:
|
|
230
|
+
|
|
231
|
+
1. **有没有画两遍** —— 列出目标原子组件默认已绘制的装饰清单,确认没有任何手写兄弟节点与之重复。两遍绘制不仅是冗余 —— 手写节点的盒与原子组件的圆角/内缩盒通常不重合,多出的装饰会落在组件**外部**,看起来像"多了一条线/一个框"。
|
|
232
|
+
2. **它画出来的那一遍,像不像参照** —— 这一问是 (b1)/(b2) 的分界。**"由原子组件接管"不是终态,不终止检查。** 原子组件按**目标平台自己的设计语言**绘制(配色取自 ArkUI 主题、图形结构取自 ArkUI 的视觉模型),与 Android 参照没有任何理由自动一致。
|
|
233
|
+
|
|
234
|
+
> 漏检形态:第 1 问答"没重复,过",「平台默认值差异」那条答"你没设错值,过"——**两条各自正确的规则都放行,却没有任何一条问过第 2 问**。典型后果是选中标记/底线/进度轨道以目标平台的默认配色与图形结构渲染,编译通过、引用名正确、逐属性对照亦"忠实"。
|
|
235
|
+
>
|
|
236
|
+
> 尤其注意**图形结构**(不只是颜色)可能根本无法用样式接口表达:若参照是"环 + 间隙 + 点"而原子组件的模型是"实心圆 + 点"(`checkedBackgroundColor` 填满整圆、`indicatorColor` 叠一个点),则无论怎么调色都产生不出中间那圈透明间隙 —— 这类必须走 (b2) 的后半句,改判 (a) 自绘。判断方法:**先读 `.d.ts` 弄清该组件有哪几个样式旋钮、各自作用于哪一层几何,再问这组旋钮能否拼出参照的图层结构**,而不是先假设"调个颜色就行"。
|
|
237
|
+
|
|
238
|
+
报告中必须给出这张归属表,**每行标注 (a) / (b1) / (b2)**:(b1) 附上述两栏正面陈述;(b2) 附覆写手段,或改判 (a) 的理由。
|
|
239
|
+
|
|
240
|
+
**通则 —— "由平台接管"不终止检查(适用于本流程全部检查项)**:
|
|
241
|
+
|
|
242
|
+
凡某条规则的结论形如「由平台/原子组件/框架接管」「组件自带」「默认行为即可」「无需设置」,该结论**不构成通过**。必须再答一句:**它接管之后产出的结果,与参照是否等价,依据是什么。**
|
|
243
|
+
|
|
244
|
+
判据(机械):结论文本里出现「自带 / 默认 / 接管 / 无需设置 / 已吸收」字样时,必须紧跟一条带依据的等价性陈述(SDK `.d.ts` 的 `@default` 值、或截图/view tree 实测),否则该项判定失败。
|
|
245
|
+
|
|
246
|
+
> 本流程已登记若干「两条各自正确的规则组合出错」的实例(`fill="none"` 与 `.fillColor()` 互为对偶、view tree 部件与原子组件自带装饰、平台默认值差异、容器对齐默认值相反、`padding` 语义反向……)。**这类清单永远不可能完备** —— 每出现一个新的原子组件或新的默认值,就多一个未登记实例。因此不要依赖"查清单",而要在每次得出"平台接管"结论时机械地追问上面那一句。清单是例子,通则才是防线。
|
|
247
|
+
|
|
248
|
+
**架构**:
|
|
249
|
+
- MVVM:page/component 用 viewmodel,viewmodel 用 model
|
|
250
|
+
- 转换前先扫 `{harmony_project_dir}` 找可复用组件/VM/Model;toolbar 等通用组件抽到 components 复用;相关 model/viewmodel 合并复用,不要每页新建一份
|
|
251
|
+
- **按 Phase 2.0 判定的范式选装饰器,全程保持同一套,不混用**:
|
|
252
|
+
- **V1**:`@Component` 组件;组件内状态 `@State`,父子 `@Prop`(单向)/`@Link`(双向),跨层级 `@Provide`/`@Consume`;ViewModel/Model 类用 `@Observed` + 属性 `@Track`;副作用监听 `@Watch`
|
|
253
|
+
- **V2**:`@ComponentV2` 组件;组件内状态 `@Local`,父子输入 `@Param`(+ 可选 `@Once`)、子→父 `@Event`(配合 `!!` 双向绑定),跨层级 `@Provider`/`@Consumer`;ViewModel/Model 类用 `@ObservedV2` + 属性 `@Trace`;监听 `@Monitor`,计算属性 `@Computed`
|
|
254
|
+
|
|
255
|
+
**资源**:每个 HarmonyOS UI 引用的资源(图片/字符串/颜色/尺寸等)必须探索并按语义匹配使用`鸿蒙项目已转换的resource` 。
|
|
256
|
+
|
|
257
|
+
**图标来源分级 —— 逐个图标判定并在报告中标注级别**:
|
|
258
|
+
|
|
259
|
+
| 级别 | 判据 | 做法 |
|
|
260
|
+
|---|---|---|
|
|
261
|
+
| **L1** | `res/drawable/` 有对应 `<vector>` | **先全量列出 `{harmony_project_dir}/entry/src/main/resources/base/media` 的文件清单**(`Get-ChildItem <media> -Name`,**禁止 `Select-Object -First N` / `head -N` 等截断式列举** —— 截断结果按字母序开头,通常全是 `abc_*` 之类 AppCompat 内置资源,与"目录里没有该图标"完全同形),再按 Android 侧 `res/drawable/` 的文件名与该清单 join;命中即用 Step 2 的转换产物。**L1 未做过这次全量列举,不得进入 L2** |
|
|
262
|
+
| **L2** | 库编译图标,`res/` 无文件,但资源转换阶段已提取出 SVG | **在`resource_mapping.md` 中用 Grep 定位 `### Code-Defined Vector Icons` 章节(含 Converted / Unavailable 分栏),不要 Read 整个文件** —— `resource_mapping.md` 按设计可能有 4~22 MB。检索方式**首选按符号名直接 grep**(`grep -n "ic_get_app\|ic_query_stats\|ic_glasses" <报告>`,也可用 Android 源码符号名 `grep -n "Icons.Outlined.GetApp" <文件>`);`grep -n -A 80 "^### Code-Defined Vector Icons" <报告>` 作为**补充**手段 —— 该章节标题在部分资源转换器产物中并不存在,命中失败不代表图标不可得,此时必须回退到按符号名 grep,并注意已转换的库图标也可能登记在通用资源表里(形如 `| res/drawable/ic_glasses_24dp.xml | ... | converted |`)而不在该章节内。命中后**按 Android 源码符号名精确匹配映射行**(如 `Icons.Outlined.GetApp` → 映射行的 Compose Reference 列),用其中登记的 HarmonyOS Target media,并在报告里注明来自哪个库的哪个符号(可溯源) |
|
|
263
|
+
| **L3** | L1/L2 都拿不到(含转换报告 **Unavailable** 里列出的符号) | 报告中显式登记 `已降级`:缺哪个图标、当前用**等尺寸空 Row/Column 占位**(禁止 emoji/文字字形/Unicode)、影响哪个位置 |
|
|
264
|
+
|
|
265
|
+
判定顺序固定为 L1 → L2 → L3,**不要自行推断某个图标"应该"长什么样**。L2 匹配必须用 Android 源码符号与映射表 join,**不得**按"看起来像"选——当一页有多个相近图标(download/get_app/install 在 Material 里都存在)时按相似度选必选错。`Unavailable` 里的每一项都已经是上游确认拿不到的,直接归 L3,不要再尝试补。**检索空命中不构成"该资源不存在"的证据**:`grep` 返回 `No files found`、或列目录结果里没看到,都只说明**该模式/该次列举没有匹配**——文件很可能存在(可用 `Test-Path` 单独验证),措辞却与"资源不存在"完全同形(同 L2 格里 Read 被静默截断的坑)。宣布任一图标为 L3 之前,必须换至少一种检索方式(换匹配模式、换检索路径、或改为全量列目录)复现空结果,仅凭一次空命中即落 L3 判定失败。**L3 占位禁止用 emoji/文字字形**(见下文禁止项 1),改用等尺寸空位,以便后续补实时清晰可辨。
|
|
266
|
+
|
|
267
|
+
**禁止的三种降级**(编译全过、静态引用检查全过,但视觉必错):
|
|
268
|
+
|
|
269
|
+
1. **emoji / Unicode/文字字形代替图标** —— `←` `✓` `★` `🔍` `≡` `↗` `⤓` 之类。L3 占位改用等尺寸空 Row/Column(如 `Row().width(24).height(24)`)并在 TODO 注释里标注缺失图标名,以便后续补实时清晰可辨。
|
|
270
|
+
2. **语义不符的现有 drawable 顶替** —— 例如拿 `ic_placeholder` 当设置图标、拿箭头图标当勾选标记、拿横三点 `sys.symbol.more` 当竖三点 overflow。「名字存在且能编译」不是选它的理由;**兄弟页面的既有用法("工程惯例")不得覆盖实测证据与 L1/L2 素材**——若 media 目录已有语义正确的图标、或 measure_pack 已给出正确尺寸,必须优先使用,即使既有页面用错了也不得复制错误。
|
|
271
|
+
3. **凭记忆手写 SVG path** —— 手写产物几乎必然带 Material web SVG 的包围盒 `<path d="M0 0h24v24H0z" fill="none"/>`,被 `.fillColor()` 染色后整个图标变实心色块(见 Phase 5 常见误译速查)。**ArkUI `Path.commands` 单位为 px 不是 vp**,直接抄 24×24 坐标会在真机上缩成 1/density 大小(3.5 倍屏缩到约 7vp);若必须用 Path 绘制(coral 播放三角等),坐标需按设计稿 vp 值乘以目标 density 后写入。
|
|
272
|
+
|
|
273
|
+
**L3 未登记即判定失败。** 宁可显式记一条「缺图标」,也不要静默换成一个错的。
|
|
274
|
+
|
|
275
|
+
**消费前必须打开文件确认内容可渲染**:`ls media/` 只能证明名字存在。以下文件存在、引用名正确、编译通过,却画不出图形或画错 —— 必须打开文件核对:
|
|
276
|
+
|
|
277
|
+
- **`fill`/`stroke` 里残留未解析的 Android 主题属性**(`?attr/...`、`?colorPrimary`)→ SVG 不认识该值,该几何不着色(白框)。需回到 Android `<vector>` 源,把主题属性解析成 theme 里的具体颜色后重新转换。
|
|
278
|
+
- 资源转换阶段生成的**占位 SVG**(灰底矩形 + 资源名文本)→ 界面上是个灰框。属于 L3,必须补真实素材或登记降级。
|
|
279
|
+
- **空 SVG 文件**(`<svg></svg>` 或 0 字节)→ 完全不渲染,界面上该位置为空白。
|
|
280
|
+
- **重复 XML 属性**(同一元素上同一属性出现两次,如 `<path fill-opacity="1" fill-opacity="0.8" .../>`)→ XML 规范下是致命错误,解析器拒绝整份文档,该图标一个像素都不会画出来。转换器的常见 bug 是先写死默认值、再追加真实值(保留后者)。
|
|
281
|
+
- **描边图形(`fill="none" stroke="#..."`)被用 `.fillColor()` 染色**:`fillColor` 只叠加 fill 通道,对 stroke 完全无效 → 颜色不生效,图形保持原色(通常是黑)。修法三选一:把描边几何改写成等价 fill 几何(等宽圆环 = 外圆 + 内圆同 path + `fill-rule="evenodd"`);改用目标端原生组件;或按状态准备已着色素材。
|
|
282
|
+
- **`fill="none"` 包围盒被染色**(形如 `<path d="M0 0h24v24H0z" fill="none"/>`):`fillColor` 叠加到**全部**几何,`fill="none"` 拦不住 → 该 path 被染成实心色块,盖住整个图标。Android `<vector>` 源**不会**产生这条 path(它是 Material web SVG 写法),其出现即证明该 SVG 是凭记忆手写的,应从 `res/drawable` 重新转换。
|
|
283
|
+
- **文件名选对但像素内容错**:Material Design Compose `ImageVector` 等库图标,文件名与源码符号对应但转换产物内容画的是另一个图形 → 必须逐个**打开代码中引用的 SVG 读 path 数据**,与参考截图里该图标的形状比对;不一致即判定失败,需回溯资源转换阶段修正或换用 L3 等尺寸空 Row 占位 + 降级登记。参考侧的形状可用 `measure_pack.js --probe <screenshot.png> --rect <图标 bounds> --mode runs`(或 `--mode ascii` 先看清形状)读出;**`--probe` 只解码 PNG,不能传 SVG** —— 目标侧只能读 SVG 源码,没有随包工具能把 SVG 栅格化后逐像素比对。**禁止只抽查部分文件后对未检查文件下全称断言**(如"打开 2 枚 → 声称全部 14 枚正确")。
|
|
284
|
+
|
|
285
|
+
**ShapeDrawable / 可拉伸背景特例**:
|
|
286
|
+
|
|
287
|
+
- 当 Android `android:background` 指向 XML `<shape>` 时,必须读取该 drawable XML 的 `shape`、`solid`、`corners`、`stroke` 和 `padding`。
|
|
288
|
+
- 对 rectangle/oval/line 等静态背景,优先映射为 ArkUI 容器样式:`.backgroundColor()`、`.borderRadius()`、`.border()`、`.padding()`。
|
|
289
|
+
- 尤其当 `<shape>` 没有 `<size>` 时,它是随宿主尺寸拉伸的背景,禁止把资源转换阶段生成的 24×24 SVG 作为普通 `Image` 再用 `ImageFit.Fill` 非等比拉伸。
|
|
290
|
+
- 示例:440×56dp、`<corners android:radius="12dp">` 应实现为宿主容器 `.backgroundColor($r('app.color.card_bg_color')).borderRadius(12)`,不能渲染为圆角已烘焙的 24×24 图片。
|
|
291
|
+
- 只有图形本身是固定尺寸图标,或资源明确声明 `<size>` 且消费尺寸一致时,才使用转换后的 SVG media。
|
|
292
|
+
|
|
293
|
+
**可见资源闭环**:
|
|
294
|
+
|
|
295
|
+
- 对参考截图中每个可见 Image/Video/Text,记录 Android 来源、HarmonyOS 目标资源和 ArkUI 代码引用。
|
|
296
|
+
- 已迁移但未在代码中消费的关键可见媒体(例如首页 PRESET 视频/背景)必须补齐绑定;不得用纯色或占位渐变替代后仍判定完成。
|
|
297
|
+
|
|
298
|
+
**始终先查映射文件**(项目专属、覆盖更全),覆盖内置知识。
|
|
299
|
+
|
|
300
|
+
**单位与样式**:
|
|
301
|
+
- `dp` → `vp`,`sp` → `fp`
|
|
302
|
+
- `bounds` 差值只用于计算区域比例和验证相对布局,不能直接当 vp;固有 dp/sp 尺寸以 Android XML/dimens 为准
|
|
303
|
+
- 资源用 `$r('app.color.xxx')` / `$r('app.string.xxx')` / `$r('app.media.xxx')`
|
|
304
|
+
|
|
305
|
+
**路由**:实现 UI 与组件之间的路由关系。
|
|
306
|
+
|
|
307
|
+
**交互元素**:
|
|
308
|
+
- 给每个 `clickable_element` 挂 `.onClick()`
|
|
309
|
+
- `click_path` 显示跳到另一 Activity → `router.pushUrl()`
|
|
310
|
+
- 状态切换(dialog/menu/toggle)→ 用 `@State` 管理
|
|
311
|
+
- **优先真实实现**:mock 之前先在 `{harmony_project_dir}` 搜目标页面/组件 .ets 是否存在;存在则接真实 `router.pushUrl()`;**仅当目标尚未实现**才 mock + `console.info('TODO: ...')`
|
|
312
|
+
- **mock 数据必须复现参考截图的可见状态**:mock/占位数据不是自由值。参考截图里每个可见条目的**取值、数量、起止与顺序**都必须被 mock 如实复现(真实数据层未转换时,mock 应从参考截图**反推**得出)。若实现产出的可见状态与参考不一致,**不得**以「这是 mock 数据、接真实数据后会自愈」为由判定通过 —— 必须先证明规则层正确,再讨论数据来源。规则层错误(选择规则、序列起点、条目数)接上真实数据同样是错的。
|
|
313
|
+
|
|
314
|
+
### 4.4 Lottie 动画集成(仅当父 skill 注入了 `LOTTIE_ENTRIES_FOR_THIS_PAGE` 时执行)
|
|
315
|
+
|
|
316
|
+
父 skill 会在你的 prompt 里以 JSON 块 `LOTTIE_ENTRIES_FOR_THIS_PAGE` 传入本页对应的 Lottie 条目(可能有多个)。**如果 prompt 中没有这个块,跳过本节** —— 不要主动去读 `resource_mapping.md`(该文件按设计 4~22 MB,且 Lottie 章节根本不在其中,而在转换报告里),不要在生成的 .ets 里额外提及 Lottie。
|
|
317
|
+
|
|
318
|
+
如果有,按下面步骤处理:
|
|
319
|
+
|
|
320
|
+
#### 4.4.1 定位 Android 侧动画的宿主逻辑
|
|
321
|
+
|
|
322
|
+
对每一条条目,`host_android` 是宿主 Activity/Fragment 类名,`host_layout` 是宿主 layout(可能为空)。**回读 Android 源码**理解它是怎么播动画的:
|
|
323
|
+
|
|
324
|
+
1. 打开 `{android_project_dir}` 下的 `host_android` 类,重点看:
|
|
325
|
+
- `onCreate` / `onCreateView` / `onViewCreated`:动画视图初始化、`setAnimation(...)` 调用、循环/自动播放设置
|
|
326
|
+
- 什么时候开始播、循环、暂停、销毁(`playAnimation` / `pauseAnimation` / `cancelAnimation` / `resumeAnimation`)
|
|
327
|
+
- 是否随控件状态切换动画(下拉刷新的 `onStateChange`、Tab 切换、点击切换等)
|
|
328
|
+
- 是否有 `addAnimatorListener` / progress 监听
|
|
329
|
+
2. 打开 `host_layout` 里含 `lottie_fileName` / `lottie_rawRes` 的那个 `LottieAnimationView` 节点,记录:
|
|
330
|
+
- `lottie_autoPlay`、`lottie_loop`、`lottie_speed`、`lottie_renderMode`、`lottie_scaleType`
|
|
331
|
+
- 宽高、位置、`lottie_fallbackRes`
|
|
332
|
+
3. 记录是否是"下拉刷新自定义动画"、"Splash 一次性动画"、"Tab 切换 icon 动画"这类经典模式 —— 影响后面选择的鸿蒙容器组件与生命周期时机。
|
|
333
|
+
|
|
334
|
+
#### 4.4.2 使用 `@ohos/lottie` 生成 ArkTS 代码
|
|
335
|
+
|
|
336
|
+
**前提**:`@ohos/lottie` 由父 skill 的 Step 6 build fix 阶段确认安装(生成的代码里可以直接 `import lottie, { AnimationItem } from '@ohos/lottie'`,不需要你去动 `oh-package.json5`)。
|
|
337
|
+
|
|
338
|
+
**放置位置**:
|
|
339
|
+
- 若动画在一个可复用组件里出现,拆到 `{ui_module}/src/main/ets/components/<Name>LottieView.ets`(常见:下拉刷新头、加载 loading、Splash)
|
|
340
|
+
- 否则直接放在页面 `.ets` 里作为一个 `@Builder` 或子组件
|
|
341
|
+
|
|
342
|
+
**path 值必须来自条目的 `load_path` 字段,原样使用**(通常形如 `lottie/xxx.json`);**不要**猜别的路径,不要写成 `$rawfile('...')`。
|
|
343
|
+
|
|
344
|
+
**根据 Android 宿主逻辑改造上述骨架**:
|
|
345
|
+
|
|
346
|
+
- **下拉刷新场景**(Android 用 `SwipeRefreshLayout` + Lottie 或者自定义 refresh header):鸿蒙用 `Refresh` 组件的 `builder` 传入本 Lottie 组件,并映射 `onStateChange`:
|
|
347
|
+
|
|
348
|
+
- **一次性动画**(Splash、Onboarding):`loop: false, autoplay: true`,`animateItem.addEventListener('complete', ...)` 里做路由跳转。
|
|
349
|
+
|
|
350
|
+
- **点击触发**:去掉 `autoplay`,`onClick` 里 `this.animateItem?.goToAndPlay(0, true)`。
|
|
351
|
+
|
|
352
|
+
- **状态切换 icon**(Tab、播放/暂停):不同状态用 `goToAndStop(frame, true)` 切段;若 Android 用了多段(minFrame/maxFrame),映射到 `playSegments([[min, max]], true)`。
|
|
353
|
+
|
|
354
|
+
- **销毁**:Android `onDestroyView` / `onDestroy` 中的 `cancelAnimation` → 鸿蒙 `aboutToDisappear` 和 `Canvas.onDisAppear` 都调用 `lottie.destroy(name)`,并把 `animateItem` 置 `null`,避免泄漏。
|
|
355
|
+
|
|
356
|
+
#### 4.4.3 融入到当前页面
|
|
357
|
+
|
|
358
|
+
- 如果本页有多个 Lottie 条目,分别生成对应的子组件,并在 Android 宿主 layout 对应位置嵌入(用 `Stack`/`Column`/`Row` 与其他 ArkUI 组件同级摆放,尺寸沿用 4.5.1 记录的 bounds/scaleType)。
|
|
359
|
+
- 保留 `lottie_fallbackRes` 的降级语义:在 `loadAnimation` 失败回调里(`animateItem.addEventListener('data_failed', ...)`)显示 `$r('app.media.<fallback>')` 的 `Image`。
|
|
360
|
+
- 每个动画组件的 `animateName` 必须唯一(用页面名 + 动画名拼),否则 `lottie.destroy(name)` 会误伤别的实例。
|
|
361
|
+
|
|
362
|
+
## Phase 5 — 一致性校验(关键,必跑 2 轮 + 条件第 3 轮)
|
|
363
|
+
|
|
364
|
+
**轮次规则**:第 1、2 轮**必跑**且都要走完下方全部检查项。第 3 轮**按需**:
|
|
365
|
+
- 第 2 轮**有任何修改**(改了 .ets、资源或路由)→ **必须**跑第 3 轮,且第 3 轮只针对第 2 轮改动
|
|
366
|
+
波及的检查项复核,直到某一轮**零修改**为止;
|
|
367
|
+
- 第 2 轮**零修改**(纯确认)→ **跳过**第 3 轮,直接进入报告,并在报告中记录「第 2 轮零修改,
|
|
368
|
+
依规则跳过第 3 轮」。
|
|
369
|
+
|
|
370
|
+
### Phase 5 的输出契约 —— 同一事实只写一遍
|
|
371
|
+
|
|
372
|
+
**检查项的数量、两轮必跑规则、每项必须得出的结论,全都不变。改的只是「写在哪、写几遍」。**
|
|
373
|
+
|
|
374
|
+
更关键的是可靠性:那次运行的报告**根本没送达**(工具通道中断),父流程最终是靠读磁盘上的
|
|
375
|
+
注释 + 重新复算恢复的。**证据的可靠载体是代码注释与可复算产物,不是散文报告。**
|
|
376
|
+
|
|
377
|
+
据此分两档:
|
|
378
|
+
|
|
379
|
+
**A 档 —— 报告中必须写出完整推导**(5 项):
|
|
380
|
+
`窗口模式证据`、`过渡实现证据`、`装饰重复检查`、`可见资源闭环`、`位置锚点证据`。
|
|
381
|
+
|
|
382
|
+
这 5 项的漏检形态都是「两条各自正确的规则组合出错」(`fill="none"` 与 `.fillColor()` 互为对偶、
|
|
383
|
+
view tree 部件与原子组件自带装饰、①用截图误判+④用「与基线一致」确认……)。
|
|
384
|
+
这类错误**只有把推理链摊开写出来才可能自查到**,压缩成一句结论必然放过。
|
|
385
|
+
|
|
386
|
+
**B 档 —— 报告中只写「结论 + `file:line` 指针」**(其余各项):
|
|
387
|
+
推导以结构化注释留在对应 `.ets` 里,格式固定:
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
// EVIDENCE(<检查项名>): <结论> —— 依据: <来源>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
- 括号内名称必须与下方检查项标题**逐字一致**(如 `EVIDENCE(文字对齐证据)`),否则父侧无法机械定位。
|
|
394
|
+
- `<来源>` 仍须是老四类之一:`约束求解实测` / `XML 显式 dp` / Android 源码 `文件:行号` / 像素实测。
|
|
395
|
+
- 报告里对每个 B 档项给出一行:`<检查项名>: <结论> -> <文件>:<行号>`。
|
|
396
|
+
|
|
397
|
+
**禁止把同一事实在注释与报告里各写一遍。** 注释是唯一载体,报告只做索引。
|
|
398
|
+
|
|
399
|
+
副产品比省时间更值钱:统一标记让父侧复核从「读散文」变成「跑 grep」——
|
|
400
|
+
`grep -rn "EVIDENCE(" <ets 目录>` 即可点出全部证据位置并逐条比对。
|
|
401
|
+
|
|
402
|
+
列出转换后 HarmonyOS UI 中所有 layout/component/resource reference,对每一个**逐项**走完以下检查(不可省略):
|
|
403
|
+
|
|
404
|
+
- **参考输入证据**:报告中列出本页实际读取的 `REFERENCE_SCREENSHOTS` 和 `REFERENCE_VIEW_TREES`;缺失时本页不得通过。
|
|
405
|
+
- **主要区域证据**:列出主要区域的 Android bounds、相对内容区的归一化比例和对应 ArkUI 容器;检查顶层区域不存在无来源的大数值 `.position()`/固定高度。**并且**逐区域标注每个宽高值的**来源**:`约束求解实测`(来自参考 view tree)或 `XML 显式 dp`。无法标注来源者判定失败;用 `'100%'`/`.height('100%')` 表达实测尺寸不等于父容器的区域,同样判定失败(详见 Phase 4.2 的约束布局译法表)。
|
|
406
|
+
- **位置锚点证据**:除宽高外,**逐项列出每个 `.position()` / `.offset()` 的每个坐标**,标注它是 `固定偏移`(Android 侧本身相对父容器起始边固定)还是 `由父尺寸推导`(Android 侧为右/下/居中锚定)。**`由父尺寸推导` 的坐标写成常量即判定失败** —— 必须还原为对齐关系(`RelativeContainer` + `.alignRules()`、`Stack` + `Alignment`、父 `padding`)或实时父尺寸表达式。逐坐标自查:「把屏幕宽度改小 10%,这个值还对吗」。此项漏检的典型表现是元素在较窄屏幕上移出可视区被裁切,而在参考分辨率上完全正常(详见 Phase 4.2 的**硬性要求(位置)**)。
|
|
407
|
+
- **窗口模式证据**:沉浸式在 ArkUI 里由**两层**共同决定,缺任一层都不生效,必须**逐页列出**并核对五项:
|
|
408
|
+
① Android 参考是沉浸式绘制还是避让系统栏。**唯一合法判据是 `android_project_dir` 里的源码检索结果**,必须在报告中给出命中的 `文件:行号`,或写明「以下关键字全部检索、零命中」:
|
|
409
|
+
`enableEdgeToEdge` / `EdgeToEdge.enable` / `setDecorFitsSystemWindows` / `WindowCompat.setDecorFitsSystemWindows` / `windowTranslucentStatus` / `windowDrawsSystemBarBackgrounds` / `fitsSystemWindows` / **`statusBarColor` / `colorPrimaryDark` / `setStatusBarColor`**。
|
|
410
|
+
检索范围优先本页宿主 Activity 及其 Application/基类;宿主自身没有时,命中项属于别的 Activity(如 `CrashActivity`、`ReaderActivity`)**不能**算作本页的证据。
|
|
411
|
+
**禁止用截图推断本项**:「截图里状态栏可见」「内容起点在状态栏下方」都**不能**推出非沉浸式 —— edge-to-edge 页面会自行对 insets 做 padding,其截图形态与非沉浸式**完全同形**,该判据在沉浸式场景下必然误判。截图只能用于交叉印证,不能作为①的依据。**measure_pack 已产出截图顶边色(top edge color)时,若该色与系统栏默认色(浅色主题 #F5F5F5、深色主题 #1C1C1C)显著不同(ΔE > 20),必须触发交叉印证:要么①源码命中主题着色关键字、要么 HarmonyOS 侧用 `setWindowSystemBarProperties` 镜像该颜色,否则判定失败。**
|
|
412
|
+
② **窗口层**:`UIAbility`(通常是 `EntryAbility`)中的窗口配置 —— `setWindowLayoutFullScreen(true/false)`、系统栏属性(`setWindowSystemBarProperties`),或明确"未设置";
|
|
413
|
+
③ **组件层**:ArkUI 顶层实际采用的 `expandSafeArea` 参数(类型 + 边,或明确"未使用");
|
|
414
|
+
④ ①②③ 三者是否等价。
|
|
415
|
+
⑤ **内容层 inset 避让**(仅当②判定为全屏模式 `setWindowLayoutFullScreen(true)` 时检查):页面内容如何处理状态栏/导航栏区域,必须给出具体实现的 `file:line` 并分类标注:
|
|
416
|
+
- `getWindowAvoidArea(AvoidAreaType.TYPE_SYSTEM)` 并将其 `topRect.height` 用作内容顶部 padding —— 引用调用点 `file:line`;
|
|
417
|
+
- 仅背景层使用 `expandSafeArea`、内容层不延伸(如 Stack 里背景 Column 开启、内容 Column 不开启)—— 引用两层代码 `file:line`;
|
|
418
|
+
- toolbar 类组件自带避让(如平台提供的 `Navigation`)—— 引用该组件 `file:line` 并标注其避让语义来自平台文档;
|
|
419
|
+
- **明确"非沉浸式页面(②未全屏或③未扩展)不需要⑤"。**
|
|
420
|
+
并与 Android 参考 view tree 的内容顶部 y 坐标回代比对:设状态栏高度实测为 `h_status`、内容首个文字节点的 `bounds.top` 为 `y_content`,若 `y_content > h_status`(内容本就在状态栏下方),则 HarmonyOS 侧必须复现该避让;若 `y_content ≈ 0`(内容从屏幕顶起绘),则 HarmonyOS 全屏模式下可无 padding。**未给出⑤的实现 file:line、或回代比对失败,判定失败。**
|
|
421
|
+
**关键**:`expandSafeArea` 只让**组件**能延伸进安全区,**并不能**让系统让出状态栏条带 —— 窗口未处于全屏布局模式时,系统仍会为状态栏保留空间,页面顶部出现空白条带。因此「组件层已正确声明」**不足以**判定本项通过;未列出②即判定失败。**`setWindowLayoutFullScreen(true)` 后系统不再保留状态栏空间,内容若不自加 inset padding 即从 y=0 起绘,与状态栏文字/图标重合**——这是⑤必检的原因。
|
|
422
|
+
**页间一致性只是提示,不是通过路径。** ①与④冲突时**以①为准**。基线(既有页面、`EntryAbility`)**不构成**①的证据 —— 基线只说明"现状如何",不说明"Android 侧是什么"。
|
|
423
|
+
当①判定为沉浸式、而基线页面全部非沉浸式时,正确做法是**补齐窗口层 + 组件层 + 内容避让**(含改 `EntryAbility`),**不是**跟随基线保持非沉浸式。「与 base 保持一致」在这种情形下是把错误固化,且因为全批一致,跨页比对会满分通过 —— 这正是本项最常见的漏检路径:①用截图误判成非沉浸式,④用"与基线一致"确认,两步都"有据",结论却是错的。
|
|
424
|
+
若确实决定不实现沉浸式(例如①零命中),需在报告中写明①的检索结果作为依据,**并用 `setWindowSystemBarProperties` 镜像 Android 状态栏颜色(若 measure_pack 顶边色与系统栏默认色不同)**。此项漏检的典型表现是页面内容整体上移或下移一个状态栏高度,或顶部出现一条系统栏底色的空白带,**或全屏模式下内容顶部文字/图标与系统状态栏重合**。
|
|
425
|
+
- **可见资源闭环**:列出可见 Image/Video/Text 的 Android 来源、Harmony 资源和代码引用;关键媒体缺少任一环时不得通过。**并且**对每个由运行时值选择变体的可见元素,列出「参考截图所示状态 → 该状态下你的规则实际选出的变体」的对照;仅列出映射表完整不足以通过本项。若本页使用 mock/占位数据,逐条列出参考截图可见状态与实现产出状态的对照,不一致即判定失败(依据 Phase 4.3 的 mock 规则)。**凡引用参考截图/view tree 作为某规则的依据时,必须回代验证该规则能否真的推出所引状态** —— 论据与结论互斥(如声称「本规则复现了参考」而该规则推不出参考所示结果)即判定失败。
|
|
426
|
+
- **形状背景证据**:列出 Android ShapeDrawable 到 ArkUI 样式的映射;无 `<size>` 的 stretchable shape 若通过普通 Image + `ImageFit.Fill` 使用,判定失败。
|
|
427
|
+
- **静态裁切检查**:根据参考截图/view tree 的主要区域比例检查 toolbar、内容区、卡片和底部区域不会相互覆盖或超出预期内容区。
|
|
428
|
+
- **交互可达性证据**:对每个可点击元素,列出它在父 `Stack`/层叠结构中的**层序**,并确认其后**没有**任何兄弟节点在其可视矩形上方覆盖它。**并且**列出本页所有 `width('100%')` 且 `height('100%')`(或等效撑满)的容器,逐个标注它是可交互的、还是已声明 `hitTestBehavior(HitTestMode.None)`。「视觉不重叠」与「命中不被遮挡」是两件事:透明或贴底的容器在视觉检查中不构成遮挡,却能吞掉其下全部点击。此项漏检的典型表现是元素显示正常、点击无任何反应(含控制台无输出),因为事件被上层节点消费而该节点自身没有 `onClick`。
|
|
429
|
+
|
|
430
|
+
- **观测装饰器缺失**(按 Phase 2.0 判定的范式分支):
|
|
431
|
+
|
|
432
|
+
检查对象是**「`build()` / `@Builder` 里读到的每一个成员」**,不是「类里声明的每一个属性」。
|
|
433
|
+
这两者不等价,而差集正是本项最常见的漏检来源:字段侧完备性可以 100% 通过,
|
|
434
|
+
而 UI 侧读到的某个东西根本不是字段。**逐条列出 build() 的读取面**(成员名 → 它是
|
|
435
|
+
`@Track`/`@Trace` 字段 / getter / 方法 / 继承成员 → 合法性依据),只列字段清单不算走完本项。
|
|
436
|
+
|
|
437
|
+
- V1:
|
|
438
|
+
- `@Observed` 类中所有在 UI 中使用的属性是否都加了 `@Track`?没有则补。
|
|
439
|
+
- **一旦类中出现任何 `@Track`,该类未被 `@Track` 装饰的属性就不能在 UI 中使用**,
|
|
440
|
+
否则运行时抛 `BusinessError 140110: Illegal usage of not @Track'ed property 'x' on UI!`
|
|
441
|
+
(依据:`{mvvm_dir}/@Track装饰器:class对象属性级更新.md:15` 与 `:198`)。
|
|
442
|
+
这是 all-or-nothing 语义,不是「加了的能属性级刷新、没加的退化为整类刷新」。
|
|
443
|
+
- **`get x()` 无法被 `@Track` 装饰**,因此在「已使用 `@Track` 的类」里用 getter 暴露派生值,
|
|
444
|
+
一旦被 `build()` 读到就必然首帧崩溃。**V1 没有 `@Computed`**(那是 V2 能力),
|
|
445
|
+
所以派生值只有两条合法出路:写成**普通方法**(`title(): string`,UI 侧 `vm.title()`;
|
|
446
|
+
原型方法不是数据属性,不经过 `@Track` 代理),或提升为真正的 `@Track` 字段并在赋值处同步维护。
|
|
447
|
+
注意「getter 被读取时会重新求值」这一点是**对的**,但它只说明**响应性**没问题,
|
|
448
|
+
**不说明合法性** —— 两者极易混为一谈,且混淆后的结论能通过编译与全部静态检查。
|
|
449
|
+
机械兜底:GATE 规则 `track-class-getter-in-ui`。
|
|
450
|
+
- 继承成员同理:父类属性若未 `@Track` 而子类实例被 UI 读取,同样触发 140110。
|
|
451
|
+
- V2:`@ObservedV2` 类中所有在 UI 中使用的属性是否都加了 `@Trace`?没有则补(`@ObservedV2` 与 `@Trace` 必须配合,单独使用任一个都不生效)。派生值用 `@Computed`。
|
|
452
|
+
- 范式一致性:本页新增/修改的组件与 VM/Model 是否与工程判定范式一致?严禁 V1/V2 装饰器混用(如 `@Component` 里用 `@Local`、`@ComponentV2` 里用 `@State`)
|
|
453
|
+
- **颜色冲突**:fill/background/foreground 颜色是否冲突导致不可见(白底白图)?有则修
|
|
454
|
+
- **颜色设置**:背景/填充/前景色是否与 Android UI 一致(漏设、错设)?有则修
|
|
455
|
+
- **文字对齐证据**:逐个列出直接含 `Text`(或含渲染 `Text` 的 `@Builder` 调用)的 `Column`/`Row`,标注它是否显式声明了**决定水平位置的那个轴**,以及该取值的 Android 依据。
|
|
456
|
+
**两种容器的该轴属性不同名,必须按容器类型取**:`Column` 的水平方向是交叉轴 → `.alignItems(HorizontalAlign.*)`;`Row` 的水平方向是主轴 → `.justifyContent(FlexAlign.*)`。
|
|
457
|
+
`Row` 上的 `.alignItems(VerticalAlign.*)` 管的是**纵轴**,**不满足本项** —— 它常常本身完全正确(对应 Android 的 `verticalAlignment`/`gravity="center_vertical"`),正因如此最容易被当成"对齐已经写了"而放过:一个正确的纵轴设置掩盖了缺失的横轴设置,两处单看都没错。实测漏检形态是日期分隔行、章节标题居中而 Android 齐左。判据:宽度撑满的 `Row` 里若有收缩宽度的 `Text`,就必须能指出是哪个属性把它定在了左边。**ArkUI `Column` 默认居中、Android `LinearLayout`/Compose `Column` 默认 start** —— Android 侧未写 gravity 时必须显式译成 `.alignItems(HorizontalAlign.Start)`,不能同样"不写"。未显式声明且未说明依据者判定失败(居中确为本意时,注明理由即可通过,例如空态插图、按钮内图标)。此项漏检的典型表现是章节标题、列表项文字整体居中而 Android 是齐左,且逐属性对照会确认成"翻译正确"。
|
|
458
|
+
- **图标来源分级证据**:对每个可见图标,标注它属于 Phase 4.3 的 L1 / L2 / L3 哪一级;L2 须给出库与符号名,L3 须给出降级说明。**L3 未登记即判定失败**。**每个 L3 还须出示检索痕迹而非结论断言**:逐个列出该图标实际执行过的 L1 全量列目录命令与其输出摘要、L2 按符号名 grep 命令与其输出,二者缺任一即判定失败;一句「库图标不可提取」「全部降级为占位」之类的概括**不构成**已检索的证据。并逐个确认没有使用被禁止的三种降级(emoji/文字字形、语义不符的 drawable 顶替、凭记忆手写 SVG)。**并且必须确认内容语义正确**:对代码中引用的每个 SVG,**打开文件读 path/circle/rect 几何**,与参考截图中该图标的形状比对(参考侧形状可用 `measure_pack.js --probe <screenshot.png> --rect <图标 bounds> --mode ascii|runs` 读出;`--probe` 只接受 PNG,不能传 SVG);形状不符即判定失败。这一步是**人工比对**——静态检查里的 `media-element-count-mismatch` 只数几何元素个数,抓不到元素数相同而轮廓不同的情形。**禁止只抽查部分后对未检查文件下全称断言**。对单个图标尺寸,列出 measure_pack 实测值(如有)与代码字面值,偏差 > 30% 即判定失败。
|
|
459
|
+
- **资源引用错用**:资源引用是否与 Android UI 一致;不存在的资源要重新查找正确名; "不允许用任何emoji、硬编码图标"。
|
|
460
|
+
**并且必须确认资源内容本身与 Android 源语义等价 —— 文件存在不等于内容正确**。对由 Android `<vector>` 转换而来的 SVG,打开文件核对:
|
|
461
|
+
- `clip-path` 是否保留(丢失会改变图形的位置、尺寸与可见范围;若 path 铺满 viewport 而靠 clip 裁形,丢失后会渲染成一个纯色矩形)
|
|
462
|
+
- 无 `fillColor` 但有 `strokeColor` 的描边图形是否为 `fill="none"`(被写成具体颜色时,线条图标会渲染成实心色块)
|
|
463
|
+
- `viewBox` 是否与 Android `viewportWidth/Height` 一致
|
|
464
|
+
- **染色通道是否与染色手段匹配**:若代码用 `.fillColor()` 给该 SVG 染色,需要变色的几何**必须位于 `fill` 通道**。`fillColor` 只叠加 fill(SDK `image.d.ts`:*Sets the fill color to be superimposed on the image*),**对 `stroke` 完全无效** —— 描边图形(`fill="none"` + `stroke="#RRGGBB"`)用 `fillColor` 染色时颜色不生效,图形保持原始色(通常是黑)。
|
|
465
|
+
本条与上面那条 `fill="none"` 子项**互为对偶**:上一条要求描边图形必须写成 `fill="none"`(否则线条图标变实心色块),而**正因为它是 `fill="none"`,`fillColor` 才染不上它**。两条必须一起看,不能只满足其中一条就判定通过。
|
|
466
|
+
修法三选一:把描边几何改写成等价的 fill 几何(等宽圆环 = 外圆 + 内圆同 path + `fill-rule="evenodd"`,几何完全等价);改用目标端原生组件;或按状态准备两份已着色素材。
|
|
467
|
+
|
|
468
|
+
- **是否存在铺满 viewBox 的 `fill="none"` 包围盒 path**(形如 `<path d="M0 0h24v24H0z" fill="none"/>`)。`fillColor` 叠加到**全部**几何,`fill="none"` 拦不住它 —— 该 path 会被染成实心色块,盖住整个图标。Android `<vector>` 源**不会**产生这条 path(它是 Material web SVG 的写法),所以它的出现即证明该 SVG 是凭记忆手写的,应从 `res/drawable` 重新转换。
|
|
469
|
+
- **`fill`/`stroke` 是否残留未解析的 `?attr/...` 主题属性**。SVG 不认识 Android 主题属性,该几何不会着色(白框)。需回到 Android 源把它解析成 theme 里的具体颜色。
|
|
470
|
+
- **是否是占位 SVG**(灰底矩形 + 资源名文本)。那是资源转换阶段为保证编译而生成的,不是真实图标,界面上渲染为灰框。
|
|
471
|
+
- **是否是合法 XML —— 同一元素上有没有重复属性**。重复属性在 XML 规范下是**致命错误**,解析器拒绝整份文档,于是该图标**一个像素都画不出来**(空白,而非画错)。这一条与上面各条的性质不同:其余各条都是"先假定文件是有效 SVG,再查语义",而本条查的是"这份文件解析得开吗"——语义检查再多也覆盖不到。实测来源是转换器给每个 path 先写死 `fill-opacity="1"`、再把 Android 的 `fillAlpha` 作为**第二个** `fill-opacity` 追加(一个仓里命中 11/93)。修法:回 Android `<vector>` 源核对该属性,只保留正确取值(转换器先默认后真值时保留后者)。机械兜底:GATE 规则 `media-duplicate-attribute`。
|
|
472
|
+
|
|
473
|
+
这类缺陷**能编译通过且引用名正确**,只能靠读文件内容发现。若 `a2h-resource-convert` 的报告里有 **Vector Fidelity Issues** 章节,先按其 error 条目修资源,再判定本页通过。
|
|
474
|
+
- **过渡实现证据**:逐条列出 Phase 4.2「已声明的状态过渡」清单里的每一项,标注 `已实现` / `已降级`(附降级理由与降级后的实际行为)/ `需上报` / `不适用`(附理由)。**清单缺项、或有条目未标注,即判定失败。**
|
|
475
|
+
|
|
476
|
+
**`已降级` 不是可以自行采用的万能出口。** 当某条过渡的**终态已被任一参考快照捕获**时,本页**不得**自行标 `已降级` —— 只能标 `已实现`,或标 `需上报`(附缺口描述),由父流程决定接受或退回。理由:`已降级` 与 `已实现` 并列为零成本的合法终态,而参考快照拍到的状态**本身就是验收目标**,不是可选的行为细节;允许自我降级等于允许本页自行缩小验收范围。
|
|
477
|
+
判据(机械,与 Phase 4.2 的交叉验证同一条):同一 `resource-id` 在多份快照间的 bounds 或尺寸变化**无法由滚动位移解释**(整块变矮而非整块上移)时,该终态即属"已被捕获"。
|
|
478
|
+
未被任何快照捕获的过渡(纯交互态、无对应快照)仍可自行 `已降级`。 只实现基础态而把过渡整体略过时,**必须**在报告中显式写成 `已降级` 并说明缺什么 —— 不得因为「过渡属于行为、不属于布局」就不登记:参考快照里若已捕获到过渡后的状态(如 `screenshot_scroll_n` 拍到了折叠后的头部),该状态就是本页验收范围的一部分。此项漏检的典型表现是静态首屏满分、一交互就与源 App 不同。
|
|
479
|
+
- **装饰重复检查**:对照 Phase 4.3 的「框架复合控件部件归属表」,逐个确认目标原子组件自带的装饰(输入框底线、光标、图标位、选中标记等)**没有**被手写兄弟节点再画一遍。判据:同一视觉元素在最终树中只应有一个绘制来源。此项漏检的典型表现是界面上多出一条线/一个框,且它的位置常落在组件**外部**(手写节点的盒与原子组件的圆角盒不重合)。
|
|
480
|
+
- **形状错用**:圆角/直角等形状是否与 Android UI 一致;ShapeDrawable 是否保留固定 dp 圆角而非被非等比拉伸
|
|
481
|
+
- **尺寸问题**:图标/组件大小是否与 Android 真实 bounds 一致(measure_pack 已产出实测值时必须采用,**不得用 Material Design 规范记忆值覆盖**——32dp assist chip、24dp 图标等规范值只在参考 App 本身就严格遵守规范时适用;实测为 48dp 时写 24dp 即判定失败)
|
|
482
|
+
- **位置问题**:toolbar 是否在顶部、图标位置是否过左/过右等
|
|
483
|
+
- **路由关系**:UI 与组件间路由是否正确实现
|
|
484
|
+
- **Lottie 集成**(仅当本页注入了 Lottie 条目时校验):
|
|
485
|
+
- `lottie.loadAnimation` 的 `path` 是否与条目 `load_path` 完全一致
|
|
486
|
+
- `loop` / `autoplay` / `contentMode` 是否忠实映射 Android `lottie_loop` / `lottie_autoPlay` / `lottie_scaleType`
|
|
487
|
+
- 是否在 `aboutToDisappear` + `Canvas.onDisAppear` 都调用了 `lottie.destroy(name)`
|
|
488
|
+
- 同页多个动画的 `animateName` 是否唯一
|
|
489
|
+
- 若 Android 用的是下拉刷新动画,`onStateChange` 的 4 个状态分支是否齐全
|
|
490
|
+
|
|
491
|
+
**按本 Phase 开头的轮次规则跑验证迭代(2 轮必跑,第 2 轮有修改则续跑至零修改),并输出验证报告**。
|
|
492
|
+
|
|
493
|
+
### 静态检查器(可选自查,父流程 Step 6.0 会统一跑)
|
|
494
|
+
上表中属于**纯词法**的若干项已实现为脚本,可在本页写完后自查一遍(静态、无需设备与 SDK,秒级):
|
|
495
|
+
```
|
|
496
|
+
node <skill>/scripts/arkts_static_check.js --ets <harmony_project_dir>/{ui_module}/src/main/ets \
|
|
497
|
+
--resources <harmony_project_dir>/{ui_module}/src/main/resources \
|
|
498
|
+
--android <android_project_dir> \
|
|
499
|
+
--ui-info <ui_info_root>
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
- `[GATE]` 段是零歧义缺陷,**必须清零**。其中「块注释被 `*/` 提前闭合」尤其值得提前跑:把含通配符的证据路径(形如 `foo_*/view.xml`)写进 JSDoc 会让编译器报出**数百条**坐标指向注释正文的假错误,从构建输出反推极其费时,而脚本直接点名。
|
|
503
|
+
- `[CANDIDATES]` 段**不是缺陷**,只是把人工核对范围缩小。它列出的撑满容器与常量坐标是否正确,取决于 Android 源侧的锚定方向与层叠次序 —— 词法分析判不了。请把它当作「位置锚点证据」「交互可达性证据」「窗口模式证据」三项的待核对清单,逐条标注来源;**不要因为被列出就直接改代码**,撑满容器多数是合法的。
|
|
504
|
+
- `android-immersive-not-mirrored`(需要 `--android`)把①的证据变成必须逐条回应的清单:Android 侧有 edge-to-edge 调用而鸿蒙侧两层皆无时列出调用点。它是 CAND 而非 GATE,因为命中项可能属于别的 Activity(`CrashActivity`/`ReaderActivity` 等),脚本不知道 page → Activity 的映射 —— 需你按①判断宿主归属,但**不得**用截图或"与基线一致"替代该判断。
|
|
505
|
+
- 其中三条**读 SVG/资源文件内容**(需要 `--resources`),针对同一个失效模式 ——「资源存在」不等于「资源能渲染对」:`fillcolor-on-stroke-svg`(stroke 几何染不上)、`fillcolor-on-full-viewport-path`(`fill="none"` 包围盒被染上 → 整个图标变实心块,同时是"凭记忆手写 SVG"的铁证)、`media-unresolved-theme-attr` / `media-placeholder-in-use`(白框 / 灰框)、`media-empty-svg`(`<svg></svg>` 或 0 字节 → 完全不渲染)。这几条正是"只跑 `ls media/` 确认名字存在"漏掉的那一类。
|
|
506
|
+
- `media-element-count-mismatch` 只在 `measure_pack.json` 的 regions 里**人工写入** `shape_sig` 字段(`{ resource_name, runs, coverage }`)后才生效 —— 没有随包脚本产出该字段,因此默认整条跳过。生效时它只比对 `<path>/<circle>/<rect>/<ellipse>` 的**元素个数**(差异 > 50% 且绝对差 ≥ 2 即 GATE),能抓到「三点 overflow 译成单个圆」这类量级差异,**抓不到**元素数相同而轮廓不同(下载箭头 vs 上传箭头)的情形。轮廓一致性没有机械门禁,仍靠 Phase 5 的人工比对。
|
|
507
|
+
- 脚本覆盖不到的项(取值规则作用域、mock 是否复现参考、自证据是否自洽)仍须模型复核走完。**`[GATE]` 清零不构成本页通过的依据。**
|
|
508
|
+
|
|
509
|
+
### 常见误译速查(编译期无法发现,逐条自查)
|
|
510
|
+
|
|
511
|
+
以下缺陷的共同特征是:**语法合法、编译通过、零告警**,静态引用检查也全部通过 ——
|
|
512
|
+
只有读代码语义或跑真机才会暴露。生成代码时就应规避,而非事后补救。
|
|
513
|
+
|
|
514
|
+
| 误译 | 为什么会发生 | 后果 | 自查方法 |
|
|
515
|
+
|---|---|---|---|
|
|
516
|
+
| `.width('100%')` 与 `.margin({left,right})` 叠加 | 直觉认为 `100%` 会自动扣掉 margin | 实际宽度 = 父宽 + 左右 margin,**右侧溢出**、圆角被裁 | 搜同一元素上同时出现 `width('100%')` 与左右 `margin`;改为父 `padding` 或 `layoutWeight(1)` |
|
|
517
|
+
| 用父容器尺寸充当子元素的约束尺寸 | Android `0dp` + 约束被当成「填满父容器」 | 区域偏大/偏高,挤压后续内容 | 拿参考 view tree 的实测尺寸与父容器比;不等就不能用 `'100%'` |
|
|
518
|
+
| 负 margin 被丢弃 | 负值看起来像笔误 | 整块内容下移(常见于顶部贴合状态栏) | 在 Android 布局里 grep `margin.*="-`,逐个确认已译出 |
|
|
519
|
+
| `expandSafeArea` 策略页间不一致 | 逐页转换时容易漏设 | 部分页面内容偏移一个状态栏高度 | 汇总本批所有页面的顶层 safeArea 设置,必须一致或有据可依 |
|
|
520
|
+
| 只设了组件层 `expandSafeArea`,漏了窗口层 `setWindowLayoutFullScreen` | 沉浸式需要窗口层 + 组件层**两层**配合;只看组件层时,页面代码「看起来完全正确」,且**全批页面会一致地错**,跨页一致性检查照样满分通过 | 系统仍为状态栏保留空间,页面顶部出现一条系统栏底色的空白带 | 打开 `UIAbility`/`EntryAbility` 确认 `setWindowLayoutFullScreen(true)` 已调用;一致性检查不能替代正确性检查 |
|
|
521
|
+
| 右/下锚定元素被写成常量 `.position()` 坐标 | 参考 view tree 只给出「该设备上的绝对解」,量出来的数值在参考分辨率上完全正确 | 屏幕更窄时元素右侧移出可视区**被裁切** | 对每个坐标问「把屏幕宽度改小 10%,这个值还对吗」;Android 侧是 `constraintEnd_toEndOf`/`Bottom_toBottomOf`/`gravity="end"` 的,必须用 `alignRules()`/`Alignment`/父尺寸表达式 |
|
|
522
|
+
| `<vector>` 的 `clip-path` / `fill` 语义丢失 | 引用名正确、文件存在,检查即通过 | 图标位置尺寸错乱,或线条图标变实心色块 | 打开 SVG 核对;资源侧可用 `a2h-resource-convert` 的 `scripts/svg_fidelity_check.js` |
|
|
523
|
+
| `$r()` 传模板字符串(如 `$r(\`app.media.${name}\`)`) | 看起来能拼出正确资源名 | `$r()` 要求**字面量**,拼接在运行时不可靠,图片可能不显示 | 动态选图必须建显式映射表(键 → `$r('app.media.xxx')` 字面量),并给未命中的默认值 |
|
|
524
|
+
| 字符串资源残留字面引号 | Android `"…"` 是转义语法,不是文本内容 | UI 上出现可见的引号 | 扫 `element/string.json`,找首尾同为 `"` 的值 |
|
|
525
|
+
| 撑满容器承载底/右锚定,吞掉其下点击 | 用 `width/height('100%')` + 贴边对齐表达锚定,既满足「不写死坐标」又视觉正确 | 该容器成为层叠末位即最上层,其下所有可点击元素**点击无响应**;因其自身无 `onClick`,事件被静默消费,控制台亦无输出 | 列出所有撑满的容器,逐个确认可交互或已设 `hitTestBehavior(HitTestMode.None)`;对每个可点击元素确认其上无覆盖的兄弟节点 |
|
|
526
|
+
| 取值规则的作用域/粒度被改变 | 源侧把某值提到循环外当常量复用,译文按直觉改成每条目自算(或反向) | 映射表与 `$r()` 全部正确、逐项检查全过,渲染出的变体系统性错误 | 追到该值在源侧被生成并传入的调用点,核对作用域(每条目/每屏/每请求)与译文一致 |
|
|
527
|
+
| **`padding` 语义反向**:把 Android 的「N dp 图标 + P dp padding」直译成 `.width(N).padding(P)` | Android 的 `padding` 在声明尺寸**之外**(总占位 N+2P),ArkUI 在**之内**。逐属性对照翻译时两边字面完全一致,最像"忠实" | 内容区被压成 `N-2P`(P≥N/2 时为 **0**)——元素**照常占位、完全不可见**。相邻元素位置全对,只有它消失;编译、`[GATE]`、资源引用检查全过 | 逐个列出同时带 `.width()/.height()` 与 `.padding()` 的元素,核对声明值**是否已包含** padding(Android 侧 `src` N dp + `padding` P dp ⇒ ArkUI 应写 `N+2P`)。`Image` + 圆形图标按钮这类"图标 + 内边距"样式是高发处 |
|
|
528
|
+
| 平台默认值差异 | 同名属性两端默认行为不同(如 sheet 键盘避让 ArkUI 默认 `TRANSLATE_AND_SCROLL`,Android 默认 `adjustResize` 是压缩) | 参考截图与实现产出不一致,且无任何报错 | 凡依赖"默认行为"的属性,查 SDK `.d.ts` 确认默认值;与 Android 不一致时**显式设置**,不要继承默认 |
|
|
529
|
+
| **`.fillColor()` 染 `stroke` 着色的 SVG** | 上游规则(本表所在检查项的 `fill="none"` 子项)**要求**描边图形写成 `fill="none"`,于是 SVG 文件内容完全正确;染色代码也「看起来」正确。两边都对,组合起来才错 | 颜色**完全不生效**,图形保持原始色(通常是黑)。编译通过、资源存在、引用名正确、逐属性对照亦「忠实」;若未选中态本就该是深色,缺陷只在选中态显形 | 打开 SVG:需变色的几何若是 `stroke="#RRGGBB"` + `fill="none"`,`fillColor` 无效。改写成等价 fill 几何(等宽圆环 = 外圆 + 内圆同 path + `fill-rule="evenodd"`)、改用原生组件、或按状态备两份已着色素材 |
|
|
530
|
+
| **容器对齐默认值两端相反** | ArkUI `Column` 默认 `HorizontalAlign.Center`、`Row` 默认居中;Android `LinearLayout` / Compose `Column` 默认 **start**。两侧都不写对齐属性时字面完全一致,逐属性对照会确认成「忠实」 | 收缩宽度的子节点(尤其 `Text`、`@Builder` 里的标题)整体居中,而 Android 是齐左 | 列出所有直接含 `Text` 或含渲染 `Text` 的 `@Builder` 调用的 `Column`/`Row`,确认已显式写 `.alignItems()`。**Android 侧「没写 gravity」等于 start,不等于 ArkUI 的「没写 alignItems」** —— 必须显式译成 `.alignItems(HorizontalAlign.Start)`。检查器 CAND 规则 `container-align-unset` 会列出待核对位置 |
|
|
531
|
+
| **凭记忆手写图标 SVG** | 目标图标在 `res/drawable` 里不存在(Compose Material Icons、Phosphor 等是编译进库的 `ImageVector`),而规则只说"不许硬编码、不许 emoji",没给第三条路,于是按记忆写出 Material 的 web SVG | Material web SVG 的标准写法带一条铺满 viewBox 的 `<path d="M0 0h24v24H0z" fill="none"/>` 包围盒。`fillColor` 叠加到**全部**几何、`fill="none"` 拦不住它 → 整个图标渲染成**实心色块**(黑框)。编译、引用名、文件存在全过 | Android `<vector>` 源**不会**产生这条包围盒 path(实测 10 个安卓仓零命中),所以它的出现本身即证明该文件是手写的。GATE 规则 `fillcolor-on-full-viewport-path` 直接点名;拿不到真实素材时按本节的图标来源分级登记 `已降级`,不要手写 |
|
|
532
|
+
| **消费了内容画不出来的 media** | 页面侧惯于用 `ls media/` 确认资源"存在"就通过,从不打开文件 | 两类都能编译、都过引用存在性检查:① 转换器把未解析的 `?attr/...` 主题属性留在了 `fill`/`stroke` 里,SVG 不认识该值 → 白框;② 资源转换阶段生成的**占位 SVG**(灰底矩形 + 资源名文本)被当真图标消费 → 灰框 | GATE 规则 `media-unresolved-theme-attr` / `media-placeholder-in-use` 从文件内容机械判定,无需读 `resource_mapping.md`。**「文件存在」永远不等于「能渲染对」** |
|
|
533
|
+
| **在原子组件之外重建它自带的装饰** | Android view tree 暴露了框架复合控件的内部件(如 SearchView 的 `search_plate` 下划线),逐个映射显得更「忠实」,而 ArkUI 的 `Search` 已经自带该装饰 | 同一视觉元素被画两遍;手写节点的盒与原子组件的圆角/内缩盒不重合时,多出的装饰会落在组件**外部**,表现为「多了一条线」 | 先列出目标原子组件默认已绘制的装饰,再逐个确认无同义手写兄弟节点(见 Phase 4.3 的部件归属表) |
|
|
534
|
+
| **(V1)用 `get x()` 给 UI 暴露派生值** | `@Track` 的完备性直觉是**面向字段**的:「所有参与渲染的**属性**都加 `@Track`」——15 个字段全加了,检查项自然判过。而 getter **不是字段**,落在这条直觉的射程之外。更隐蔽的是 **V1 没有 `@Computed`**(那是 V2 能力),派生值在 V1 侧没有正面出路,于是「裸 getter + 反正读取会重新求值」成了极自然的推理 —— 该推理关于**响应性**是对的,关于**合法性**是错的 | **首帧运行时崩溃**:`BusinessError 140110: Illegal usage of not @Track'ed property 'x' on UI!`。编译通过、零告警、`[GATE]` 全清、字段侧完备性检查全过、逐属性对照亦「忠实」;只有真机拉起页面才会显形(`/data/log/faultlog/faultlogger/jscrash-<bundle>`) | 对每个「用了 `@Track` 的 `@Observed` 类」grep `get [a-zA-Z]*(`:命中即缺陷。改成**普通方法**(`title(): string`,UI 侧 `vm.title()`;原型方法不经过 `@Track` 代理),或提升为真正的 `@Track` 字段。检查的对象应是**「`build()` 里读到的每一个成员」**,而不是「类里声明的每一个属性」。GATE 规则 `track-class-getter-in-ui` 机械点名 |
|
|
535
|
+
|
|
536
|
+
## 全局准则
|
|
537
|
+
|
|
538
|
+
- **严格保真**:layout 结构、组件层级、资源引用必须与 Android 源一一对应;不增删/重排 UI 元素
|
|
539
|
+
- **资源精确匹配**:所有 color/string/dimension/image 必须有对应 HarmonyOS 资源;不要硬编码
|
|
540
|
+
- **可编译性是必须的**:已实现目标接真实事件;未实现才 mock 至可编译
|
|
541
|
+
- 仅用声明式 ArkUI;绝不写命令式 DOM 操作
|
|
542
|
+
- 资源一律 `$r('app.type.name')`
|
|
543
|
+
- 留 `// TODO:` 注释给:业务逻辑、ViewModel 数据绑定、未转换的目标 Activity
|
|
544
|
+
- 响应式布局:优先百分比宽度 + Flex,少用固定尺寸
|
|
545
|
+
- 无障碍:`content-desc` → `.accessibilityText()`,有意义的 `text` → 正确的 label
|
|
546
|
+
- 嵌套 > 5 层时把相关 UI 拆为 `@Component` 子组件
|
|
547
|
+
- 任一 mapping 文件缺失/为空:用内置知识继续,并在报告中注明
|