Date: 2026/5/18Tags: UGUIUnity
问题
在前段时间的BooomJam开发和在公司的项目中都遇到了一样的问题。TextMeshPro + ContentSizeFitter + VerticalLayoutGroup 是一套非常常见的组合,用来实现文本内容驱动布局的自适应大小。理想情况下,当你修改文本内容后,面板应该立刻根据新文本的尺寸重新计算布局。但事实上我我遇到了这样的一个问题——面板从隐藏状态切换为显示状态并写入新文案时,首次打开总会看到尺寸或宽度未能立刻适配,只有再次打开才正确的自适应
问题复现
我制作了一个配方描述面板,通过配置表为文本组件进行赋值,结构如下:
RecipeDescPanel (VerticalLayoutGroup + ContentSizeFitter)
└── Text (TextMeshProUGUI + ContentSizeFitter)
当调用 ShowRecipeDesc(string desc) 设置文案并激活面板时:
panel.SetActive(true);
text.text = desc;
我所期望面板宽度立刻根据文本长度自适应。但实际上,第一帧往往显示的是旧尺寸,直到下一次渲染才纠正过来。
原因
Unity 的 UI 布局系统是分帧计算的,依赖 Canvas 的重建管线。具体流程如下:
- TextMeshPro 网格生成:TMP 的文本网格生成并非完全同步。当文本内容改变时,TMP 会先标记为 dirty,在后续的网格重建阶段才会真正生成顶点数据。
- ContentSizeFitter 计算:
ContentSizeFitter依赖子元素的preferredWidth/preferredHeight。如果 TMP 的网格尚未重建,这些值可能是旧数据。 - LayoutGroup 重排:
VerticalLayoutGroup同样依赖子元素的尺寸信息,而子元素尺寸又依赖 TMP 的文本布局。 - Canvas 重建时机:所有这些刷新操作都在
Canvas.willRenderCanvases事件中统一处理,这意味着设置文本后至少要等到下一帧的渲染阶段,布局才算真正完成。
更复杂的是,当面板初始处于 inactive 状态时,其下的布局组件甚至不会参与 Canvas 的重建循环,激活后需要额外的帧来"启动"。
解决方案:手动刷新管线
要解决这个问题,不能被动等待 Unity 的自动重建,而是需要主动触发完整的布局刷新链路。以
设置文本后,立刻从底层到上层手动驱动重建:
text.text = desc;
// 1. 强制 TMP 立刻生成网格
text.ForceMeshUpdate();
// 2. 从内到外强制重建布局
LayoutRebuilder.ForceRebuildLayoutImmediate(text.rectTransform);
LayoutRebuilder.ForceRebuildLayoutImmediate(panelRectTransform);
// 3. 强制 Canvas 立即更新
Canvas.ForceUpdateCanvases();
这里的关键是调用顺序:
ForceMeshUpdate()确保 TMP 的文本网格和preferredWidth是最新的。ForceRebuildLayoutImmediate从内向外(Text → Panel)递归计算布局。Canvas.ForceUpdateCanvases()将整个 Canvas 的重建提前到当前帧。