844просмотров
29 сентября 2025 г.
questionScore: 928
Как перестроить UI при включении/выключении дочернего элемента? Внедряемся в Layout. Сегодня снова про 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