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 滑动布局显示不全的现象。
三、问题分析
案例中:
这里时使用 ScrollView 进行内容过多的时候,可以进行自动滑动浏览内容,其中使用到 LayoutGroup 进行排布,ContentSizeFitter 进行画布自动适配调整大小。
值得注意的是:我这里的嵌套的使用了 LayoutGroup ,和 ContentSizeFitter ,就是Item 中的子物件中还有自动排布和自动适配大小的 LayoutGroup ,和 ContentSizeFitter 组件使用。
由于嵌套,在动态生成中,子物体中也嵌套 LayoutGroup ,和 ContentSizeFitter ,父物体中的 LayoutGroup ,和 ContentSizeFitter 进行计算的时候,就会出现计算错误的现象,布局不对,且Scroll 滑动布局显示不全的现象。
四、问题解决思路
1、Canvas 强制刷新
Canvas.ForceUpdateCanvases();
2、对于unity的gridLayout verticalLayout 或者 horizontalLayout 经常有加入新成员或者改变成员大小后,部件大小、位置不对的问题,一般来说 ,这个方法就能解决
LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform);
3、用开关 gameobject 等一帧之类的方法解决,然后处理
horizLayoutGroup.CalculateLayoutInputHorizontal();
horizLayoutGroup.CalculateLayoutInputVertical();
horizLayoutGroup.SetLayoutHorizontal();
horizLayoutGroup.SetLayoutVertical();
4、还有就是进行 ContentSizeFitter 组件可以根据其子对象的大小来自动调整父对象的大小。但是,有时在动态添加内容后,ContentSizeFitter 可能无法立即更新,可以进下面
// 手动强制更新 ContentSizeFitter
ContentSizeFitter contentSizeFitter = yourContent.GetComponent<ContentSizeFitter>();
if (contentSizeFitter != null)
{
contentSizeFitter.SetLayoutHorizontal();
contentSizeFitter.SetLayoutVertical();
}
5、延迟更新: 如果进行一次更新无法达到刷新的效果,在向ScrollRect添加内容后,有时需要在下一帧中更新滚动视图和滚动条的范围。这可以通过将更新放置在 LateUpdate 中实现。
- 如果这里比较耗性能,
- 可以试着在有数据UI变化的时候触发更新若干时间关闭更新
- 根据需要合理调整即可
void LateUpdate()
{
// 在 LateUpdate 中更新内容和滚动条范围
// 更新 Content 大小
// 更新滚动条范围
}
五、案例解决实现步骤
1、创建 Unity 工程,进行 UI 布局如下,主要是 Scroll View ,然后再 Content 添加 LayoutGroup ,和 ContentSizeFitter 组件
2、要进行实例化的预制体说明,ImageGroup,和 ImageItem
3、新建脚本,进行ScrollView 的Content 动态生成,开始预生成部分,然后按下空格键 Space ,进行随机生成
4、把 Test 挂载到场景中,赋值父物体,和对应预制体
5、运行,按下空格键Space 随机生成 Item 有时候就会出现不能及时适配更新的情况
6、进行对应的处理后,这里是在 LateUpdate 进行更新
- 如果这里比较耗性能,
- 可以试着在有数据UI变化的时候触发更新若干时间关闭更新
- 根据需要合理调整即可
7、运行场景,不能及时更新布局问题基本解决
六、案例关键代码
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
}