Harmony os 技术实战|拼豆制图02:50 张图纸的 Repository 与轻量预览
Harmony os 技术实战|拼豆制图02:50 张图纸的 Repository 与轻量预览
图库从 7 张扩到 50 张后,最容易遇到的问题不是数据不够,而是页面一打开就把所有完整编号矩阵都渲染出来。拼豆图纸每张都可能有 70 x 70 个格子,如果列表页把这些格子全部创建成 UI,用户只是滑动图库,却要替详情页付出成本。
这篇文章会把问题拆到能落地的层面,重点解决:
- 把图纸种子、资源矩阵和页面展示分开。
- 列表页使用 previewCells,详情页才使用 chartCells。
- 用统计函数复算颜色数量和拼豆总数,避免手填失真。
- 保留没有真实矩阵时的程序化图纸生成能力。
问题不是 50 张图,而是 50 张完整矩阵
拼豆制图的图库包含动漫人物、游戏人物、爱豆、场景、潮玩盲盒等分类。列表页需要快速展示封面、标题、难度、色数和收藏按钮;详情页才需要每一格色号、坐标、图例和放大查看。如果把这两种需求混成一个对象,在列表页就会过度计算。
项目里已经把PatternRepository放在 services 层,这是一个正确方向。它既不是简单常量表,也不是 UI 组件;它负责把种子数据、真实矩阵资产和颜色统计拼成页面可消费的Pattern。这样图库页只管渲染卡片,编号图纸页只管展示完整施工图。
落地时可以先抓三个信号:
Repository的职责是否只在一个层级里被定义,而不是页面、服务和资源各写一份。Pattern相关状态是否能从用户入口一路追到结果页,中间没有隐式副作用。- 遇到空数据、取消操作或边界输入时,页面是否还能停在可继续操作的状态。
Repository 应该拥有数据组装权
Repository 的边界可以定得更清楚:它接收图纸 ID 和加载策略,返回稳定的业务对象。页面不关心矩阵来自脚本、资源文件还是程序化算法;页面只关心对象里有没有 previewCells、chartCells 和 colorStats。
| 决策点 | 推荐做法 | 避免的问题 |
|---|---|---|
| 列表展示 | 读取previewCells | 列表滑动卡顿 |
| 详情展示 | 进入编号页再取完整chartCells | 首屏承担全部矩阵成本 |
| 颜色统计 | 从格子数组复算 | 手填统计和实际图纸不一致 |
| 资源缺省 | 保留程序化生成兜底 | 某张图缺资源导致页面空白 |
这张表真正约束的是变更顺序:先固定“列表展示”的归属,再处理“详情展示”的输入输出,最后围绕“颜色统计”做回归。只要这三处没有漂移,后续增加页面、资源或参数时,改动就不会一路扩散到无关模块。实际排查时也建议按这个顺序记录结论:哪一层接收输入、哪一层生成结果、哪一层负责失败后的恢复。记录得越具体,下一轮迭代越不容易把已经稳定的路径改坏。
Pattern 模型要覆盖列表和施工图
模型要同时服务列表和详情,但不要让页面猜字段含义。previewCells是小尺寸预览,chartCells是施工图矩阵;二者都使用同一个BeadCell,这样渲染组件可以复用。
1. 模型同时覆盖预览和完整图纸
一个Pattern可以被列表页和详情页共用,但字段含义必须区分清楚。
exportinterfaceBeadCell{id:string;row:number;col:number;colorId:string;colorCode:string;hex:string;isEmpty:boolean;}exportinterfacePattern{id:string;title:string;category:string;width:number;height:number;previewCells:BeadCell[];chartCells:BeadCell[];colorStats:BeadColorStat[];}previewCells解决列表页轻量展示,chartCells解决施工图细节。两者共用BeadCell,避免写两套渲染逻辑。
按需创建预览和完整图纸
Repository 的核心不是返回数组,而是把来源不同的数据统一成Pattern。真实矩阵存在时使用资源矩阵;没有资源时使用程序化图形,保证每张图都能进入详情页。
2. Repository 暴露列表和按 ID 查询
页面不用知道种子数组在哪里,也不用关心真实矩阵是否存在。
exportclassPatternRepository{staticgetPatterns():Pattern[]{constseeds=PatternRepository.createSeeds();constresult:Pattern[]=[];for(leti=0;i<seeds.length;i++){result.push(PatternRepository.createPattern(seeds[i],false));}returnresult;}staticgetPatternById(id:string):Pattern|null{constseeds=PatternRepository.createSeeds();for(leti=0;i<seeds.length;i++){if(seeds[i].id===id){returnPatternRepository.createPattern(seeds[i],true);}}returnnull;}}列表读取时includeFullChart=false,详情读取时才打开完整矩阵。这个参数把性能策略放在 Repository,而不是散在页面分支里。
矩阵编码要能被脚本稳定生成
图库页的渲染策略应该克制。列表卡片用预览格子和封面信息,不直接渲染完整图纸。这样搜索和分类切换时,只需要处理轻对象。
3. 真实矩阵和程序化图纸走同一出口
不是每张图一开始都有真实矩阵,所以需要一个统一的创建函数。
privatestaticcreatePattern(seed:PatternSeed,includeFullChart:boolean):Pattern{constassetChart=PatternAssetCharts.get(seed.id);constpalette=assetChart===null?seed.palette:PatternRepository.assetPalette(seed.id,assetChart.colors);constpreviewCells=assetChart===null?PatternRepository.createCells(`${seed.id}-preview`,palette,seed.variant,16):PatternRepository.createCellsFromRows(`${seed.id}-preview`,palette,assetChart.previewRows);constchartCells=assetChart!==null&&includeFullChart?PatternRepository.createCellsFromRows(seed.id,palette,assetChart.chartRows):previewCells;returnPatternRepository.buildPattern(seed,palette,previewCells,chartCells);}这里的重点是出口一致:页面拿到的永远是Pattern。资源矩阵、程序化兜底、轻量预览都被收在 Repository 内部。
统计结果从格子反推
统计信息不要做成静态文本。色号数量、每色颗数、总颗数都可以从格子里复算,导出图和详情页也会得到同一组数字。
4. 用字符矩阵表达颜色索引
资源矩阵适合脚本生成,字符编码比直接写 4900 个对象更容易维护。
privatestaticcreateCellsFromRows(id:string,palette:PaletteColor[],rows:string[]):BeadCell[]{constcells:BeadCell[]=[];for(letrow=0;row<rows.length;row++){constsourceRow=rows[row];for(letcol=0;col<sourceRow.length;col++){consttoken=sourceRow.substring(col,col+1);constcolorIndex=PatternRepository.assetColorIndex(token);constcolor=colorIndex<0?null:palette[colorIndex];cells.push(PatternRepository.createCell(id,row,col,color));}}returncells;}字符.表示空格,数字和字母表示颜色索引。脚本只要输出稳定字符串,就能被 Repository 转成统一的格子对象。
页面只消费业务对象
批量生成后的矩阵资源需要有稳定命名。文件名、图纸 ID 和资源映射要保持同一套规则,否则 50 张图扩到 100 张时,最先出问题的是错配而不是算法。
5. 统计从格子反推
图例和总颗数不要手写,避免换图后忘记同步。
privatestaticcountColors(palette:PaletteColor[],cells:BeadCell[]):BeadColorStat[]{conststats:BeadColorStat[]=[];for(leti=0;i<palette.length;i++){constcolor=palette[i];letcount=0;for(letcellIndex=0;cellIndex<cells.length;cellIndex++){if(cells[cellIndex].colorId===color.id){count++;}}stats.push({colorId:color.id,colorCode:color.code,colorName:color.name,hex:color.hex,count});}returnstats;}统计函数拥有完整格子输入,因此能服务详情页、导出图和个人页作品统计。后续如果增加材料清单,也可以从这里派生。
验证图库加载路径
实际项目里,验证不能只看页面有没有打开。更稳的做法是把入口、状态、边界数据和失败路径都走一遍,尤其是拼豆图纸这种“看起来能显示,放大后才暴露问题”的功能。
验证前建议准备一组固定样本:一条正常路径、一条空数据路径、一条失败路径,再加一条连续操作路径。这样每次改动都能比较同一批场景,不会只凭当前页面肉眼感觉判断。如果涉及屏幕尺寸、资源替换或系统能力,还要保留修改前后的截图和关键输入,方便回退时确认差异来自哪里。
hvigor assembleHap--no-daemonWrite-Host"手动验证:图库滚动 -> 分类切换 -> 打开编号图 -> 返回图库 -> 再打开另一张图"- 图库首次打开时不应明显卡顿,分类切换后卡片高度和列数保持稳定。
- 随机打开 5 张图纸,确认详情页宽高、色数、总颗数与图例一致。
- 缺少真实矩阵的图纸也能进入编号图纸页,不能出现空白详情。
- 搜索结果中的图纸点击后仍能读取完整 chartCells。
- 批量替换资源后,图纸 ID 和矩阵资源要一一对应。
常见问题和处理
| 现象 | 先看哪里 | 处理方式 |
|---|---|---|
| 图库滑动卡顿 | 列表页是否渲染完整矩阵 | 列表只渲染 previewCells 或封面图 |
| 详情色号为空 | assetColorIndex 和 palette 长度 | 检查矩阵字符是否越界 |
| 图例数量不对 | countColors 输入的是预览还是完整图 | 详情和导出使用 chartCells 统计 |
| 某张图打不开 | PatternAssetCharts.get 返回值 | 缺资源时回退程序化图纸 |
继续扩展图库时的做法
当图库继续扩容,可以把种子数据拆成 JSON5 或生成脚本输出的 ArkTS 文件,Repository 只保留解析和统计逻辑。若后续支持用户下载主题包,也可以把PatternAssetCharts.get替换成异步读取,本层接口仍然返回同样的Pattern。
继续推进时建议保持三条约束:
- 先让现有主路径可回归,再拆更细的组件或服务。
- 新增状态必须能说明来源、更新时机和失败后的保留策略。
- 新增资源或配置要能从页面反查到生成来源,避免后期只靠人工记忆维护。
小结
图库稳定的核心是按场景给数据减重。列表页要快,详情页要准,统计要能复算。Repository 负责把资源和算法收成统一模型,页面就不会被 50 张图纸的矩阵细节拖住。
