Flexible "grid" layout?

I want a layout controller capable of expanding its height based on its children. Basically like a text area works, but instead of words there would be child elements.

So when adding elements (of variable width) to this layout controller, it would first try to fill its width before going to a new row.

Is there currently a way to set this up using the pre-existing components in Unity UI? The horizontal and vertical layout groups don’t handle rows or columns, respectively. The grid layout seems to require a fixed size of each cell, which is undesirable.

Obviously a combination of vertical and horizontal groups can be used, but it would be far neater if there’s a way to set up a grid layout capable of having children of flexible width. Otherwise a tree of vertical and horizontal groups would have to be rebuilt when children at the top are removed.

2 Likes

Yes, you simply need to use a Content Size fitter which will scale a parent GO around it’s children.

If you also use a Layout group, make sure you un check the “Child Force Expand” option for the direction you want it to shrink

Hope this helps

5 Likes

Either I’m misunderstanding you, or you me. How would creating a content size fitter automatically create rows, or columns, for a layout group? As far as I know, grid layout is the only layout creating both columns and rows, but that requires a fixed cell size.

I think you’re (maybe) asking the wrong question; reading between the lines, you want a packed layout equivalent of the grid?

Content size fitter just does what it says…it doesn’t care much how the contents are created or by what.

Yeah, I suppose it can be called a packed grid layout. Basically this is what I’m trying to do:

1 Like

Yeah easy enough (and easy to say!), but you’ll have to write this yourself. The UI is open sourced and the UI components are all standard C# components, so a good starting point would be to extend or do a custom component based on GridLayoutGroup.

https://bitbucket.org/Unity-Technologies/ui
UI / UnityEngine.UI / UI / Core / Layout / GridLayoutGroup.cs

1 Like

Right, didn’t get that distinction in your query.

No the current Grid layout won’t dynamically change it’s row value. (just answered that in another post, second today, v.odd)
Will see if the community comes up with anything or I’ll add one myself to the UI Extensions repo https://bitbucket.org/ddreaper/unity-ui-extensions

3 Likes

The best thing to do here is make a grid layout group as the parent then within that group make horizontal layout groups of elements as shown below

-Grid Layout Group
-Horizontal Layout Group
-Element
-Element
-Element
-Element
-Horizontal Layout Group
-Element
-Element
-Element
-Element

You would need to do the math for when you want want to create new horizontal layout groups within the grid layout group

Hope this helped

4 Likes

So, it wont resize dynamically. What I did was on Start() get the width of the scrolling area (the parent) and then divide it by the number of columns I wanted then changed the fixed cell size in the Grid Layout Group.

Here is my code:

    public float width;

    // Use this for initialization
    void Start ()
    {
        width = this.gameObject.GetComponent<RectTransform>().rect.width;
        Vector2 newSize = new Vector2(width / 2, width / 2);
        this.gameObject.GetComponent<GridLayoutGroup>().cellSize = newSize;
    }

You can also do the same for height. Or put the code in Update() if you want it to resize constantly.

Hope this helps!

5 Likes

This code is so simple and works perfect!! You saved my time a lot. Thank you!!!

ahh, are you looking to have it work just like kissUI’s Flowing Layouts?

ex:

5 Likes

2922427--215904--Layout.gif

2922427–215901–S_2.unitypackage (13.1 KB)

1 Like

Maybe this is also helpful for the topic? It is called a FlowLayout in this case.

1 Like

Just want to add, if someone has a problem like me, which is trying to maximize the horizontal count first in grid layout group before going down

void Start()
    {
        float width;
        GridLayoutGroup lg;
        lg = GetComponent<GridLayoutGroup>();
        width = gameObject.GetComponent<RectTransform>().rect.width;
        float sizeButt = lg.cellSize.x + lg.spacing.x;
        int hSize = Mathf.FloorToInt(width / sizeButt);
        lg.constraintCount = hSize;
    }
2 Likes

It’s not properly tested, but I use following script.
The logic is simple. prior layout calculation just calculate cell size and set it

/// <summary>
/// In this class we calculate board cell width height prior layout calculation.
/// </summary>
public class FlexibleGridLayout : GridLayoutGroup
{
   public int ColumnCount = 12;
   public int RowCount = 20;


   public override void SetLayoutHorizontal()
   {
      UpdateCellSize();
      base.SetLayoutHorizontal();
   }

   public override void SetLayoutVertical()
   {
      UpdateCellSize();
      base.SetLayoutVertical();
   }

   private void UpdateCellSize()
   {        
      float x = (rectTransform.rect.size.x - padding.horizontal - spacing.x*(ColumnCount - 1)) / ColumnCount;
      float y = (rectTransform.rect.size.y - padding.vertical - spacing.y * (RowCount - 1)) / RowCount;
      this.constraint = Constraint.FixedColumnCount;
      this.constraintCount = ColumnCount;
      this.cellSize = new Vector2(x,y);     
   }
}

And if you want to hide unneded properties and reveal Column, Row count just create Editor script as foloow

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;


namespace Tetris.UI.Editor
{

   [CustomEditor(typeof(TetrisGridLayoutGroup), true)]
   [CanEditMultipleObjects]
   public class FlexibleLayoutGroupEditor : UnityEditor.Editor {
   
      SerializedProperty m_Padding;     
      SerializedProperty m_Spacing;
      SerializedProperty m_StartCorner;
      SerializedProperty m_StartAxis;
      SerializedProperty m_ChildAlignment;
      SerializedProperty m_ColumnCount;
      SerializedProperty m_RowCount;

      protected virtual void OnEnable()
      {
         m_Padding = serializedObject.FindProperty("m_Padding");
         m_Spacing = serializedObject.FindProperty("m_Spacing");
         m_StartCorner = serializedObject.FindProperty("m_StartCorner");
         m_StartAxis = serializedObject.FindProperty("m_StartAxis");
         m_ChildAlignment = serializedObject.FindProperty("m_ChildAlignment");
         m_ColumnCount = serializedObject.FindProperty("ColumnCount");
         m_RowCount = serializedObject.FindProperty("RowCount");
      }

      public override void OnInspectorGUI()
      {
         serializedObject.Update();
         EditorGUILayout.PropertyField(m_Padding, true);
         EditorGUILayout.PropertyField(m_Spacing, true);
         EditorGUILayout.PropertyField(m_StartCorner, true);
         EditorGUILayout.PropertyField(m_StartAxis, true);
         EditorGUILayout.PropertyField(m_ChildAlignment, true);
         EditorGUILayout.PropertyField(m_ColumnCount, true);
         EditorGUILayout.PropertyField(m_RowCount, true);         
         serializedObject.ApplyModifiedProperties();
      }
   }
}

All of this stuff is based on unity UI source code which is available here:
https://bitbucket.org/Unity-Technologies/ui

5 Likes

Exactly what i needed. Thank you!

You can try the FlowLayoutGroup component in unity-ui-extensions project.

2 Likes

I know this isn’t the OP’s desire but in case anyone finds themselves here (like I did) looking for solution to maximising the cell size in a GridLayoutGroup, I extended the code snippet above from @jamesburford - HTH

using System;
using UGS.unityutils.attributes;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.UI;

namespace ui {
    /// <summary>
    /// Configures a <see cref="GridLayoutGroup.cellSize"/> on same GameObject to have
    /// maximum size for the specified number of rows or columns.
    /// By default, it uses the number from the constraint configured in the <see cref="GridLayoutGroup"/>.
    /// Use the 'override' values here to override or specify a size
    /// when <see cref="GridLayoutGroup.Constraint.Flexible"/> used.
    /// </summary>
    public sealed class GridLayoutMaximiser : MonoBehaviour {

        [Tooltip("Override the number of columns to aim for (or zero for default/disabled).\n" +
                "Takes priority over rows.")]
        [SerializeField]
        private int numColumnsOverride = 0;

        [Tooltip("Override the number of rows to aim for (or zero for default/disabled).")]
        [SerializeField]
        private int numRowsOverride = 0;

        public void OnValidate() {
            Assert.IsNotNull(transform as RectTransform);
            Assert.IsNotNull(GetComponent<GridLayoutGroup>());
        }

        public void OnEnable() {
            setSizes();
        }

        [ContextMenu("Set sizes")]
        private void setSizes() {
            var gridLayoutGroup = GetComponent<GridLayoutGroup>();
            var columns = this.numColumnsOverride;
            var rows = this.numRowsOverride;
            switch (gridLayoutGroup.constraint) {
                case GridLayoutGroup.Constraint.Flexible: // nop
                    break;

                case GridLayoutGroup.Constraint.FixedColumnCount:
                    columns = gridLayoutGroup.constraintCount;
                    break;

                case GridLayoutGroup.Constraint.FixedRowCount:
                    rows = gridLayoutGroup.constraintCount;
                    break;

                default:
                    throw new ArgumentOutOfRangeException(gridLayoutGroup.constraint.ToString());
            }
            var padding = gridLayoutGroup.padding;
            var spacing = gridLayoutGroup.spacing;
            var size = ((RectTransform) transform).rect.size - new Vector2(padding.horizontal, padding.vertical);
            float width, height;
            if (0 < columns) {
                width = (size.x - (columns - 1) * spacing.x) / columns;
                if (0 < rows) {
                    height = (size.y - (rows - 1) * spacing.y) / rows;
                } else {
                    // TODO: account for different vertical to horizontal spacing
                    height = width;
                }
            } else {
                if (0 < rows) { // rows specified but not columns
                    // TODO: account for different vertical to horizontal spacing
                    width = height = (size.y - (rows - 1) * spacing.y) / rows;
                } else { // neither specified
                    return;
                }
            }

            gridLayoutGroup.cellSize = new Vector2(width, height);
        }

    }
}
2 Likes

In case someone was trying to make a ‘flexible’ UI like me and ended up here as well, I went with @lkc2015 logic with some changes. No code needed.

I wanted to split the grid in 4 equal cells, and when the parent canvas is resized (in different aspect ratios), the cells are also resized automatically, preserving their aspect ratio, and without overlapping each other.
Here is the hierarchy of objects:
Root Canvas (Screen Space - Camera)
– Flexible Canvas (Vertical Layout Group, with Child Control Size checked in both width and height)
---- Row 1 Canvas (Horizontal Layout Group, with Child Control Size checked in both width and height)
------ Child Canvas 1
------ Child Canvas 2
---- Row 2 Canvas (Same as Row 1)
------ Child Canvas 3
------ Child Canvas 4

Here is the result
3984838--342715--2ooumw.gif

Now you can put your own UI objects inside each Canvas and they will resize with it. (You might need to add an Aspect Ratio Fitter to your buttons and UI elements though and set their Rect Transform to Stretch).
Is there a better way ?

1 Like

What should I do if I want this: