添加水平与垂直布局的重写管理,支持对不可见对象的布局计算。

添加支持自动可见性剔除的滚动管理器。
This commit is contained in:
2025-09-17 19:51:16 +08:00
parent 28a96e1fd8
commit ddb6806a9b
17 changed files with 590 additions and 6 deletions
@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UI;
using XericLibrary.Runtime.MacroLibrary;
namespace XericUI.OverrideLayout
{
[AddComponentMenu("Xeric UI Vessel/Layout/Xeric Horizontal Layout Group")]
/// <summary>
/// Layout child layout elements below each other.
/// </summary>
public class XericHorizontalLayoutGroup: XericHorizontalOrVerticalLayoutGroup
{
protected XericHorizontalLayoutGroup()
{}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void CalculateLayoutInputHorizontal()
{
rectChildren.Clear();
var toIgnoreList = ListPool<Component>.Get();
for (int i = 0; i < rectTransform.childCount; i++)
{
var rect = rectTransform.GetChild(i) as RectTransform;
if (rect == null ||
(layoutIgnoreInactive && !rect.gameObject.activeInHierarchy))
continue;
rect.GetComponents(typeof(ILayoutIgnorer), toIgnoreList);
if (toIgnoreList.Count == 0)
{
rectChildren.Add(rect);
continue;
}
for (int j = 0; j < toIgnoreList.Count; j++)
{
var ignorer = (ILayoutIgnorer)toIgnoreList[j];
if (!ignorer.ignoreLayout)
{
rectChildren.Add(rect);
break;
}
}
}
ListPool<Component>.Release(toIgnoreList);
m_Tracker.Clear();
CalcAlongAxis(0, false);
}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void CalculateLayoutInputVertical()
{
CalcAlongAxis(1, false);
}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void SetLayoutHorizontal()
{
SetChildrenAlongAxis(0, false);
}
/// <summary>
/// Called by the layout system. Also see ILayoutElement
/// </summary>
public override void SetLayoutVertical()
{
SetChildrenAlongAxis(1, false);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3e670ed839c14690b0def247bb026b7f
timeCreated: 1758071458
@@ -0,0 +1,20 @@
using Sirenix.OdinInspector;
using UnityEngine.UI;
namespace XericUI.OverrideLayout
{
public abstract class XericHorizontalOrVerticalLayoutGroup : HorizontalOrVerticalLayoutGroup
{
/// <summary>
/// 布局时忽略不可见的成员
/// </summary>
public bool layoutIgnoreInactive = false;
#if UNITY_EDITOR
protected override void Update()
{
base.Update();
}
#endif
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 03e36a172f6a436aa493090a9e69d25a
timeCreated: 1758073390
+211
View File
@@ -0,0 +1,211 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UI;
using XericLibrary.Runtime.MacroLibrary;
using XericLibraryEditor.Debug;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector;
#endif
namespace XericUI.OverrideLayout
{
[AddComponentMenu("Xeric UI Vessel/UI/Xeric Scroll Rect")]
[SelectionBase]
[ExecuteAlways]
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
public class XericScrollRect : ScrollRect
{
#region 事件委托
// public Action
#endregion
#region 字段属性
/// <summary>
/// 启用自动剔除功能
/// </summary>
public bool enableAutoActiveByMaskVisible = true;
/// <summary>
/// 容器布局
/// </summary>
public LayoutGroup layoutGroup;
/// <summary>
/// 容器预加载像素范围
/// </summary>
public float preloadMargin = 200f;
/// <summary>
/// 容器加载响应可被察觉的变化长度
/// </summary>
public float discernibleDistance = 10f;
/// <summary>
/// 自动剔除更新速率
/// </summary>
[Tooltip("当设定满速率时,实际上是期望每帧执行更新")]
// ReSharper disable once InconsistentNaming
[Range(0.02f, 1)]
public float AAMVUpdateRate = .9f;
#endregion
#region 布局参考
private float _aamvUpdateTime;
private Vector2 _lastContextSize;
private Vector2 _lastContextAnchoredPosition;
// 脏标记处理率,每次激活脏标记时给予多份权重,每次空处理则返还一部分权重,如果出现标记大于0的情况,说明脏标记处理次数太多
private float _dirtyTakeRate;
private HashSet<Transform> _lastActiveSet;
#endregion
#region 生命周期
#if UNITY_EDITOR
private Vector3[] vertex1 = new Vector3[4];
private Vector3[] vertex2 = new Vector3[4];
private Vector3[] vertex3 = new Vector3[4];
private void OnDrawGizmosSelected()
{
var lossyScale = transform.lossyScale;
var scale = lossyScale * preloadMargin;
var dis2 = lossyScale * discernibleDistance;
content.GetWorldCorners(vertex1);
viewport.GetWorldCorners(vertex2);
var rect = new Rect(vertex2[0], new Vector2(vertex2[2].x - vertex2[0].x, vertex2[2].y - vertex2[0].y));
var viewportMin = rect.min * lossyScale;
var viewportMax = rect.max * lossyScale;
// vertex3[0] = new Vector3(vertex2[0].x - scale.x, vertex2[0].y - scale.y, vertex2[0].z);
// vertex3[1] = new Vector3(vertex2[1].x - scale.x, vertex2[1].y + scale.y, vertex2[1].z);
// vertex3[2] = new Vector3(vertex2[2].x + scale.x, vertex2[2].y + scale.y, vertex2[2].z);
// vertex3[3] = new Vector3(vertex2[3].x + scale.x, vertex2[3].y - scale.y, vertex2[3].z);
vertex3[0] = new Vector3(rect.min.x - scale.x, rect.min.y - scale.y, vertex2[0].z);
vertex3[1] = new Vector3(rect.min.x - scale.x, rect.max.y + scale.y, vertex2[1].z);
vertex3[2] = new Vector3(rect.max.x + scale.x, rect.max.y + scale.y, vertex2[2].z);
vertex3[3] = new Vector3(rect.max.x + scale.x, rect.min.y - scale.y, vertex2[3].z);
Gizmos.color = Color.yellow;
MacroGizmosDraw.DrawGizmosRectLinkLine(vertex1, vertex3, Color.grey, Color.cyan, true);
}
#endif
protected override void Awake()
{
base.Awake();
// 布局
if (layoutGroup == null)
layoutGroup = transform.GetComponentInParent<LayoutGroup>();
if (layoutGroup is XericHorizontalOrVerticalLayoutGroup xericLayoutGroup)
{
xericLayoutGroup.layoutIgnoreInactive = false;
}
}
protected override void LateUpdate()
{
if (!enableAutoActiveByMaskVisible)
{
base.LateUpdate();
return;
}
// 计算更新速率
var targetFrameRate = Application.targetFrameRate;
var frameAamvRate = Time.deltaTime / Mathf.Max(0.02f, AAMVUpdateRate);
_dirtyTakeRate = Mathf.Max(_dirtyTakeRate - AAMVUpdateRate, 0);
if (AAMVUpdateRate < 1)
{
if (_aamvUpdateTime < frameAamvRate)
{
_aamvUpdateTime += Time.deltaTime;
base.LateUpdate();
return;
}
_aamvUpdateTime %= frameAamvRate;
}
var sqrDistance = MacroMath.SqrMagnitudeDistance(content.anchoredPosition, _lastContextAnchoredPosition);
if (sqrDistance > discernibleDistance * discernibleDistance)
{
_lastContextAnchoredPosition = content.anchoredPosition;
SetAAMVDirty();
}
base.LateUpdate();
}
private Vector3[] viewportVertex = new Vector3[4];
/// <summary>
/// 自动剔除更新标记
/// </summary>
protected void SetAAMVDirty()
{
_dirtyTakeRate += AAMVUpdateRate * 2;
var nowActiveChildrenSet = HashSetPool<Transform>.Get();
// 将视口尺寸转换到content本地空间
// 手动计算视口在content空间的矩形
viewport.GetWorldCorners(viewportVertex);
var viewportRect = new Rect(
content.InverseTransformPoint(viewportVertex[0]),
content.InverseTransformPoint(viewportVertex[2]) - content.InverseTransformPoint(viewportVertex[0]));
var contentRect = content.rect;
// 计算预加载边距(考虑content缩放)
var scaledMargin = preloadMargin / ((content.lossyScale.x + content.lossyScale.y) * 0.5f);
// 扩展可见区域边界
var visibleMin = viewportRect.min - new Vector2(preloadMargin, preloadMargin);
var visibleMax = viewportRect.max + new Vector2(preloadMargin, preloadMargin);
// 获取当前滚动归一化位置
var scrollPos = new Vector2(
horizontal ? horizontalScrollbar?.value ?? 0 : 0,
vertical ? 1 - (verticalScrollbar?.value ?? 1) : 0);
var isinit = _lastActiveSet == null;
foreach (var child in content.GetChildren())
{
var rectTransform = child.RectTransform();
// 直接使用anchoredPosition进行边界检测
var childPos = rectTransform.anchoredPosition;
var isVisible =
childPos.x >= visibleMin.x &&
childPos.x <= visibleMax.x &&
childPos.y >= visibleMin.y &&
childPos.y <= visibleMax.y;
var isActive = isinit || _lastActiveSet.Contains(child);
if (isVisible)
nowActiveChildrenSet.Add(child);
if (isVisible == isActive)
continue;
child.gameObject.SetActive(isVisible);
}
if (!isinit)
HashSetPool<Transform>.Release(_lastActiveSet);
_lastActiveSet = nowActiveChildrenSet;
}
#endregion
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 21d3fc25898d49ccb45afe12994fe271
timeCreated: 1758087168
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UI;
using XericLibrary.Runtime.MacroLibrary;
namespace XericUI.OverrideLayout
{
[AddComponentMenu("Xeric UI Vessel/Layout/Xeric Vertical Layout Group")]
/// <summary>
/// Layout child layout elements below each other.
/// </summary>
public class XericVerticalLayoutGroup : XericHorizontalOrVerticalLayoutGroup
{
protected XericVerticalLayoutGroup()
{}
public override void CalculateLayoutInputHorizontal()
{
rectChildren.Clear();
var toIgnoreList = ListPool<Component>.Get();
for (int i = 0; i < rectTransform.childCount; i++)
{
var rect = rectTransform.GetChild(i) as RectTransform;
if (rect == null ||
(layoutIgnoreInactive && !rect.gameObject.activeInHierarchy))
continue;
rect.GetComponents(typeof(ILayoutIgnorer), toIgnoreList);
if (toIgnoreList.Count == 0)
{
rectChildren.Add(rect);
continue;
}
for (int j = 0; j < toIgnoreList.Count; j++)
{
var ignorer = (ILayoutIgnorer)toIgnoreList[j];
if (!ignorer.ignoreLayout)
{
rectChildren.Add(rect);
break;
}
}
}
ListPool<Component>.Release(toIgnoreList);
m_Tracker.Clear();
CalcAlongAxis(0, true);
}
public override void CalculateLayoutInputVertical()
{
CalcAlongAxis(1, true);
}
public override void SetLayoutHorizontal()
{
SetChildrenAlongAxis(0, true);
}
public override void SetLayoutVertical()
{
SetChildrenAlongAxis(1, true);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 77c074fdcbd94c09a9c1272318ebfda6
timeCreated: 1758069667