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

添加支持自动可见性剔除的滚动管理器。
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
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 978948c474d54c189ea6916ce91c0f0f
timeCreated: 1758073298
@@ -0,0 +1,48 @@
using UnityEditor;
using UnityEditor.UI;
using XericUI.OverrideLayout;
namespace XericUIEditor.OverrideLayout
{
[CustomEditor(typeof(XericScrollRect), true)]
[CanEditMultipleObjects]
public class XericScrollRectEditor : ScrollRectEditor
{
SerializedProperty m_EnableAutoActiveByMaskVisible;
SerializedProperty m_layoutGroup;
SerializedProperty m_preloadMargin;
SerializedProperty m_discernibleDistance;
SerializedProperty m_AAMVUpdateRate;
protected override void OnEnable()
{
base.OnEnable();
m_EnableAutoActiveByMaskVisible = serializedObject.FindProperty("enableAutoActiveByMaskVisible");
m_layoutGroup = serializedObject.FindProperty("layoutGroup");
m_preloadMargin = serializedObject.FindProperty("preloadMargin");
m_discernibleDistance = serializedObject.FindProperty("discernibleDistance");
m_AAMVUpdateRate = serializedObject.FindProperty("AAMVUpdateRate");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
EditorGUILayout.PropertyField(m_EnableAutoActiveByMaskVisible);
if (m_EnableAutoActiveByMaskVisible.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(m_layoutGroup, EditorGUIUtility.TrTextContent("Layout Group"));
EditorGUILayout.PropertyField(m_preloadMargin, EditorGUIUtility.TrTextContent("Preload Margin"));
EditorGUILayout.PropertyField(m_discernibleDistance, EditorGUIUtility.TrTextContent("Discernible Distance"));
EditorGUILayout.PropertyField(m_AAMVUpdateRate, EditorGUIUtility.TrTextContent("Aamv Update Rate"));
EditorGUI.indentLevel--;
}
serializedObject.ApplyModifiedProperties();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: da9c9fe3761a4cd4869d933df681ce9f
timeCreated: 1758088115
@@ -0,0 +1,32 @@
using UnityEditor;
using UnityEditor.UI;
using UnityEngine;
using XericUI.OverrideLayout;
namespace XericUIEditor.OverrideLayout
{
[CustomEditor(typeof(XericHorizontalOrVerticalLayoutGroup), true)]
[CanEditMultipleObjects]
public class XericHorizontalOrVerticalLayoutGroupEditor : HorizontalOrVerticalLayoutGroupEditor
{
SerializedProperty ignoreInvisible;
protected override void OnEnable()
{
base.OnEnable();
ignoreInvisible = serializedObject.FindProperty("layoutIgnoreInactive");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
EditorGUILayout.PropertyField(ignoreInvisible, true);
serializedObject.ApplyModifiedProperties();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 767372c332594dbf8fe2bca7e13fe0f4
timeCreated: 1758073315
+104 -6
View File
@@ -2,6 +2,7 @@
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace XericUI.Core.Base
{
@@ -11,11 +12,85 @@ namespace XericUI.Core.Base
[HideLabel, DisplayAsString]
string __EMPTYFIELD;
protected bool PublicFieldNullReference;
[System.NonSerialized] private Canvas _canvas;
[System.NonSerialized] private CanvasScaler _canvasScaler;
[System.NonSerialized] private GraphicRaycaster _graphicRaycaster;
[System.NonSerialized] private RectTransform _rect;
/// <summary>
/// 层级脏(层级被转移)
/// </summary>
private bool _hierarchyDirty;
/// <summary>
/// 根布局管理器
/// </summary>
public Canvas canvas
{
get
{
if (_canvas == null)
_canvas = GetComponentInParent<Canvas>();
#if UNITY_EDITOR
if (_canvas == null)
Debug.LogError($"{name} 无法找到布局组件,接下来的流程可能会引发错误", this);
#endif
return _canvas;
}
}
/// <summary>
/// 根布局缩放
/// </summary>
public CanvasScaler canvasScaler
{
get
{
if (_canvasScaler == null)
_canvasScaler = GetComponentInParent<CanvasScaler>();
#if UNITY_EDITOR
if (_canvas == null)
Debug.LogError($"{name} 无法找到布局缩放组件,接下来的流程可能会引发错误", this);
#endif
return _canvasScaler;
}
}
/// <summary>
/// 根布局射线检测
/// </summary>
public GraphicRaycaster graphicRaycaster
{
get
{
if (_graphicRaycaster == null)
_graphicRaycaster = GetComponentInParent<GraphicRaycaster>();
#if UNITY_EDITOR
if (_canvas == null)
Debug.LogError($"{name} 无法找到布局射线检测,接下来的流程可能会引发错误", this);
#endif
return _graphicRaycaster;
}
}
/// <summary>
/// ui变换组件
/// </summary>
protected RectTransform rectTransform
{
get
{
if (_rect == null)
_rect = GetComponent<RectTransform>();
return _rect;
}
}
#region 生命周期
protected bool AlreadyAwake { get; private set; }
protected bool AlreadyStart { get; private set; }
protected bool AlreadyOnEnable { get; private set; }
@@ -37,7 +112,7 @@ namespace XericUI.Core.Base
protected override void OnEnable()
{
AlreadyOnEnable = true;
AlreadyOnEnable = true;
base.OnEnable();
}
@@ -46,6 +121,29 @@ namespace XericUI.Core.Base
AlreadyOnDisable = true;
base.OnDisable();
}
#endregion
#region 脏处理
/// <summary>
/// 设置层级脏
/// </summary>
public void SetHierarchyDirty()
{
_canvas = null;
_canvasScaler = null;
_graphicRaycaster = null;
_hierarchyDirty = true;
}
/// <summary>
/// 复位层级脏
/// </summary>
public void ResetHierarchyDirty()
{
_hierarchyDirty = false;
}
#endregion
}
}
@@ -28,6 +28,7 @@ namespace XericUI.FlipPage
/// 两侧是固定的按钮,中间是一个横向的排列容器,在指定了按钮对象池后,将会自动生成绑定好的按钮用于翻页。
/// 页数按钮需要让button在预制体最外层,它的子集中需要包含一个TMP_Text。
/// </code>
[AddComponentMenu("Xeric UI Vessel/FlipPage/Basic FilpPage Manager")]
public class FlipPageComponentManager : XericFlipPageIndexVesselBase
{
#region 界面属性
@@ -29,6 +29,7 @@ namespace XericUI.FlipPage
/// currentPageIndex可以获取当前页数,Currentpage获取当前页面数据;
/// 在页面数据中pageNumber可以获取自己所在的页数,itemInfos获取页面中的所有数据项目。
/// </code>
[AddComponentMenu("Xeric UI Vessel/FlipPage/Basic FilpPage Database")]
public class XericFlipPageIndexVesselBase : XericUIBehaciour
{
#region 设定参数
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b1579b72a1f34c109523ae2b00b97d64
timeCreated: 1758069644
@@ -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