[Solved]How to make Grid Layout group Cell Size X Auto expand

I am trying to make a list of UI elements. I want there to always be 2 columns and a potentially infinite number of rows. I want each cell to expand their x size constraint to completely fill the allocated space without any empty space on the sides. How can I achieve this?

This is what I want it to look like:
2901617--213633--What I want.PNG
This is what I get upon changing resolutions:
2901617--213634--What I get.PNG

3 Likes

I found a small hack that let me do what I want. I took the source code of the grid layout group and changed a few lines to fit my needs.

Found answer here:

Full script, just create a new script copy/paste the code and make sure it is named properly and id should work

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

        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]);
            }
        }
    }
}
20 Likes

GREAT works just as i wanted! thx you

Thank you so much! cellSize in read only… :frowning: Great job Unity…

2 Likes

thanks for the code.

added feature to choose between scaling on x, y, both, or none

add enum

  public enum ExpandSetting { x, y, both, none };
  public ExpandSetting expandSetting;

then replace

    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]);
            }
        }

with

 float realsizey;
                if (expandSetting == ExpandSetting.both)
                {
                    if (rectChildren.Count != 1)
                    {
                        realsizey = ((height - (spacing[0] * (actualCellCountY - 1))) / actualCellCountY);
                    }
                    else
                    {
                        realsizey = ((height / constraintCount - (spacing[0] * (actualCellCountY - 1))) / actualCellCountY);

                    }

                    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 + (realsizey + spacing[1]) * positionY, realsizey);

      
                }
                if (expandSetting == ExpandSetting.x)
                {
              

                    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]);

                }
                if (expandSetting == ExpandSetting.y)
                {
                    if (rectChildren.Count != 1)
                    {
                        realsizey = ((height - (spacing[0] * (actualCellCountY - 1))) / actualCellCountY);
                    }
                    else
                    {
                        realsizey = ((height / constraintCount - (spacing[0] * (actualCellCountY - 1))) / actualCellCountY);

                    }

                 
                    SetChildAlongAxis(rectChildren[i], 1, startOffset.y + (realsizey + spacing[1]) * positionY, realsizey);

                    SetChildAlongAxis(rectChildren[i], 0, startOffset.x + (cellSize[0] + spacing[0]) * positionX, cellSize[0]);
            

                }
                if (expandSetting == ExpandSetting.none)
                {
                    SetChildAlongAxis(rectChildren[i], 1, startOffset.y + (cellSize[1] + spacing[1]) * positionY, cellSize[1]);
                    SetChildAlongAxis(rectChildren[i], 0, startOffset.x + (cellSize[0] + spacing[0]) * positionX, cellSize[0]);
                }
2 Likes

Hi …i have done GridLayout using this AutoExpandGridLayoutGroup…Any idea on how to know which cell is clicked?So that I can perform certain action for that button click

You can either add a button script to the cell elements OR add a custom script that implements the IPointerClickHandler interface and get the click event.
The parent will have a collection (array/list) of the cell elements and add click listeners for all of them with a specific parameter to identify each cell.
I can write some sample code if you are still confused about this.

1 Like

I want to populate the Gridview with dynamic image buttons…which gets loaded from the data in the API.Sample code will be helpful,thanks…

This script has small bug for vertical expansion of items. So, replace each occurence of:

if (rectChildren.Count != 1)
{
    realsizey = ((height - (spacing[0] * (actualCellCountY - 1))) / actualCellCountY);
}
else
{
    realsizey = ((height / constraintCount - (spacing[0] * (actualCellCountY - 1))) / actualCellCountY);
}

with:

if (rectChildren.Count != 1)
{
    realsizey = ((height - (spacing[1] * (actualCellCountY - 1))) / actualCellCountY);
}
else
{
    realsizey = ((height / constraintCount - (spacing[1] * (actualCellCountY - 1))) / actualCellCountY);
}

Basically, old script involved spacing[0] (X-spacing) in the equation for vertical (Y-spacing), so this fix just reverts spacing[0] to spacing[1] in the cases where Y-expansion should occur

1 Like

Padding from the bottom d the right don’t work with this script, and padding from the top and the left pushes the cells outside the boundaries. How can I fix this?

Also, is there a way to maintain an aspect ratio with the cells? For example if you have a 2:1 window with 1:1 cells in it. Is there a way to expand the cells to fit the window while maintaining it’s shape?

1 Like

not to be a downer, but the script scales all my objects to 0 along the x axis. It seems to ignore its cellsize parameter, or does not apply its calculations correctly

Edit: To simply adjust width or height I created a script to attach to the gridlayoutgroup object.
It gets the needed vales, calculates either width or height for a cell and reapplies the result

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[ExecuteInEditMode]
[RequireComponent(typeof(UnityEngine.UI.GridLayoutGroup))]
public class AdjustGridLayoutCellSize : MonoBehaviour
{
    public enum ExpandSetting { X, Y,};

    public ExpandSetting expandingSetting;
    GridLayoutGroup gridlayout;
    int maxConstraintCount = 0;
    RectTransform layoutRect;
    private void Awake()
    {
        gridlayout = GetComponent<GridLayoutGroup>();
    }

    // Start is called before the first frame update
    void Start()
    {
        UpdateCellSize();
    }

    // Update is called once per frame
    void Update()
    {
       
    }

    private void OnValidate()
    {
        UpdateCellSize();
    }

    private void UpdateCellSize()
    {
        maxConstraintCount = gridlayout.constraintCount;
        layoutRect = gridlayout.gameObject.GetComponent<RectTransform>();

        if (expandingSetting == ExpandSetting.X)
        {
            float width = layoutRect.rect.width;
            float sizePerCell = width / maxConstraintCount;
            gridlayout.cellSize = new Vector2(sizePerCell, gridlayout.cellSize.y);
        }
        else if (expandingSetting == ExpandSetting.Y)
        {
            float height = layoutRect.rect.height;
            float sizePerCell = height / maxConstraintCount;
            gridlayout.cellSize = new Vector2(gridlayout.cellSize.x, sizePerCell);
        }
    }
}
1 Like

Thanks for the code, I added flexibility on both column and row count, it tries to make a square grid (2x2, 3x3, 4x4, etc.)

2 Likes

I’ve updated the code to include spacing between cells.

using UnityEngine;
using UnityEngine.UI;

[ExecuteInEditMode]
[RequireComponent(typeof(UnityEngine.UI.GridLayoutGroup))]
public class AdjustGridLayoutCellSize : MonoBehaviour
{
    public enum ExpandSetting { X, Y };

    public ExpandSetting expandingSetting;
    GridLayoutGroup _gridlayout;

    private GridLayoutGroup gridlayout
    {
        get
        {
            if(!_gridlayout)
                _gridlayout = GetComponent<GridLayoutGroup>();
            return _gridlayout;
        }       
    }

    int maxConstraintCount = 0;
    RectTransform layoutRect;
  
    // Start is called before the first frame update
    void Start()
    {       
        UpdateCellSize();
    }

   
    private void OnValidate()
    {
        UpdateCellSize();
    }

    private void UpdateCellSize()
    {
        maxConstraintCount = gridlayout.constraintCount;
        layoutRect = gridlayout.gameObject.GetComponent<RectTransform>();

        if (expandingSetting == ExpandSetting.X)
        {
            float spaceForSpacing = (maxConstraintCount - 1) * gridlayout.spacing.x;
            float width = layoutRect.rect.width - spaceForSpacing;
          
            float sizePerCell = width / maxConstraintCount ;
            gridlayout.cellSize = new Vector2(sizePerCell, gridlayout.cellSize.y);
        }
        else if (expandingSetting == ExpandSetting.Y)
        {
            float spaceForSpacing = (maxConstraintCount - 1) * gridlayout.spacing.y;
            float height = layoutRect.rect.height - spaceForSpacing;
            float sizePerCell = height / maxConstraintCount;
            gridlayout.cellSize = new Vector2(gridlayout.cellSize.x, sizePerCell);
        }
    }
}
2 Likes

i’ve added aspect ratio control to code, because when i added Aspect Ratio Control to an object inside Grid Layout Content Size Filter of that grid didn’t work as it should.

using UnityEngine;
using UnityEngine.UI;

[ExecuteInEditMode]
[RequireComponent(typeof(UnityEngine.UI.GridLayoutGroup))]
public class AdjustGridLayoutCellSize : MonoBehaviour
{
    public enum ExpandSetting { X, Y };

    public ExpandSetting expandingSetting;
    GridLayoutGroup _gridlayout;

    public bool aspectTurn;
    public float aspectRatio;

    private GridLayoutGroup gridlayout
    {
        get
        {
            if (!_gridlayout)
                _gridlayout = GetComponent<GridLayoutGroup>();
            return _gridlayout;
        }
    }

    int maxConstraintCount = 0;
    RectTransform layoutRect;

    // Start is called before the first frame update
    void Start()
    {
        UpdateCellSize();
    }


    private void OnValidate()
    {
        UpdateCellSize();
    }

    private void UpdateCellSize()
    {
        maxConstraintCount = gridlayout.constraintCount;
        layoutRect = gridlayout.gameObject.GetComponent<RectTransform>();

        if (expandingSetting == ExpandSetting.X)
        {
            float spaceForSpacing = (maxConstraintCount - 1) * gridlayout.spacing.x;
            float width = layoutRect.rect.width - spaceForSpacing;
            float sizePerCell = width / maxConstraintCount;

            float x = sizePerCell;
            float y = gridlayout.cellSize.y;
            if (aspectTurn)
                y = x * aspectRatio;

            //gridlayout.cellSize = new Vector2(sizePerCell, gridlayout.cellSize.y);
            gridlayout.cellSize = new Vector2(x, y);
        }
        else if (expandingSetting == ExpandSetting.Y)
        {
            float spaceForSpacing = (maxConstraintCount - 1) * gridlayout.spacing.y;
            float height = layoutRect.rect.height - spaceForSpacing;
            float sizePerCell = height / maxConstraintCount;

            float x = gridlayout.cellSize.x;
            float y = sizePerCell;
            if (aspectTurn)
                x = y * aspectRatio;

            //gridlayout.cellSize = new Vector2(gridlayout.cellSize.x, sizePerCell);
            gridlayout.cellSize = new Vector2(x, y);
        }
    }
}

i only looked at the code for a few minutes, but it seems like exchanging line 207 should do the trick :

float realsize = ((width - padding.left - padding.right - (spacing[0] * (actualCellCountX - 1))) / actualCellCountX);

You’ve save me some serious cursing while setting up this on my own! Thanks!

There’s no need to replace GridLayoutGroup for this, that’s adding a heap more code to maintain. The solution by Mathyx92 is a better approach IMO.

Here’s an updated version that adds the following:

  • Works with all GridLayoutGroup’s settings (padding, spacing, constraints)
  • Added optional ratio mode for cells (force square or 2:1 etc)
  • Updates at runtime when rect transform is resized
  • Updates in editor when values changed
  • General code cleanup
using UnityEngine;
using UnityEngine.UI;

[ExecuteInEditMode]
[RequireComponent(typeof(GridLayoutGroup))]
public class AdjustGridLayoutCellSize : MonoBehaviour
{
    public enum Axis { X, Y };
    public enum RatioMode { Free, Fixed };
   
    [SerializeField] Axis expand;
    [SerializeField] RatioMode ratioMode;
    [SerializeField] float cellRatio = 1;

    new RectTransform transform;
    GridLayoutGroup grid;

    void Awake()
    {
        transform = (RectTransform)base.transform;
        grid = GetComponent<GridLayoutGroup>();
    }

    // Start is called before the first frame update
    void Start()
    {
        UpdateCellSize();
    }

    void OnRectTransformDimensionsChange()
    {
        UpdateCellSize();
    }

#if UNITY_EDITOR
    [ExecuteAlways]
    void Update()
    {
        UpdateCellSize();
    }
#endif

    void OnValidate()
    {
        transform = (RectTransform)base.transform;
        grid = GetComponent<GridLayoutGroup>();
        UpdateCellSize();
    }

    void UpdateCellSize()
    {
        var count = grid.constraintCount;
        if (expand == Axis.X)
        {
            float spacing = (count - 1) * grid.spacing.x;
            float contentSize = transform.rect.width - grid.padding.left - grid.padding.right - spacing;
            float sizePerCell = contentSize / count;
            grid.cellSize = new Vector2(sizePerCell, ratioMode == RatioMode.Free ? grid.cellSize.y : sizePerCell * cellRatio);
           
        }
        else //if (expand == Axis.Y)
        {
            float spacing = (count - 1) * grid.spacing.y;
            float contentSize = transform.rect.height - grid.padding.top - grid.padding.bottom -spacing;
            float sizePerCell = contentSize / count;
            grid.cellSize = new Vector2(ratioMode == RatioMode.Free ? grid.cellSize.x : sizePerCell * cellRatio, sizePerCell);
        }
    }
}
11 Likes

Thank you!
I’m testing this solution with Unity 2019.4.5. It works great in Editor but once built (tried for Windows Standalone x64 and Android), it does’nt. Do you have it working in a built game?

My bad… I populate the GridLayoutGroup from script and the items were re-scaled by Unity. I had to force the scale from my script. The element are correctly adjusted.

Thanks you so much! Works great!