原生鸿蒙像素画板实战 14:工具状态管理

发布时间:2026/7/21 1:12:12
原生鸿蒙像素画板实战 14:工具状态管理 编辑器工具一多最怕的不是按钮不够而是状态互相污染。用户刚把橡皮擦调成 6px不应该切回铅笔后发现笔刷也变成 6px填充透明度的调整也不该影响橡皮擦。形状工具还多了 activeShape选区又有“是否已选中”和尺寸提示。若这些判断散落在每个组件里页面很快会出现同一状态被不同面板解释成不同结果。bitArt 把工具标签、当前尺寸、透明度和写入规则集中到 EditorToolStateService。工具状态的核心是区分共享字段和专属字段常见的实现方式是一个全局 size 和一个全局 opacity。短期看很省事实际会让不同工具失去独立配置用户每次切换都得重新调参数。另一个常见问题是 UI 直接写 activeTool 后历史面板、顶部状态条、工具栏又各自写一遍“当前工具名称”形状或选区状态一变就有一个区域忘了更新。工具配置由谁保存谁只负责显示工具状态不是把几个字段塞进页面就结束了。尺寸、透明度和标签都需要根据当前工具推导才能让面板切换后仍然说同一种状态语言。同一个滑杆如何服务不同工具当前服务明确区分 brushSize 与 eraserSize透明度则分为 brushOpacity、eraserOpacity、fillOpacity。getActivePaintSize 和 getActiveToolOpacity 根据 activeTool 取值setActivePaintSize 与 setActiveToolOpacity 则只写回真正属于当前工具的字段。页面因此只需绑定“当前可调参数”而不必知道当前是铅笔还是橡皮擦。形状名称也通过 normalizeShape 和 getShapeLabel 统一处理。无效的持久化值回退到 line顶部状态则可根据 activeTool、activeShape 和 selectionActive 推导为“形状 · 椭圆”或“选区 8x6”。这让显示文本成为状态的函数而不是额外保存的一份易过期数据。工具服务的价值体现在页面只问“当前参数是什么”不必自己维护一套工具分支。static getActivePaintSize(activeTool: string, brushSize: number, eraserSize: number): number { return activeTool eraser ? eraserSize : brushSize; } static getActiveToolOpacity(activeTool: string, brushOpacity: number, eraserOpacity: number, fillOpacity: number): number { if (activeTool eraser) return eraserOpacity; if (activeTool fill) return fillOpacity; return brushOpacity; }状态标签要从事实推导而不是手工拼接工具状态服务不直接接触 Canvas也不直接安排保存。它只返回 changed、下一组尺寸或透明度。工作流收到 changed 后再做 requestUiRefresh 与 markProjectDirty。这个分层很朴素却能避免一个滑杆拖动触发多条保存和多次历史记录。参数规范化也集中在服务里size 经过 normalizePaintSize透明度四舍五入并夹在 0 到 100 之间。static setActivePaintSize(activeTool: string, brushSize: number, eraserSize: number, size: number) { const activePaintSize EditorToolStateService.getActivePaintSize(activeTool, brushSize, eraserSize); const normalizedSize normalizePaintSize(size, activePaintSize); return activeTool eraser ? { changed: eraserSize ! normalizedSize, brushSize, eraserSize: normalizedSize } : { changed: brushSize ! normalizedSize, brushSize: normalizedSize, eraserSize }; }参数属于工具显示文本属于推导结果当前工具改变时旧工具的参数仍留在自己的字段中当前工具读取时才切换到正确值。选择形状只影响 activeShape不会覆盖画笔或填充配置。对于选区selectionActive 和宽高来自画布状态而不是工具服务凭空维护。这样“工具配置”和“当前编辑对象”保持了两个独立来源。尺寸和透明度的字段归属固定后切换工具只是换读取视图不会把旧工具的配置覆盖掉。static getActiveToolStatusLabel(activeTool: string, activeShape: string, selectionActive: boolean, selectionWidth: number, selectionHeight: number): string { if (activeTool shape) return 形状 · EditorToolStateService.getShapeLabel(activeShape); if (activeTool selection selectionActive) { return 选区 selectionWidth.toString() x selectionHeight.toString(); } return EditorToolStateService.getToolLabel(activeTool); }工具切换时常见的状态串扰不要把铅笔、橡皮擦和填充共用一个可写的尺寸或透明度字段。不要把“当前状态文本”持久化为独立字符串应根据 activeTool 和实际选区大小计算。参数变化前先规范化避免恢复项目后出现负数尺寸、超过 100 的透明度或未知形状。这种状态拆分还有一个实际收益功能新增时可预测。增加喷枪或镜像笔刷时只需决定它复用哪一种参数模型再在服务里添加映射而不是在每个控制面板加一串 if。对复杂编辑器而言规则集中比一开始少写几个字段更重要。从面板行为反查工具状态工具状态可以用一张切换表来回归先为铅笔设置尺寸和透明度再为橡皮擦设置另一组值为填充设置第三组透明度然后按照“铅笔—填充—橡皮擦—形状—选区—铅笔”的顺序切换。每到一个工具都读取顶部标签、滑杆值和实际绘制结果。若其中一个值被上一个工具覆盖问题通常不在控件而在参数归属或归一化服务。另一个值得检查的路径是项目恢复。把当前工具、形状、三类透明度和两种尺寸保存后重开项目确认无效值被规范化、合法值保持原样。这样能避免只在实时操作正常、恢复后却出现未知形状或 0px 笔刷。对于编辑器而言状态切换和状态恢复同样重要二者都应该由同一个服务规则解释。新工具加入时如何避免状态爆炸工具服务还为后续扩展留出了一个实用检查点新增工具前先问它属于哪一类参数模型。若它与铅笔一样使用大小和透明度可以复用现有映射若它只读数据例如取色器就不应在尺寸滑杆上制造一个无意义的值若它拥有新的专属参数则应新建清楚的字段和规范化函数而不是借用 brushSize 再给它附加隐藏语义。这种分类也能改善界面可读性。工具栏只展示当前工具真正可用的控制项形状工具显示形状选择选区工具显示范围信息取色器不显示无效的画笔尺寸。用户不需要猜“这个滑杆会不会影响当前操作”代码也不需要维护一堆仅为隐藏控件而存在的临时条件。怎样确认工具参数能独立保留工具状态检查以切换前后参数是否各自保留为主不把 UI 标签正常误当作配置正确。分别设置铅笔和橡皮擦尺寸来回切换工具确认两个值各自保留。设置填充透明度后切换到画笔确认画笔透明度不变再切回填充确认原值仍在。把 activeShape 恢复为非法字符串确认界面和绘制逻辑回退到直线。激活选区后观察顶部标签确认宽高随拖动变化清除选区后不再显示旧尺寸。工具状态检查记录 铅笔与橡皮尺寸独立保存 三类透明度互不覆盖 无效形状回退为直线 选区标签来自实时边界不保存旧文本工具状态并不是完整命令系统当前工具状态不是完整的命令系统也没有为每个参数建立独立的撤销步骤。笔刷尺寸和透明度属于项目配置用户通常不希望撤销一笔时顺带回退工具滑杆像素修改才进入历史栈。这一篇的重点是让每个工具保存自己的配置并让显示状态由事实推导。工具能稳定切换后颜色输入就是下一块容易失控的状态调色板、RGB、HEX 和背景色要如何对齐。