using UnityEngine;
|
using System.Collections.Generic;
|
|
[ExecuteAlways]
|
[RequireComponent(typeof(RectTransform))]
|
public class HomeGridLayout : MonoBehaviour
|
{
|
public RectOffset padding = new RectOffset(); // 边距
|
public Vector2 cellSize = new Vector2(100f, 100f); // 单元格大小
|
public Vector2 spacing = new Vector2(10f, 10f); // 元素间距
|
public int rows = 7; // 固定行数
|
|
private bool _isDirty = true; //优化新增:脏标记与计时器 初始设为 true,确保第一帧会排版
|
private float _checkTimer = 0f;
|
private const float CHECK_INTERVAL = 0.1f; // 100ms 检查间隔
|
|
/// <summary>
|
/// 供外部或子元素调用,标记当前网格需要重新排版
|
/// </summary>
|
public void MarkAsDirty()
|
{
|
_isDirty = true;
|
}
|
|
// 尺寸或子节点数量发生变化时,同样只打上脏标记
|
private void OnRectTransformDimensionsChange() => MarkAsDirty();
|
private void OnTransformChildrenChanged() => MarkAsDirty();
|
|
private void Update()
|
{
|
#if UNITY_EDITOR
|
// 编辑器非运行状态下(Edit Mode)Update 帧率不稳定,为了方便拖拽预览,依然有脏标记就立即更新
|
if (!Application.isPlaying)
|
{
|
if (_isDirty)
|
{
|
UpdateLayout();
|
_isDirty = false;
|
}
|
return;
|
}
|
#endif
|
|
// 运行时(Play Mode)每 100ms 轮询一次
|
_checkTimer += Time.deltaTime;
|
if (_checkTimer >= CHECK_INTERVAL)
|
{
|
if (_isDirty)
|
{
|
UpdateLayout();
|
_isDirty = false; // 排版完成后,清除标志位
|
}
|
_checkTimer = 0f; // 重置计时器
|
}
|
}
|
|
public void UpdateLayout()
|
{
|
if (rows <= 0 || transform.childCount == 0) return;
|
|
List<RectTransform> flowChildren = new List<RectTransform>();
|
List<RectTransform> fixedChildren = new List<RectTransform>();
|
|
// 1. 遍历收集并分类所有激活的子物体
|
for (int i = 0; i < transform.childCount; i++)
|
{
|
RectTransform child = transform.GetChild(i) as RectTransform;
|
if (child == null || !child.gameObject.activeSelf) continue;
|
|
HomeGridLayoutCell cell = child.GetComponent<HomeGridLayoutCell>();
|
|
if (cell != null && cell.isFixedPosition)
|
fixedChildren.Add(child);
|
else
|
flowChildren.Add(child);
|
}
|
|
// 2. 排序流动物体 (按 sortIndex)
|
flowChildren.Sort((a, b) =>
|
{
|
var cellA = a.GetComponent<HomeGridLayoutCell>();
|
var cellB = b.GetComponent<HomeGridLayoutCell>();
|
int indexA = cellA != null ? cellA.sortIndex : int.MaxValue;
|
int indexB = cellB != null ? cellB.sortIndex : int.MaxValue;
|
return indexA.CompareTo(indexB);
|
});
|
|
// 3. 排序固定物体 (优先 sortIndex,其次 subSortIndex)
|
fixedChildren.Sort((a, b) =>
|
{
|
var cellA = a.GetComponent<HomeGridLayoutCell>();
|
var cellB = b.GetComponent<HomeGridLayoutCell>();
|
|
int sortA = cellA != null ? cellA.sortIndex : int.MaxValue;
|
int sortB = cellB != null ? cellB.sortIndex : int.MaxValue;
|
|
if (sortA != sortB) return sortA.CompareTo(sortB);
|
|
int subA = cellA != null ? cellA.subSortIndex : int.MaxValue;
|
int subB = cellB != null ? cellB.subSortIndex : int.MaxValue;
|
return subA.CompareTo(subB);
|
});
|
|
// 4. 开始分配网格 (核心优化部分)
|
int currentGridIndex = 0; // 当前推演到的真实网格坑位
|
int flowIndex = 0;
|
int fixedIndex = 0;
|
int totalValidChildren = flowChildren.Count + fixedChildren.Count;
|
|
for (int i = 0; i < totalValidChildren; i++)
|
{
|
RectTransform targetChild = null;
|
HomeGridLayoutCell nextFixedCell = null;
|
|
if (fixedIndex < fixedChildren.Count)
|
{
|
nextFixedCell = fixedChildren[fixedIndex].GetComponent<HomeGridLayoutCell>();
|
}
|
|
// 分支 A:固定物体已经到了它期望的网格位置(或已经被前面的元素挤到了当前位置)
|
if (nextFixedCell != null && nextFixedCell.sortIndex <= currentGridIndex)
|
{
|
targetChild = fixedChildren[fixedIndex];
|
fixedIndex++;
|
}
|
// 分支 B:当前位置没有固定物体抢占,且还有流动物体排队,让流动物体填补空缺
|
else if (flowIndex < flowChildren.Count)
|
{
|
targetChild = flowChildren[flowIndex];
|
flowIndex++;
|
}
|
// 分支 C:没有流动物体填补空缺了,直接让网格索引“快进”到下一个固定物体的位置
|
else if (nextFixedCell != null)
|
{
|
currentGridIndex = nextFixedCell.sortIndex; // 快进跳过中间的所有空白格子
|
targetChild = fixedChildren[fixedIndex];
|
fixedIndex++;
|
}
|
|
// 应用计算好的网格坐标
|
if (targetChild != null)
|
{
|
SetChildTransform(targetChild, currentGridIndex);
|
currentGridIndex++; // 占位成功,坑位后移
|
}
|
}
|
}
|
|
/// <summary>
|
/// 将子物体放置到指定的网格索引位置
|
/// </summary>
|
private void SetChildTransform(RectTransform child, int gridIndex)
|
{
|
int col = gridIndex / rows;
|
int row = gridIndex % rows;
|
|
// 1. 先计算出如果轴心在右上角时的理论边缘坐标
|
float edgeXPos = -padding.right - col * (cellSize.x + spacing.x);
|
float edgeYPos = -padding.top - row * (cellSize.y + spacing.y);
|
|
// 2. 为了消除团队潜规则,我们将格子的轴心改回正中心 (0.5, 0.5)
|
// 因此实际坐标需要向左、向下再偏移半个单元格的大小
|
float centerXPos = edgeXPos - (cellSize.x * 0.5f);
|
float centerYPos = edgeYPos - (cellSize.y * 0.5f);
|
|
// 统一锚点为右上角 (1, 1)
|
child.anchorMin = new Vector2(1, 1);
|
child.anchorMax = new Vector2(1, 1);
|
|
// 轴心(Pivot)改为正中心!
|
child.pivot = new Vector2(0.5f, 0.5f);
|
|
child.sizeDelta = cellSize;
|
|
// 赋予中心坐标
|
child.anchoredPosition = new Vector2(centerXPos, centerYPos);
|
}
|
}
|