原生鸿蒙像素画板实战 18:素材库预览
素材库在编辑器里不应只是几张缩略图。对像素画板来说素材本身最好仍是可编辑的像素数据用户在面板中看到的预览、导入后的图层和调色板应当来自同一份模板而不是三套彼此对不上的图片资源。bitArt 的素材库把本地模板、预览格子和导入结果放在同一条数据链路里素材卡片只是这条链路的展示端。先定义素材的可编辑数据而不是只存缩略图若把素材库只做成 PNG 列表用户点入后还需要额外解析或重新绘制缩略图与实际导入内容容易脱节。另一种做法是把模板直接塞进组件短期能显示后续一旦要加远程刷新或缓存UI 会承担数据源选择、坐标计算和颜色透明处理维护难度会迅速上升。素材卡片不应承担哪些数据职责素材库如果只输出一张缩略图后续很快会变成无法编辑的黑盒。让预览和导入共享模板数据才能保证用户看到什么、导入什么、继续编辑什么。预览网格必须从模板实时推导当前 MaterialLibraryService 先从缓存或本地提供者取得 MaterialLibrarySnapshot。每个素材项只保存 ID、类型、名称和 localTemplateId 等描述信息真正的像素内容由 BeadTemplateService 生成。createPreviewCells 将模板采样为小格子createFullPreviewGrid 则按实际宽高展开完整网格。两者都从同一份颜色符号表读取所以卡片和导入结果不会各自演化。导入到当前画布时服务先建立透明目标数组再计算起始行列把模板像素居中写入。只有落在目标画布范围内的坐标才会写入四周继续保持透明。这保证素材不会把用户已有内容之外的区域填成白底。预览格子从模板颜色计算而来卡片不持有自己的像素副本。static createPreviewCells(material: MaterialLibraryItem): MaterialPreviewCell[] { const template MaterialLibraryService.findLocalTemplate(material.localTemplateId); if (template null) return []; const beadCells BeadTemplateService.createPreviewCells(template); const cells: MaterialPreviewCell[] []; for (let index 0; index beadCells.length; index) { cells.push({ id: beadCells[index].id, color: beadCells[index].color }); } return cells; }导入已有画布时要保留透明边界素材预览并不需要把每个模板按原尺寸渲染到卡片上。小预览按固定采样尺寸读取代表性格子完整预览才迭代每行每列。透明像素在展示层可转换成带 alpha 的透明色导入层则继续保留内部透明标记。显示语义与编辑语义分别明确就不会把“看起来是白色”的预览误写进画布。const targetPixels PixelCanvasService.createTransparentPixels(canvasWidth, canvasHeight); const startRow Math.floor((canvasHeight - sourceHeight) / 2); const startCol Math.floor((canvasWidth - sourceWidth) / 2); for (let row 0; row sourceHeight; row) { for (let col 0; col sourceWidth; col) { const targetRow startRow row; const targetCol startCol col; if (targetRow 0 || targetRow canvasHeight || targetCol 0 || targetCol canvasWidth) continue; targetPixels[targetRow * canvasWidth targetCol] sourcePixels[row * sourceWidth col]; } }素材导入结果如何交回工作区素材库的输入是快照输出是 MaterialTemplateImportResult 或 MaterialCanvasLayerResult。页面拿到输出后决定新建项目还是加为新图层并负责记录历史、标脏和刷新素材服务本身不直接修改当前项目。这条边界让同一素材可以在新建画布和已有项目两个入口复用。素材服务输出的是描述与像素结果页面把它接入新建或新增图层流程后才产生项目级变化。const importResult MaterialLibraryService.createMaterialImportResult(materialId); if (importResult null) return null; return { pixels: targetPixels, projectName: importResult.projectName, activeColor: importResult.activeColor, paletteColors: importResult.paletteColors };素材预览和导入最容易脱节的位置不要只保存预览图片应该让预览和导入都来自同一个可编辑模板。在已有画布中导入时应从透明数组开始不能用白色背景覆盖用户画面。卡片小预览可以采样但真正导入时必须使用完整模板像素不能放大采样结果。素材库的好处不只是提供快捷入口。它把重复出现的像素结构、推荐配色和画布尺寸变成有来源的数据。用户导入后仍能像普通图层一样隐藏、移动、改色或删除素材不会成为一个无法拆开的位图黑盒。让预览与导入共用一组证据素材预览的验证可以从一个包含透明、边缘颜色和多种主色的模板开始。先看小卡片能不能辨认主题轮廓再打开完整预览确认它没有用白色替代透明色最后把素材导入到空项目和已有项目。三处看到的颜色、图案方向和主要比例应一致只是卡片采用了更低的采样密度。已有项目导入时要特别检查居中和裁剪。选择比画布更小的素材应在透明新层中居中选择更大的素材应安全裁掉越界部分同时保持画布原尺寸。导入后再做隐藏、调整不透明度、移动和撤销能验证它是否真的进入了普通图层链路。这样的检查也能防止未来为了优化卡片预览而误把低分辨率缓存作为正式像素来源。素材元数据也应成为可检查的契约除了像素本身素材项还需要稳定的元数据唯一 ID、类型、分类、名称、模板 ID、尺寸和颜色数量。卡片列表根据这些信息分组和排序导入流程则用 localTemplateId 找到真实模板。若页面直接用显示名称作为主键重命名或多语言显示都会让导入找错对象。把 ID 与显示文本分开素材库后续增加筛选、远程更新或收藏状态时才能继续稳定扩展。完整预览网格还可以成为调试工具。它从 importResult 的第一层像素逐格建立 MaterialPreviewCell因此能直接看出模板行列是否错位、透明标记是否被错误转成白色。与其只截一张卡片截图不如在开发阶段同时对照模板文本、完整预览格和导入后的图层。三者一致时才能确认素材链路没有在某一步发生数据转换偏差。怎样确认素材从预览到导入一致素材检查应比对预览、完整导入和现有画布导入三种结果而不是只看卡片样式。打开素材卡片确认预览色块与模板导入后的主要轮廓一致。将同一素材分别导入新项目和已有项目确认前者使用模板尺寸后者居中落在透明图层上。把目标画布设得比素材小确认越界部分被安全裁掉而不会数组越界。导入后调整图层可见性和透明度确认素材仍按普通图层参与合成。素材库检查记录 卡片预览与导入轮廓一致 完整导入保留模板原始像素 已有画布使用透明图层居中放置 素材导入后仍可按普通图层编辑素材库的定位仍是可编辑模板当前素材库优先承载本地像素模板不做照片级资源管理、版权分发或在线编辑市场。它的目标是让项目内常用的像素图案可复用、可预览、可继续编辑。这一篇的重点是让素材的预览、导入和后续编辑共享同一份像素来源。本地素材可用以后下一篇再处理远程更新和网络失败时怎样保留可用的离线体验。