/// /// AutoExpandGridLayoutGroup a variation of Unity's Grid Layout Group. /// It sorts children under a panel into dynamic grid layout. /// Also resizes children inside panel according to panel size and child count. /// Helps taken from https://forum.unity.com/threads/solved-how-to-make-grid-layout-group-cell-size-x-auto-expand.448534/ /// using UnityEngine; using System.Collections.Generic; namespace UnityEngine.UI { [AddComponentMenu("Layout/Auto Expand Grid Layout Group", 152)] public class AutoExpandGridLayoutGroup : LayoutGroup { public enum Corner { UpperLeft = 0, UpperRight = 1, LowerLeft = 2, LowerRight = 3 } public enum Axis { Horizontal = 0, Vertical = 1 } public enum Constraint { Flexible = 0, FixedColumnCount = 1, FixedRowCount = 2 } [SerializeField] protected Corner m_StartCorner = Corner.UpperLeft; public Corner startCorner { get { return m_StartCorner; } set { SetProperty(ref m_StartCorner, value); } } [SerializeField] protected Axis m_StartAxis = Axis.Horizontal; public Axis startAxis { get { return m_StartAxis; } set { SetProperty(ref m_StartAxis, value); } } [SerializeField] protected Vector2 m_CellSize = new Vector2(100, 100); public Vector2 cellSize { get { return m_CellSize; } set { SetProperty(ref m_CellSize, value); } } [SerializeField] protected Vector2 m_Spacing = Vector2.zero; public Vector2 spacing { get { return m_Spacing; } set { SetProperty(ref m_Spacing, value); } } [SerializeField] protected Constraint m_Constraint = Constraint.Flexible; public Constraint constraint { get { return m_Constraint; } set { SetProperty(ref m_Constraint, value); } } [SerializeField] protected int m_ConstraintCount = 2; public int constraintCount { get { return m_ConstraintCount; } set { SetProperty(ref m_ConstraintCount, Mathf.Max(1, value)); } } protected AutoExpandGridLayoutGroup() { } #if UNITY_EDITOR protected override void OnValidate() { base.OnValidate(); constraintCount = constraintCount; } #endif protected override void OnRectTransformDimensionsChange(){ base.OnRectTransformDimensionsChange (); float meSize = getPieceSquareCalculated (rectTransform.rect.size.y, rectTransform.rect.size.x, rectChildren.Count); m_CellSize = new Vector2 (meSize,meSize); } protected virtual void OnTransformChildrenChanged(){ base.OnTransformChildrenChanged(); float meSize = getPieceSquareCalculated (rectTransform.rect.size.y, rectTransform.rect.size.x, rectChildren.Count); m_CellSize = new Vector2 (meSize,meSize); } public override void CalculateLayoutInputHorizontal() { base.CalculateLayoutInputHorizontal(); int minColumns = 0; int preferredColumns = 0; if (m_Constraint == Constraint.FixedColumnCount) { minColumns = preferredColumns = m_ConstraintCount; } else if (m_Constraint == Constraint.FixedRowCount) { minColumns = preferredColumns = Mathf.CeilToInt(rectChildren.Count / (float)m_ConstraintCount - 0.001f); } else { minColumns = 1; preferredColumns = Mathf.CeilToInt(Mathf.Sqrt(rectChildren.Count)); } SetLayoutInputForAxis( padding.horizontal + (cellSize.x + spacing.x) * minColumns - spacing.x, padding.horizontal + (cellSize.x + spacing.x) * preferredColumns - spacing.x, -1, 0); } public override void CalculateLayoutInputVertical() { int minRows = 0; if (m_Constraint == Constraint.FixedColumnCount) { minRows = Mathf.CeilToInt(rectChildren.Count / (float)m_ConstraintCount - 0.001f); } else if (m_Constraint == Constraint.FixedRowCount) { minRows = m_ConstraintCount; } else { float width = rectTransform.rect.size.x; int cellCountX = Mathf.Max(1, Mathf.FloorToInt((width - padding.horizontal + spacing.x + 0.001f) / (cellSize.x + spacing.x))); minRows = Mathf.CeilToInt(rectChildren.Count / (float)cellCountX); } float minSpace = padding.vertical + (cellSize.y + spacing.y) * minRows - spacing.y; SetLayoutInputForAxis(minSpace, minSpace, -1, 1); } public override void SetLayoutHorizontal() { SetCellsAlongAxis(0); } public override void SetLayoutVertical() { SetCellsAlongAxis(1); } private void SetCellsAlongAxis(int axis) { // Normally a Layout Controller should only set horizontal values when invoked for the horizontal axis // and only vertical values when invoked for the vertical axis. // However, in this case we set both the horizontal and vertical position when invoked for the vertical axis. // Since we only set the horizontal position and not the size, it shouldn't affect children's layout, // and thus shouldn't break the rule that all horizontal layout must be calculated before all vertical layout. if (axis == 0) { // Only set the sizes when invoked for horizontal axis, not the positions. for (int i = 0; i < rectChildren.Count; i++) { RectTransform rect = rectChildren[i]; m_Tracker.Add(this, rect, DrivenTransformProperties.Anchors | DrivenTransformProperties.AnchoredPosition | DrivenTransformProperties.SizeDelta); rect.anchorMin = Vector2.up; rect.anchorMax = Vector2.up; rect.sizeDelta = cellSize; } return; } float width = rectTransform.rect.size.x; float height = rectTransform.rect.size.y; int cellCountX = 1; int cellCountY = 1; if (m_Constraint == Constraint.FixedColumnCount) { cellCountX = m_ConstraintCount; cellCountY = Mathf.CeilToInt(rectChildren.Count / (float)cellCountX - 0.001f); } else if (m_Constraint == Constraint.FixedRowCount) { cellCountY = m_ConstraintCount; cellCountX = Mathf.CeilToInt(rectChildren.Count / (float)cellCountY - 0.001f); } else { if (cellSize.x + spacing.x <= 0) cellCountX = int.MaxValue; else cellCountX = Mathf.Max(1, Mathf.FloorToInt((width - padding.horizontal + spacing.x + 0.001f) / (cellSize.x + spacing.x))); if (cellSize.y + spacing.y <= 0) cellCountY = int.MaxValue; else cellCountY = Mathf.Max(1, Mathf.FloorToInt((height - padding.vertical + spacing.y + 0.001f) / (cellSize.y + spacing.y))); } int cornerX = (int)startCorner % 2; int cornerY = (int)startCorner / 2; int cellsPerMainAxis, actualCellCountX, actualCellCountY; if (startAxis == Axis.Horizontal) { cellsPerMainAxis = cellCountX; actualCellCountX = Mathf.Clamp(cellCountX, 1, rectChildren.Count); actualCellCountY = Mathf.Clamp(cellCountY, 1, Mathf.CeilToInt(rectChildren.Count / (float)cellsPerMainAxis)); } else { cellsPerMainAxis = cellCountY; actualCellCountY = Mathf.Clamp(cellCountY, 1, rectChildren.Count); actualCellCountX = Mathf.Clamp(cellCountX, 1, Mathf.CeilToInt(rectChildren.Count / (float)cellsPerMainAxis)); } Vector2 requiredSpace = new Vector2( actualCellCountX * cellSize.x + (actualCellCountX - 1) * spacing.x, actualCellCountY * cellSize.y + (actualCellCountY - 1) * spacing.y ); Vector2 startOffset = new Vector2( GetStartOffset(0, requiredSpace.x), GetStartOffset(1, requiredSpace.y) ); for (int i = 0; i < rectChildren.Count; i++) { int positionX; int positionY; if (startAxis == Axis.Horizontal) { positionX = i % cellsPerMainAxis; positionY = i / cellsPerMainAxis; } else { positionX = i / cellsPerMainAxis; positionY = i % cellsPerMainAxis; } if (cornerX == 1) positionX = actualCellCountX - 1 - positionX; if (cornerY == 1) positionY = actualCellCountY - 1 - positionY; float realsize = ((width - (spacing[0] * (actualCellCountX - 1))) / actualCellCountX); SetChildAlongAxis(rectChildren[i], 0, startOffset.x + (realsize + spacing[0]) * positionX, realsize); SetChildAlongAxis(rectChildren[i], 1, startOffset.y + (cellSize[1] + spacing[1]) * positionY, cellSize[1]); } } public static float getPieceSquareCalculated(float height, float width, int numberOfPieces) { float px = Mathf.Ceil(Mathf.Sqrt(numberOfPieces * width / height)) +1; px = px == 0 ? 1 : px; float sx, sy; if (Mathf.Floor(px * height / width) * px < numberOfPieces) sx = height / Mathf.Ceil(px * height / width); else sx = width / px; float py = Mathf.Ceil(Mathf.Sqrt(numberOfPieces * height / width))+1; py = py == 0 ? 1 : py; if (Mathf.Floor(py * width / height) * py < numberOfPieces) sy = width / Mathf.Ceil(width * py / height); else sy = height / py; return Mathf.Max(sx, sy); } } }