绘制与命中测试
绘制和命中测试共同决定用户看到什么,以及指针事件会落到哪个组件上。
绘制职责
绘制阶段只应该根据当前布局结果和组件状态输出绘制命令。它不应该改变会影响布局的数据。
绘制可以处理:
- 背景。
- 边框。
- 文本。
- 图标。
- hover、selected、focused 状态。
- 光标、选区和高亮。
- 滚动条。
绘制顺序
一般顺序是:
- 背景。
- 内容。
- 前景装饰。
- 焦点框、选中框等状态层。
复杂组件需要明确自己的层级,例如表格通常先绘制单元格背景,再绘制文字,最后绘制网格线和焦点框。
何时只需要重绘
只影响视觉、不影响尺寸时,应触发重绘:
markNeedsPaint()
典型场景:
- hover 状态变化。
- selected 状态变化。
- 焦点状态变化。
- 光标闪烁。
- 临时高亮。
- 滚动条拖动反馈。
绘制边界与几何损伤
局部重绘以 RenderObject.paintBounds 为损伤边界。默认边界是节点的布局矩形;绘制阴影、光晕或其他越界内容的组件必须覆盖该边界。
位置变化需要同时清理旧位置并绘制新位置。自定义 RenderObject 应在更新 offset 前保存旧 paintBounds,然后调用 markNeedsPaintForGeometryChange(previousPaintBounds)。同一帧内的多次位置变化会累计为一个并集区域,不会遗漏中间绘制位置。
命中测试
命中测试用于确定指针位置下有哪些组件。事件派发系统会根据命中结果把 pointer、wheel 等事件交给对应组件。
组件通常需要先判断点是否在自身矩形内,再决定是否继续检查子组件。
滚动边界
滚动容器在处理 wheel 时要明确返回是否消费事件。如果内部还能滚动,应消费事件;如果已经到达边界,可以根据组件设计决定是否让外层继续处理。
列表、树、表格这类内部滚动组件应保持一致的滚动体验。
性能原则
- paint 只绘制可视区域。
- hit test 不应递归不可见子树。
- 大数据组件应使用空间索引或可视范围,而不是每次全量扫描。
- 频繁动画和光标闪烁应只触发必要区域重绘。
- 几何移动应累计旧、新
paintBounds,不要只登记移动后的矩形。