关于ContentSizeFitter组件的警告带来的问题

UI布局

经常需要的UI布局

我们经常会有这样的UI布局,一个带有布局组件(LayoutGroup)的父物体下,有一个或多个子物体带有自适应组件(ContentSizeFitter),如图:

parent child_text

ContentSizeFitter组件出现的警告

大家有没有发现子物体的ContentSizeFitter组件的上的警告,如下图。没错,问题就出现在这里。大致的意思是:父物体上有一个布局组件。布局组件的子物体不应该有ContentSizeFitter组件,因为它应该由布局组件驱动。

warning

警告引发的问题

刚开始遇到这个警告的时候,我尝试着删除掉子物体的ContentSizeFitter组件,虽然警告不见了,但是子物体就没办法达到自适应的效果,这当然不是我们想要的,所以只能暂时的忽略掉这个警告。最后终于发现了这个警告引发的问题,那就是:当我们需要父物体布局后的长和宽时,此时取到的长和宽竟然不是布局最终的结果,当然我们都知道布局组件会在下一帧计算,可以使用LayoutRebuilder.ForceRebuildLayoutImmediate()这个方法,但是即使使用了这个方法,取到的结果依然不是布局最终的结果。以下是我的测试用例:

  • UI结构,toggle选中时为黑色,未选中时为白色,toggle的作用是在选中时将带有ContentSizeFitter组件的child_text的文本设置为一行,未选中时设置为多行。如下图:

test_ui

  • 在设置完child_text的文本后,立即强制布局计算,布局计算结束后打印parent的长和宽,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using UnityEngine;
using UnityEngine.UI;

public class test : MonoBehaviour {

public Text child_text;
public RectTransform parent;

public void OnValueChanged(bool val) {
if (val == true)
{
child_text.text = "一行的测试文本";
}
else {
child_text.text = "多行的测试文本测试文本测试文本测试文本测试文本测试文本";
}

LayoutRebuilder.ForceRebuildLayoutImmediate(parent);

Vector2 size = parent.sizeDelta;
print("parent width:"+size.x+", height:"+size.y);
}
}
  • 以下分别是toggle选中和未选中的测试结果

selected_test_result
noselected_test_result

分析产生问题的具体原因

由于子物体带有自适应组件(ContentSizeFitter),所以子物体也会进行布局计算,然而并不是子物体布局计算后父物体开始进行布局计算,两者很有可能是同时进行布局计算,这就导致在父物体进行布局计算时,子物体的长和宽并不是最终的计算结果,这样父物体的布局计算就会有误差。

解决方案

问题的原因是父物体的布局计算并不是在子物体布局计算之后,那么,我们可以强制的把父物体的布局计算和子物体的布局计算按照顺序进行,即先强制的对子物体进行布局计算,然后强制对父物体进行布局计算。强制对子物体进行布局计算可以使用ContentSizeFitter组件的SetLayoutVertical方法。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEngine;
using UnityEngine.UI;

public class test : MonoBehaviour {

public Text child_text;
public RectTransform parent;

public void OnValueChanged(bool val) {
if (val == true)
{
child_text.text = "一行的测试文本";
}
else {
child_text.text = "多行的测试文本测试文本测试文本测试文本测试文本测试文本";
}

//先强制进行子物体的布局计算
child_text.gameObject.GetComponent<ContentSizeFitter>().SetLayoutVertical();
//然后强制进行父物体的布局计算
LayoutRebuilder.ForceRebuildLayoutImmediate(parent);

Vector2 size = parent.sizeDelta;
print("parent width:"+size.x+", height:"+size.y);
}
}

经过测试,结果和我们想象中的一致。

其他问题

一些复杂的UI可能是布局组件的多重嵌套,这样就必须一层一层的按顺序强制计算每一个带有ContentSizeFitter组件的子物体,乍一想,似乎有点不靠谱的感觉。至于这个问题暂时还没有想到一个好的解决方案。