Swap from Horizontal to Vertical Layout Group?

Hi, I am trying to change a Layout Group with buttons from Horizontal to Vertical and vice versa, so the buttons are displayed horizontally when the screen orientation is Portrait, and vertically when it’s Landscape.

I also want to change the position of this Layout so it’s different in each screen orientation.

Is there any way I can do this?

Thank you really much!

The layout group horizontal and vertical can’t be assigned on the same object, fortunatly there are some solutions for you:

  • Delete and create a new layout group each time the screen change rotation
  • Create 2 panels with the layout groups, and assign the buttons to each one every time with transform.SetParent()

I suggest to you the second one

2 Likes

I wrote you some code

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

public class ChangeLayout  : MonoBehaviour {

    public VerticalLayoutGroup verticalGroup;
    public HorizontalLayoutGroup horizontalGroup;
    public Button[] buttons;

    void ChangeVerticalLayout()
    {
        verticalGroup.gameObject.SetActive ( true );
        horizontalGroup.gameObject.SetActive ( false );
        foreach ( Button _button in buttons )
        {
            _button.transform.SetParent ( verticalGroup.gameObject.transform );
        }
    }

    void ChangeHorizontalLayout()
    {
        verticalGroup.gameObject.SetActive ( false );
        horizontalGroup.gameObject.SetActive ( true );
        foreach ( Button _button in buttons )
        {
            _button.transform.SetParent ( horizontalGroup.gameObject.transform );
        }
    }

}
2 Likes

Hi, I’ve modified the GridLayout to automatically swap from horizontal-vertical mode (just with 2 panels for now).
It work in Editor mode and there’s a slide range to change the percent size of the 1st panel.
When the width of the screen is less than the height it will turn in vertical mode.

using UnityEngine;
using System.Collections.Generic;

namespace UnityEngine.UI
{
    [AddComponentMenu("Layout/Grid Layout Group", 152)]
    public class TwoPanelsLayout : 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 }

        private protected Corner m_StartCorner = Corner.UpperLeft;
        private Corner startCorner { get { return m_StartCorner; } set { SetProperty(ref m_StartCorner, value); } }

        private protected Axis m_StartAxis = Axis.Horizontal;
        private Axis startAxis { get { return m_StartAxis; } set { SetProperty(ref m_StartAxis, value); } }

        private Vector2 m_CellSize = new Vector2(100, 100);
        public Vector2 cellSize { get { return m_CellSize; } set { SetProperty(ref m_CellSize, value); } }

        private Vector2 m_Spacing = Vector2.zero;
        public Vector2 spacing { get { return m_Spacing; } set { SetProperty(ref m_Spacing, value); } }

        private protected Constraint m_Constraint = Constraint.Flexible;
        public Constraint constraint { get { return m_Constraint; } set { SetProperty(ref m_Constraint, value); } }

        private protected int m_ConstraintCount = 2;
        public int constraintCount { get { return m_ConstraintCount; } set { SetProperty(ref m_ConstraintCount, Mathf.Max(1, value)); } }

        protected TwoPanelsLayout()
        { }

        [Header ("Custom")]

        [Range (0,1)]
        [SerializeField] protected float size = 0.5f;
        private float _panelSizeDivision;

        [SerializeField] protected bool swapOnChangeAxis = true;
        private Constraint _initConstrain;
        private bool _isSwapped;


#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.


            RectTransform _rect = GetComponent<RectTransform>();
            float screenWidth = _rect.sizeDelta.x;
            float screenHeight = _rect.sizeDelta.y;


            if (screenWidth > screenHeight)
            {
                constraint = Constraint.FixedColumnCount;
            }

            else if (screenWidth <= screenHeight)
            {
                constraint = Constraint.FixedRowCount;
            }







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



            //Swap panels
            if (swapOnChangeAxis && constraint != _initConstrain)
            {
                rectChildren[1].SetSiblingIndex(0);
                _initConstrain = constraint;
                _isSwapped = !_isSwapped;
                //Debug.Log("SWAPPED");
            }



            if (!_isSwapped) _panelSizeDivision = size;
            else _panelSizeDivision = 1 - size;




            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 multiplier = 0;
                float factorX = 0;
                float factorY = 0;

                if (i == 0) multiplier = (1 - _panelSizeDivision) * 2;
                if (i == 1) multiplier = 2 - ((1 - _panelSizeDivision) * 2);


                float realsizeX = 0;

                if (m_Constraint == Constraint.FixedColumnCount)
                {
                    realsizeX = ((width - (spacing[0] * (actualCellCountX - 1))) / actualCellCountX) * multiplier;
                    factorX = (realsizeX / multiplier) - (realsizeX - (realsizeX / multiplier));
                }
                  
                else if (m_Constraint == Constraint.FixedRowCount)
                {
                    realsizeX = ((width - (spacing[0] * (actualCellCountX - 1))) / actualCellCountX);
                    factorX = realsizeX;
                }
                  

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



                float realsizeY = 0;

                if (m_Constraint == Constraint.FixedRowCount)
                {
                    realsizeY = ((height - (spacing[0] * (actualCellCountY - 1))) / actualCellCountY) * multiplier;
                    factorY = (realsizeY / multiplier) - (realsizeY - (realsizeY / multiplier));
                }
                  
                else if (m_Constraint == Constraint.FixedColumnCount)
                {
                    realsizeY = ((height - (spacing[0] * (actualCellCountY - 1))) / actualCellCountY);
                    factorY = realsizeY;
                }
                  

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

Hi, so I also wanted to be able to switch between horizontal and vertical layout on the fly, from the same component.

Fortunately I realized that HorizontalLayoutGroup and VerticalLayoutGroup are pretty thin layers over the HorizontalOrVerticalGroup script, they both have the same code with just a simple variable changed.

So I combined them into a simple script, and now I can switch between horizontal and vertical on the fly, from the inspector or from script (via the “isVertical” bool property).

Hope you’ll like it ! :slight_smile:

Edit: Fixed some typos.

#if UNITY_EDITOR

using UnityEngine.UI;

#endif

// This is a simple class that combines HorizontalLayoutGroup and VerticalLayoutGroup,
// so we can switch between horizontal and vertical layout easily.

// From script we can use the "isVertical" bool property to control the axis.
// From inspector this bool is controlled by a more convenient "Layout Axis" enum.

namespace UnityEngine.UI
{
    [AddComponentMenu("Layout/HV Layout Group", 153)]
    public class HVLayoutGroup : HorizontalOrVerticalLayoutGroup
    {
        protected HVLayoutGroup() { }

        [SerializeField] protected bool m_IsVertical = true;
        public bool isVertical { get { return m_IsVertical; } set { SetProperty(ref m_IsVertical, value); } }

        public override void CalculateLayoutInputHorizontal()
        {
            base.CalculateLayoutInputHorizontal();
            CalcAlongAxis(0, m_IsVertical);
        }

        public override void CalculateLayoutInputVertical()
        {
            CalcAlongAxis(1, m_IsVertical);
        }

        public override void SetLayoutHorizontal() => SetChildrenAlongAxis(0, m_IsVertical);
        public override void SetLayoutVertical() => SetChildrenAlongAxis(1, m_IsVertical);
    }
}

#if UNITY_EDITOR

namespace UnityEditor.UI
{
    [CustomEditor(typeof(HVLayoutGroup), true)]
    [CanEditMultipleObjects]
    public class HVLayoutGroupEditor : HorizontalOrVerticalLayoutGroupEditor
    {
        private enum LayoutAxis { Horizontal, Vertical }

        private LayoutAxis m_LayoutAxis;
        private SerializedProperty m_IsVertical;

        protected override void OnEnable()
        {
            base.OnEnable();
            m_IsVertical = serializedObject.FindProperty("m_IsVertical");
        }

        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            //EditorGUILayout.PropertyField(m_IsVertical, true);

            m_LayoutAxis = m_IsVertical.boolValue ? LayoutAxis.Vertical : LayoutAxis.Horizontal;
            m_LayoutAxis = (LayoutAxis)EditorGUILayout.EnumPopup("Layout Axis", m_LayoutAxis);
            m_IsVertical.boolValue = m_LayoutAxis == LayoutAxis.Vertical ? true : false;

            serializedObject.ApplyModifiedProperties();

            base.OnInspectorGUI();
        }
    }
}

#endif

8225898–1074600–HVLayoutGroup.cs (2.18 KB)

13 Likes

If you simply want to do this when editing prefabs, here’s a simple script to drop in the Editor folder

LayoutGroupContextUtil.cs (1.3 KB)

using UnityEngine;
using UnityEditor;

public static class LayoutGroupUtil
{
    private const string VerticalLayoutGroupGuid = "59f8146938fff824cb5fd77236b75775";
    private const string HorizontalLayoutGroupGuid = "30649d3a9faa99c48a7b1166b86bf2a0";

    [MenuItem("CONTEXT/HorizontalLayoutGroup/Convert to VerticalLayoutGroup")]
    private static void ConvertToVerticalLayoutGroup(MenuCommand command)
    {
        Convert(command.context, VerticalLayoutGroupGuid);
    }


    [MenuItem("CONTEXT/VerticalLayoutGroup/Convert to HorizontalLayoutGroup")]
    private static void ConvertToHorizontal(MenuCommand command)
    {
        Convert(command.context, HorizontalLayoutGroupGuid);
    }

    private static void Convert(Object context, string guid)
    {
        MonoBehaviour layoutGroup = (MonoBehaviour)context;

        if (!layoutGroup)
        {
            return;
        }

        Undo.RecordObject(layoutGroup, "Convert Layout Group");

        SerializedObject so = new (layoutGroup);

        SerializedProperty scriptProperty = so.FindProperty("m_Script");
        so.Update();
        scriptProperty.objectReferenceValue = AssetDatabase.LoadAssetAtPath<MonoScript>(AssetDatabase.GUIDToAssetPath(guid));
        so.ApplyModifiedProperties();
    }
}

1 Like