Сегодня снова про UI. Ничто не отнимает у меня столько времени, как борьба с UI.
Задача: сделать, чтобы в
GridLayoutGroup ячейки были покрупнее, если элементов мало, и помельче, если их много.В Unity есть коллбек
OnTransformChildrenChanged, который вызывается, когда добавляется или удаляется новый дочерний элемент. Но этот коллбек не вызывается, когда дочерний элемент становится активен или неактивен.Можно постоянно высчитывать количество активных дочерних элементовв
Update, но это на самый крайний случай. Лучше не делать никакую логику в Update.Можно повесить на дочерние элементы скрипт, который будет уведомлять родителя о своем включении/выключении, но это неудобно.
Предлагаю решение на основе методов
SetLayoutHorizontal/SetLayoutVertical из интерфейса ILayoutController. Юнити вызывает их, когда перестраивает layout, то есть как раз, когда нам нужно.После изменения нужных нам параметров, нужно попросить Unity заново построить layout (
LayoutRebuilder.ForceRebuildLayoutImmediate), желательно в том же самом кадре, чтобы не было никаких миганий. Но при этом не уйти в рекурсию.Полный код решения:
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[Serializable]
public class CellSizeRule
{
[field: SerializeField] public int MinCount { get; private set; }
[field: SerializeField] public Vector2 CellSize { get; private set; }
}
[RequireComponent(typeof(GridLayoutGroup))]
[ExecuteAlways]
public class DynamicCellSizer : UIBehaviour, ILayoutController
{
[SerializeField] private CellSizeRule[] rules;
private GridLayoutGroup grid;
private int count = -1;
private GridLayoutGroup Grid
{
get
{
if (!grid)
grid = GetComponent<GridLayoutGroup>();
return grid;
}
}
protected override void OnEnable()
{
base.OnEnable();
ApplyRule();
}
private void ApplyRuleIfNeeded()
{
var count = transform.Cast<Transform>()
.Where(t => t.gameObject.activeSelf)
.Select(t => t.GetComponent<LayoutElement>())
.Count(le => le == null || !le.ignoreLayout);
if (count == this.count)
return;
this.count = count;
ApplyRule();
}
private void ApplyRule()
{
foreach (var rule in rules.OrderByDescending(r => r.MinCount))
{
if (count >= rule.MinCount)
{
SetSize(rule);
return;
}
}
if (rules.Length > 0)
SetSize(rules[^1]);
}
private void SetSize(CellSizeRule rule)
{
Grid.cellSize = rule.CellSize;
LayoutRebuilder.ForceRebuildLayoutImmediate(Grid.transform.parent as RectTransform);
}
public void SetLayoutHorizontal() => ApplyRuleIfNeeded();
public void SetLayoutVertical() => ApplyRuleIfNeeded();
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
ApplyRule();
}
#endif
}
#ui