Unity 问题 之 ScrollView ,LayoutGroup,ContentSizeFitter 一起使用时,动态变化时无法及时刷新更新适配界面的问题
目录
[Unity 问题 之 ScrollView ,LayoutGroup,ContentSizeFitter 一起使用时,动态变化时无法及时刷新更新适配界面的问题](#Unity 问题 之 ScrollView ,LayoutGroup,ContentSizeFitter 一起使用时,动态变化时无法及时刷新更新适配界面的问题)
一、简单介绍
Unity 在开发中,记录一些报错问题,以便后期遇到同样问题处理。
有时我们在开发中,一起使用 ScrollView ,LayoutGroup,ContentSizeFitter,对于简单的排布,一般会很好的自动更新适配,但是有时候复杂了时候未必很好的自动的调整,进行很好适配。
ScrollView 在可滚动区域中显示内容。当你向ScrollView添加内容时,内容会被添加到ScrollView的内容容器(#unity-content-container)中。
Layout Group 翻译为"布局组",从字面意思就可以理解,可以对一组元素进行动态布局,这里说的动态是,组内元素数量发生变化时,Layout Group可以智能的帮助你重新排版。
ContentSizeFitter 作为一个布局控制器,控制它自己的布局元素的大小。大小由游戏对象上的布局元素组件提供的最小或首选尺寸决定。这样的布局元素可以是图像或文本组件、布局组或布局元素组件。
值得注意的是,当一个矩形变换被缩放时------无论是通过 ContentSizeFitter 还是其他东西------缩放的范围是围绕基准值进行的。这意味着可以使用基准值来控制缩放的方向。
例如,当枢轴位于中心时,ContentSizeFitter 将在所有方向上均匀地扩展矩形变换。当枢轴位于左上角时,内容大小调整器会将矩形变换向下和向右展开。
二、问题现象
现象中,刚开始与生成布局显示是没有问题,当动态添加一些 Item 的时候,会出现 布局显示都不对,会有重叠,或者 Scroll 滑动布局显示不全的现象。
![](https://file.jishuzhan.net/article/1738566496503730178/d7b6df6b37699bd1784c82b887ea5e9b.webp)
三、问题分析
案例中:
这里时使用 ScrollView 进行内容过多的时候,可以进行自动滑动浏览内容,其中使用到 LayoutGroup 进行排布,ContentSizeFitter 进行画布自动适配调整大小。
值得注意的是:我这里的嵌套的使用了 LayoutGroup ,和 ContentSizeFitter ,就是Item 中的子物件中还有自动排布和自动适配大小的 LayoutGroup ,和 ContentSizeFitter 组件使用。
由于嵌套,在动态生成中,子物体中也嵌套 LayoutGroup ,和 ContentSizeFitter ,父物体中的 LayoutGroup ,和 ContentSizeFitter 进行计算的时候,就会出现计算错误的现象,布局不对,且Scroll 滑动布局显示不全的现象。
四、问题解决思路
1、Canvas 强制刷新
cs
Canvas.ForceUpdateCanvases();
2、对于unity的gridLayout verticalLayout 或者 horizontalLayout 经常有加入新成员或者改变成员大小后,部件大小、位置不对的问题,一般来说 ,这个方法就能解决
cs
LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform);
3、用开关 gameobject 等一帧之类的方法解决,然后处理
cs
horizLayoutGroup.CalculateLayoutInputHorizontal();
horizLayoutGroup.CalculateLayoutInputVertical();
horizLayoutGroup.SetLayoutHorizontal();
horizLayoutGroup.SetLayoutVertical();
4、还有就是进行 ContentSizeFitter 组件可以根据其子对象的大小来自动调整父对象的大小。但是,有时在动态添加内容后,ContentSizeFitter 可能无法立即更新,可以进下面
cs
// 手动强制更新 ContentSizeFitter
ContentSizeFitter contentSizeFitter = yourContent.GetComponent<ContentSizeFitter>();
if (contentSizeFitter != null)
{
contentSizeFitter.SetLayoutHorizontal();
contentSizeFitter.SetLayoutVertical();
}
5、延迟更新: 如果进行一次更新无法达到刷新的效果,在向ScrollRect添加内容后,有时需要在下一帧中更新滚动视图和滚动条的范围。这可以通过将更新放置在 LateUpdate 中实现。
- 如果这里比较耗性能,
- 可以试着在有数据UI变化的时候触发更新若干时间关闭更新
- 根据需要合理调整即可
cs
void LateUpdate()
{
// 在 LateUpdate 中更新内容和滚动条范围
// 更新 Content 大小
// 更新滚动条范围
}
五、案例解决实现步骤
1、创建 Unity 工程,进行 UI 布局如下,主要是 Scroll View ,然后再 Content 添加 LayoutGroup ,和 ContentSizeFitter 组件
![](https://file.jishuzhan.net/article/1738566496503730178/c253dce37600ff9b7655e67c8452e27e.webp)
![](https://file.jishuzhan.net/article/1738566496503730178/7c939c7e944f16db978229315086c5a2.webp)
2、要进行实例化的预制体说明,ImageGroup,和 ImageItem
![](https://file.jishuzhan.net/article/1738566496503730178/b2b8882bde210e15bafa2b1e351274ef.webp)
![](https://file.jishuzhan.net/article/1738566496503730178/dedeb0af2507e22e7261bea59c1d5281.webp)
3、新建脚本,进行ScrollView 的Content 动态生成,开始预生成部分,然后按下空格键 Space ,进行随机生成
![](https://file.jishuzhan.net/article/1738566496503730178/3d65428a866527e0d0d35bf3a379597e.webp)
4、把 Test 挂载到场景中,赋值父物体,和对应预制体
![](https://file.jishuzhan.net/article/1738566496503730178/5ec257a8432a5ab8375e89bc0c20dc93.webp)
5、运行,按下空格键Space 随机生成 Item 有时候就会出现不能及时适配更新的情况
![](https://file.jishuzhan.net/article/1738566496503730178/569c31aea9ebcb2d0f32667d58f7e0bb.webp)
6、进行对应的处理后,这里是在 LateUpdate 进行更新
- 如果这里比较耗性能,
- 可以试着在有数据UI变化的时候触发更新若干时间关闭更新
- 根据需要合理调整即可
![](https://file.jishuzhan.net/article/1738566496503730178/5395d2a92b0d1b0c510a90c515d387e8.webp)
![](https://file.jishuzhan.net/article/1738566496503730178/24198679bd3fb976103efa19c6589491.webp)
7、运行场景,不能及时更新布局问题基本解决
![](https://file.jishuzhan.net/article/1738566496503730178/a02a852dfb6f4607921d6d50be7afc45.webp)
六、案例关键代码
cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// ScrollView ,LayoutGroup,ContentSizeFitter 一起使用时,动态变化时无法及时刷新更新适配界面的问题
/// </summary>
public class Test : MonoBehaviour
{
#region Data
/// <summary>
/// ImageGroup 预制体
/// </summary>
public GameObject ImageGroup;
/// <summary>
/// ImageItem 预制体
/// </summary>
public GameObject ImageItem;
/// <summary>
/// ParentTran 父物体 ImageGroup
/// </summary>
public Transform ParentTran;
/// <summary>
/// ImageGroup 列表
/// </summary>
List<GameObject> m_ImageGroupGoLst;
#endregion
#region Lifecycle function
/// <summary>
/// Start is called before the first frame update
/// </summary>
void Start()
{
Init();
}
/// <summary>
/// Update is called once per frame
/// </summary>
void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) {
GameObject go = GetRandomImageGroup();
CreateImageItemGameObject(go.transform);
}
}
/// <summary>
/// LateUpdate is called once per frame
/// </summary>
private void LateUpdate()
{
// 如果这里比较耗性能,
// 可以试着在有数据UI变化的时候触发更新若干时间关闭更新
// 根据需要合理调整即可
RefreshUICanvas();
}
#endregion
#region Private Function
/// <summary>
/// 初始化生成部分队列
/// </summary>
void Init() {
m_ImageGroupGoLst = new List<GameObject>();
for (int i = 0; i < 2; i++)
{
GameObject go = CreateImageGroupGameObject();
if (i == 1) {
for (int ii = 0; ii < 4; ii++)
{
CreateImageItemGameObject(go.transform);
}
}
m_ImageGroupGoLst.Add(go);
}
}
/// <summary>
/// 生成 ImageGroup 实体
/// </summary>
/// <returns></returns>
GameObject CreateImageGroupGameObject() {
return GameObject.Instantiate(ImageGroup, ParentTran,false);
}
/// <summary>
/// 生成 ImageItem 实体
/// </summary>
/// <returns></returns>
GameObject CreateImageItemGameObject(Transform parent)
{
return GameObject.Instantiate(ImageItem, parent, false);
}
/// <summary>
/// 随机获取 生成的 ImageGroup 实体
/// </summary>
/// <returns></returns>
GameObject GetRandomImageGroup() {
int index = Random.Range(0, m_ImageGroupGoLst.Count);
GameObject go = m_ImageGroupGoLst[index];
return go;
}
/// <summary>
/// 刷新 UI 界面
/// </summary>
void RefreshUICanvas()
{
// 强制刷新
Canvas.ForceUpdateCanvases();
// Layout 重建
LayoutRebuilder.ForceRebuildLayoutImmediate(ParentTran.GetComponent<RectTransform>());
// contentSizeFitter 设置
ContentSizeFitter contentSizeFitter = ParentTran.GetComponent<ContentSizeFitter>();
if (contentSizeFitter != null)
{
contentSizeFitter.SetLayoutHorizontal();
contentSizeFitter.SetLayoutVertical();
}
}
#endregion
}