原生鸿蒙像素画板实战 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,因此能直接看出模板行列是否错位、透明标记是否被错误转成白色。与其只截一张卡片截图,不如在开发阶段同时对照模板文本、完整预览格和导入后的图层。三者一致时,才能确认素材链路没有在某一步发生数据转换偏差。

怎样确认素材从预览到导入一致
素材检查应比对预览、完整导入和现有画布导入三种结果,而不是只看卡片样式。
- 打开素材卡片,确认预览色块与模板导入后的主要轮廓一致。
- 将同一素材分别导入新项目和已有项目,确认前者使用模板尺寸,后者居中落在透明图层上。
- 把目标画布设得比素材小,确认越界部分被安全裁掉而不会数组越界。
- 导入后调整图层可见性和透明度,确认素材仍按普通图层参与合成。
素材库检查记录
卡片预览与导入轮廓一致
完整导入保留模板原始像素
已有画布使用透明图层居中放置
素材导入后仍可按普通图层编辑

素材库的定位仍是可编辑模板
当前素材库优先承载本地像素模板,不做照片级资源管理、版权分发或在线编辑市场。它的目标是让项目内常用的像素图案可复用、可预览、可继续编辑。
这一篇的重点是让素材的预览、导入和后续编辑共享同一份像素来源。本地素材可用以后,下一篇再处理远程更新和网络失败时怎样保留可用的离线体验。
更多推荐


所有评论(0)