hch
12 小时以前 bc6f633a2f3cfc01122d8fd4452f69313ddcb32b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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);
    }
}