本页面介绍管理视觉树中的元素的最佳实践。
元素池化是为了保留稍后可能重新创建的元素,而不是每次都创建具有 new() 的元素并放弃它们。
您需要完全控制池中的所有元素,并确保在将它们返回到池之前正确对其进行重置。否则,池化系统可能会变得不稳定并带来麻烦。例如,如果同时注册事件回调并设置内部非序列化状态时对元素进行池化,则无法清理元素。
要保持较少的视觉元素数量,请尽可能使用 ListView。ListView 会池化元素并在用户滚动时回收元素。
或者,您也可以实现自己的池和回收机制,类似于 ListView,并使用以下方法来管理可见区域:
GeometryChangedEvent
VisualElement.layout 属性
使用 VisualElement.RemoveFromHierarchy() 从层级视图中删除元素并消除对它的引用时,该元素就会被垃圾回收。这样可以将 CPU 和 GPU 成本降低到零,并释放大量内存。但是,重新创建元素并在层级视图中重新加载元素是一项缓慢且成本高昂的操作。为了避免这种情况,可以在层级视图中预先创建元素,使用 USS 样式属性隐藏它们,并仅在必要时显示它们。虽然应用样式通常更快,但如果同时创建大量元素,可能会导致内存使用量增加。
以下将介绍隐藏元素的不同方法以及对处理器和内存使用的影响。
visibility: hidden; 隐藏使用此方法时,后代可以覆盖 visibility 样式。
下表描述了使用 visibility 样式隐藏或显示视觉元素时,单帧成本在不同方面的情况:
| 方面 | visibility: hidden; |
visibility: visible; |
|---|---|---|
| 样式 | 针对元素及其后代进行评估以传播可见性。 | 为其本身和本身后代计算,以传播可见性。 |
| 布局数据 | 预留 | 无 |
| 渲染命令 | 删除并释放 | 重新创建并重新插入到命令链中。 |
| 网格 | 计划释放。 | 重新曲面细分 |
下表描述了使用 visibility 样式隐藏视觉元素时 CPU 和 GPU 的每帧行为:
| 处理器 | 方面| 每帧行为 | | — | — | — | | CPU | 样式 | 对元素及其后代进行完整评估。 | | |布局数据 | 更新| | |曲面细分 | 仅涉及模板遮罩网格的最小影响(如果适用)。 | | |渲染命令 | 没有绘制常规可见几何体的命令。然而,模板遮罩网格仍然会被渲染,以便从模板中推送或弹出,确保潜在的可见子后代被遮罩。 | | GPU |网格 | 模板遮罩网格的顶点和片元着色。|
opacity: 0; 隐藏使用此方法时,如果内容位于视口 (Viewport) 中,GPU 使用率可能会很高,因为片元着色器会处理所有元素,可能导致严重的过度绘制。
下表描述了使用 opacity 样式隐藏或显示视觉元素时的单帧成本:
| 操作 | 单帧成本 |
|---|---|
opacity: 0; |
首次将 opacity 设置为 1 以外的值时,UI 工具包渲染器会修改顶点以加快 GPU 上的不透明度应用。这会触发一次性的最小 CPU 成本。虽然通常可以忽略不计,但如果元素具有大量后代或有许多顶点需要修改,此成本可能变得显著。除非从视觉树中删除元素并重新添加,否则不会再产生此成本。 |
opacity: 1; |
无 |
下表描述了使用 opacity 样式隐藏视觉元素时 CPU 和 GPU 的每帧行为:
| 处理器 | 方面| 每帧行为 |
| — | — | — |
| CPU |样式 | 对元素及其后代进行完整评估。 |
|
|曲面细分 | 正常运行并响应更改。 |
|
|渲染命令 | 执行 |
| GPU | 网格 | 顶点着色器的运行方式如同 visibility 被设置为 1。同样,片元着色器的功能也类似于 visibility 为 1。这在 GPU 受限型项目中可能有害,因为它可能导致过度绘制。 |
display: none; 隐藏使用此方法时,元素的行为类似于从布局树中删除元素,这可能会影响其他元素的布局。
下表描述了使用 display 样式隐藏或显示视觉元素时,单帧成本在不同方面的情况:
| 方面 | display: none; |
display: flex; |
|---|---|---|
| 布局数据 | 可能会重新计算其他元素的布局。 | 待处理的布局更改将被处理。 |
| 渲染命令/网格 | 为受布局更改影响的元素重新生成。 |
|
下表描述了使用 display 样式隐藏视觉元素时的 CPU 每帧行为。请注意,没有 GPU 成本。
| 方面 | 每帧行为 |
|---|---|
| 布局数据 | 预留,但可能会变为无效状态并且不会更新。 |
| 渲染命令 | 虽然被保留,但在执行期间可以跳过它们。跳过它们的方式非常便宜,但并非完全免费。成本与命令数量成正比。 |
| 网格 | 保留,但可能会变为无效状态并且不会更新。 |
您可以将 translate: -5000px -5000px; 与 DynamicTransform 使用提示相结合,将元素移出视口。几何体保持完全活跃状态,因此当您让元素重新显示在屏幕上时,CPU 使用率降至最低。但是,GPU 会继续处理顶点,根据具体情况,这或许是可以接受的。
计算变换并上传到 GPU 内存中,通常速度很快。
下表描述了通过在视口外部平移视觉元素来隐藏它时 CPU 和 GPU 的每帧行为:
| 处理器 | 方面| 每帧行为 | | — | — | — | | CPU|样式 | 更新 | | |布局日期 | 更新 | | |绘制调用 | 执行 | | GPU |网格 | 对顶点进行着色。 |
使用 VisualElement.RemoveFromHierarchy() 方法从层级视图中移除元素时,可以释放 CPU 和 GPU 内存,从而消除任何计算成本。
下表描述了通过从层级视图中删除视觉元素来隐藏或显示视觉元素时的单帧成本:
| 方面 | 删除 | 添加 |
|---|---|---|
| 样式 | 无 | 针对子树进行了更新。 |
| 布局 | 无 | 针对子树以及可能的其他元素重新计算。 |
| 渲染命令/网格 | 为受布局更改影响的元素重新生成。 |
|
下表总结了使用不同方法隐藏元素后的内存使用情况:
| 处理器 |方面| visibility:hidden; | opacity:0; | display:None; | 平移出视口 | 从层级视图中删除 |
| — | — | — | — | — | — | — |
| CPU |样式 |保留 | 保留 | 保留 | 保留 | 释放 |
|
|布局 | 保留 | 保留 | 保留 | 保留 | 保留[1] |
|
|渲染命令/网格 | 释放 | 保留 | 保留 | 保留 | 释放 |
| GPU |网格 | 释放 | 保留 | 保留 | 保留 | 释放 |
[2]:布局内存会保留下来,因为它仍然为元素预留。当 VisualElement 被垃圾回收时,布局内存会返回到池中,供其他元素使用。