Hello, this is my first post here. I am trying to create a variable width wrap grid similar to this.

For my use I’m looking for it to place children horizontally until it reaches the right edge of the container, then continue placing them on the next available row.
I’ve written a subclass of LayoutGroup called HorizontalWrapLayoutGroup, to accomplish this.
public class HorizontalWrapLayoutGroup : LayoutGroup
{
public Vector2 spacing;
public override void CalculateLayoutInputHorizontal()
{
if (transform.childCount == 0) return;
float layoutWidth = ((RectTransform)transform).rect.width;
float paddedRight = layoutWidth + padding.right;
float currentX = padding.left;
float currentY = padding.top;
float currentLineHeight = 0;
foreach (RectTransform child in transform)
{
if (currentX + child.rect.width > paddedRight)
{
currentY -= currentLineHeight + spacing.y;
currentX = padding.left;
currentLineHeight = 0;
}
child.anchoredPosition = new Vector2(currentX, currentY);
currentX = currentX + child.rect.width + spacing.x;
if (child.rect.height > currentLineHeight)
currentLineHeight = child.rect.height;
LayoutRebuilder.MarkLayoutForRebuild(child);
}
}
public override void CalculateLayoutInputVertical() {}
public override void SetLayoutHorizontal() {}
public override void SetLayoutVertical() {}
}
It appears to work if the pivot of all children are set to top left (I know this is kind of hacky). I’m okay with that setting, but the issue is when the LayoutGroup is nested under another LayoutGroup (a VerticalLayoutGroup in my case) it doesn’t position children correctly.
I noticed that when attaching a GridLayoutGroup to the same GameObject with my HorizontalWrapLayoutGroup component it sets a Height, but I’m not sure where to set that in my subclass.
I think this is a pretty useful control and I’d appreciate any help getting it to work.
1 Like
I took a look in the assembly to try and figure out what was going on with LayoutGroup and made a little progress. Now I’m stuck trying to figure out how to set HorizontalWrapLayoutGroup to redo the layout after new RectTransforms are added to it.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HorizontalWrapLayoutGroup : LayoutGroup
{
[SerializeField]
public Vector2 spacing = Vector2.zero;
public Vector2 Spacing { get { return this.spacing; } set { base.SetProperty<Vector2>(ref this.spacing, value); } }
// public bool logs = false;
public enum Axis
{
Horizontal,
Vertical
}
public override void CalculateLayoutInputHorizontal()
{
base.CalculateLayoutInputHorizontal();
// if (logs) Debug.Log(gameObject.name + " CalculateLayoutInputHorizontal, rectChildren.Count:" + rectChildren.Count);
float layoutWidth = ((RectTransform)transform).rect.width;
base.SetLayoutInputForAxis(layoutWidth, layoutWidth, 0, (int)Axis.Horizontal);
}
public override void CalculateLayoutInputVertical()
{
// if (logs) Debug.Log(gameObject.name + " CalculateLayoutInputVertical, rectChildren.Count:" + rectChildren.Count);
float num = (float)base.padding.vertical;
float totalMin = num;
float totalPreffered = num;
float totalFlexible = 0;
for (int i = 0; i < rectChildren.Count; i++)
{
RectTransform rect = rectChildren[i];
float minSize = LayoutUtility.GetMinSize(rect, (int)Axis.Vertical);
float preferredSize = LayoutUtility.GetPreferredSize(rect, (int)Axis.Vertical);
float fleibleSize = LayoutUtility.GetFlexibleSize(rect, (int)Axis.Vertical);
totalMin += minSize + this.spacing.y;
totalPreffered += preferredSize + this.spacing.y;
totalFlexible += fleibleSize;
}
base.SetLayoutInputForAxis(totalMin, totalPreffered, totalFlexible, (int)Axis.Vertical);
}
public override void SetLayoutHorizontal()
{
// if (logs) Debug.Log(gameObject.name + " SetLayoutHorizontal, rectChildren.Count:" + rectChildren.Count);
this.SetCellsAlongAxis(Axis.Horizontal);
}
public override void SetLayoutVertical()
{
// if (logs) Debug.Log(gameObject.name + " SetLayoutVertical, rectChildren.Count:" + rectChildren.Count);
this.SetCellsAlongAxis(Axis.Vertical);
}
void SetCellsAlongAxis(Axis axis)
{
if (axis == Axis.Vertical)
return;
if (transform.childCount == 0)
return;
float layoutWidth = ((RectTransform)transform).rect.width;
float paddedRight = layoutWidth + padding.right;
float currentX = padding.left;
float currentY = padding.top;
float currentLineHeight = 0;
for (int i = 0; i < rectChildren.Count; i++)
{
RectTransform child = rectChildren[i];
if (currentX + child.rect.width > paddedRight)
{
currentY -= currentLineHeight + Spacing.y;
currentX = padding.left;
currentLineHeight = 0;
}
base.SetChildAlongAxis(child, (int)Axis.Horizontal, currentX, child.rect.width);
base.SetChildAlongAxis(child, (int)Axis.Vertical, -currentY, child.rect.height);
currentX = currentX + child.rect.width + Spacing.x;
if (child.rect.height > currentLineHeight)
currentLineHeight = child.rect.height;
LayoutRebuilder.MarkLayoutForRebuild(child);
}
}
}
I’ve just now stumbled upon the need for a layout group that wraps its items, and was surprised to find that no such thing is included.
Did you ever finish your script for this?
Bump! I’m also curious to know if you ever figured this out and would be willing to share. Thx!
Chris