RenderObject 基础
本文中的业务组件示例使用:
import { RenderButton } from 'ds-ui'
RenderObject 是 DirectSurface UI 的核心模型。所有可见组件最终都是 RenderObject。
职责
一个 RenderObject 通常负责:
- 接收布局约束。
- 计算自己的尺寸。
- 布局子对象。
- 绘制自身。
- 参与命中测试。
- 接收指针、键盘、焦点等事件。
- 在销毁时释放资源。
Layout
布局阶段由父级传入约束:
performLayout(constraints, context): void
组件必须在布局阶段设置自己的 size。如果有子节点,也要给子节点执行 layout 并设置 offset。
Paint
绘制阶段通过 PaintContext 和 DrawList 输出绘制命令:
performPaint(context, offset): void
绘制应该只依赖当前布局结果和组件状态,不应该在 paint 中修改会触发布局的状态。
Hit Test
交互组件需要实现命中和事件处理:
onPointerDown(event): void
onPointerMove(event): void
onWheel(event): boolean
滚动容器或列表组件要在边界时正确处理事件传播,避免内部滚动到顶/底后异常影响外层容器。
可见性
所有 RenderObject 都有 visible 属性,默认值为 true。它是组件级的折叠隐藏能力:
visible = false时,组件尺寸为0 x 0。- 不执行自身
performLayout(),父布局组件也不会为该子节点保留间距或位置。 - 不绘制,不参与 transient paint。
- 不参与 hit test,也不会接收指针事件。
- 如果组件拥有 popup,隐藏时会关闭该组件拥有的 popup。
- 对可聚焦控件,隐藏时会退出焦点并从焦点系统注销。
业务权限、页面模式、流程状态只需要控制某个组件是否出现时,优先设置组件实例的 visible。例如:
const canAuditCurrentRecord = (): boolean => trueconst auditButton = new RenderButton({ label: '审核' })auditButton.visible = canAuditCurrentRecord()
visible 表示从布局流中移除。需要“保留占位但不绘制”时,使用布局包装组件 RenderVisibility({ mode: 'hidden' })。
状态变更
组件状态变化后调用:
markNeedsPaint()
markNeedsLayout()
只影响视觉的变化用 markNeedsPaint()。影响尺寸、子节点位置、可见项数量的变化用 markNeedsLayout()。
paintBounds 表示节点可能影响的全局绘制区域,默认等于 globalOffset + size。如果自定义组件绘制了超出布局矩形的阴影、光晕或装饰,应覆盖 paintBounds 并返回包含这些视觉溢出的保守边界。
仅改变节点位置、不改变布局尺寸时,应在修改 offset 前保存旧的 paintBounds,修改后调用:
const previousPaintBounds = this.paintBounds
this.offset = nextOffset
this.markNeedsPaintForGeometryChange(previousPaintBounds)
该方法会累计移动前后的绘制区域,同时保持 paint-only。不要用每帧 markNeedsLayout() 或全层重绘掩盖位置变化的损伤区域。
生命周期
组件持有事件、缓存、图片、定时器、popup、输入会话时,必须在 dispose() 中释放。
override dispose(): void {
// 释放本组件资源
super.dispose()
}
自定义 RenderObject 注意事项
- 不要在 layout 中调用外部业务回调。
- 不要在 paint 中做昂贵扫描。
- 优先使用
ds-ui已公开的组件和布局;只有公共能力无法覆盖时,才在业务项目中实现自定义 RenderObject。 - 自定义对象应明确所属应用模块、生命周期、测试范围和性能边界,不要依赖框架包内路径。