From 21a09edad2e54272ac63a3cf68a1ddfc7ade80a7 Mon Sep 17 00:00:00 2001 From: LiRuoChen <571244399@qq.com> Date: Thu, 16 Apr 2026 16:46:20 +0800 Subject: [PATCH] =?UTF-8?q?xericScrollRect=E7=BB=84=E4=BB=B6=E4=B8=AD?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E7=BD=91=E6=A0=BC=E5=B8=83=E5=B1=80?= =?UTF-8?q?=E5=92=8C=E5=B0=BA=E5=AF=B8=E8=AE=A1=E7=AE=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OverrideLayout/XericScrollRectEditor.cs | 45 +- Editor/OverrideUI/XericToggleEditor.cs | 11 +- Runtime/Core/Interface.meta | 3 + .../Core/Interface/IUIVesselVisibalControl.cs | 22 + .../Interface/IUIVesselVisibalControl.cs.meta | 3 + Runtime/Helper/GridLayoutCalculator.cs | 255 +++++++++++ Runtime/Helper/GridLayoutCalculator.cs.meta | 3 + Runtime/Helper/UIVesselHelp.cs | 19 + Runtime/Helper/UIVesselHelp.cs.meta | 3 + Runtime/OverrideLayout/XericScrollRect.cs | 408 +++++++++++++----- 10 files changed, 652 insertions(+), 120 deletions(-) create mode 100644 Runtime/Core/Interface.meta create mode 100644 Runtime/Core/Interface/IUIVesselVisibalControl.cs create mode 100644 Runtime/Core/Interface/IUIVesselVisibalControl.cs.meta create mode 100644 Runtime/Helper/GridLayoutCalculator.cs create mode 100644 Runtime/Helper/GridLayoutCalculator.cs.meta create mode 100644 Runtime/Helper/UIVesselHelp.cs create mode 100644 Runtime/Helper/UIVesselHelp.cs.meta diff --git a/Editor/OverrideLayout/XericScrollRectEditor.cs b/Editor/OverrideLayout/XericScrollRectEditor.cs index 43e2efd..f9bf5f4 100644 --- a/Editor/OverrideLayout/XericScrollRectEditor.cs +++ b/Editor/OverrideLayout/XericScrollRectEditor.cs @@ -1,5 +1,6 @@ using UnityEditor; using UnityEditor.UI; +using UnityEngine; using XericUI.OverrideLayout; namespace XericUIEditor.OverrideLayout @@ -13,15 +14,33 @@ namespace XericUIEditor.OverrideLayout SerializedProperty m_preloadMargin; SerializedProperty m_discernibleDistance; SerializedProperty m_AAMVUpdateRate; + SerializedProperty m_childrenPool; + + SerializedProperty m_cellSize; + SerializedProperty m_spacing; + SerializedProperty m_padding; + SerializedProperty m_startCorner; + SerializedProperty m_startAxis; + SerializedProperty m_childAlignment; + SerializedProperty m_constraint; protected override void OnEnable() { base.OnEnable(); - m_EnableAutoActiveByMaskVisible = serializedObject.FindProperty("enableAutoActiveByMaskVisible"); + m_EnableAutoActiveByMaskVisible = serializedObject.FindProperty("m_enableAdvancedFunc"); m_layoutGroup = serializedObject.FindProperty("layoutGroup"); - m_preloadMargin = serializedObject.FindProperty("preloadMargin"); - m_discernibleDistance = serializedObject.FindProperty("discernibleDistance"); - m_AAMVUpdateRate = serializedObject.FindProperty("AAMVUpdateRate"); + m_preloadMargin = serializedObject.FindProperty("m_preloadMargin"); + m_discernibleDistance = serializedObject.FindProperty("m_discernibleDistance"); + m_AAMVUpdateRate = serializedObject.FindProperty("m_AAMVUpdateRate"); + + m_childrenPool = serializedObject.FindProperty("childrenPool"); + m_cellSize = serializedObject.FindProperty("m_cellSize"); + m_spacing = serializedObject.FindProperty("m_spacing"); + m_padding = serializedObject.FindProperty("m_padding"); + m_startCorner = serializedObject.FindProperty("m_startCorner"); + m_startAxis = serializedObject.FindProperty("m_startAxis"); + m_childAlignment = serializedObject.FindProperty("m_childAlignment"); + m_constraint = serializedObject.FindProperty("m_constraint"); } public override void OnInspectorGUI() @@ -29,7 +48,7 @@ namespace XericUIEditor.OverrideLayout base.OnInspectorGUI(); serializedObject.Update(); - + EditorGUILayout.PropertyField(m_EnableAutoActiveByMaskVisible); if (m_EnableAutoActiveByMaskVisible.boolValue) { @@ -37,11 +56,21 @@ namespace XericUIEditor.OverrideLayout 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")); - + EditorGUILayout.PropertyField(m_AAMVUpdateRate, EditorGUIUtility.TrTextContent("Update Rate")); + EditorGUILayout.PropertyField(m_childrenPool, EditorGUIUtility.TrTextContent("Item Pool")); + EditorGUI.indentLevel--; + EditorGUILayout.LabelField(new GUIContent("GridLayout")); + EditorGUI.indentLevel++; + EditorGUILayout.PropertyField(m_cellSize); + EditorGUILayout.PropertyField(m_spacing); + EditorGUILayout.PropertyField(m_padding); + EditorGUILayout.PropertyField(m_startCorner); + EditorGUILayout.PropertyField(m_startAxis); + EditorGUILayout.PropertyField(m_childAlignment); + EditorGUILayout.PropertyField(m_constraint); EditorGUI.indentLevel--; } - + serializedObject.ApplyModifiedProperties(); } } diff --git a/Editor/OverrideUI/XericToggleEditor.cs b/Editor/OverrideUI/XericToggleEditor.cs index efb8840..9cb0001 100644 --- a/Editor/OverrideUI/XericToggleEditor.cs +++ b/Editor/OverrideUI/XericToggleEditor.cs @@ -70,18 +70,19 @@ namespace XericUI EditorGUILayout.PropertyField(m_GraphicProperty); EditorGUILayout.PropertyField(m_TextProperty); EditorGUILayout.PropertyField(m_EnableTextColorTransitionProperty); - if (EditorGUILayout.BeginFadeGroup(m_EnableTextColorTransitionProperty.boolValue ? 1 : 0)) + if (m_EnableTextColorTransitionProperty.boolValue) { + EditorGUI.indentLevel++; EditorGUILayout.PropertyField(m_TextTransitionColorModeProperty); + EditorGUI.indentLevel--; } - EditorGUILayout.EndFadeGroup(); - EditorGUILayout.PropertyField(m_enableTextContextTransitionProperty); - if (EditorGUILayout.BeginFadeGroup(m_enableTextContextTransitionProperty.boolValue ? 1 : 0)) + if (m_enableTextContextTransitionProperty.boolValue) { + EditorGUI.indentLevel++; EditorGUILayout.PropertyField(m_textTransitionContextModeProperty); + EditorGUI.indentLevel--; } - EditorGUILayout.EndFadeGroup(); EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_GroupProperty); diff --git a/Runtime/Core/Interface.meta b/Runtime/Core/Interface.meta new file mode 100644 index 0000000..47b1f7f --- /dev/null +++ b/Runtime/Core/Interface.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f441117a49ca43f4a6bf105e18562875 +timeCreated: 1776309301 \ No newline at end of file diff --git a/Runtime/Core/Interface/IUIVesselVisibalControl.cs b/Runtime/Core/Interface/IUIVesselVisibalControl.cs new file mode 100644 index 0000000..d6cc6a6 --- /dev/null +++ b/Runtime/Core/Interface/IUIVesselVisibalControl.cs @@ -0,0 +1,22 @@ +namespace XericUI.Core.Interface +{ + /// + /// Xeric UI Vessel容器控制接口 + /// + public interface IUIVesselVisibalControl + { + /// + /// 获取当前元素的可见性 + /// + public bool GetUIVisibal { get; } + /// + /// 当前元素处于可见范围时(预载范围) + /// + public void UnderVisibal(); + /// + /// 当前元素离开可见范围时(预载范围) + /// + + public void OutOfVisibal(); + } +} \ No newline at end of file diff --git a/Runtime/Core/Interface/IUIVesselVisibalControl.cs.meta b/Runtime/Core/Interface/IUIVesselVisibalControl.cs.meta new file mode 100644 index 0000000..23111ef --- /dev/null +++ b/Runtime/Core/Interface/IUIVesselVisibalControl.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 055f74c72fcd49e5a99faea1af98ebc1 +timeCreated: 1776309332 \ No newline at end of file diff --git a/Runtime/Helper/GridLayoutCalculator.cs b/Runtime/Helper/GridLayoutCalculator.cs new file mode 100644 index 0000000..2046108 --- /dev/null +++ b/Runtime/Helper/GridLayoutCalculator.cs @@ -0,0 +1,255 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +namespace XericUI.Helper +{ + public static class GridLayoutCalculator + { + public struct LayoutItem + { + public Vector2 position; // anchoredPosition + public Vector2 size; + public int indexInGrid; + public int rowIndex; + public int columnIndex; + } + + /// + /// 计算所有项目的布局 + /// + /// 子项数量 + /// 所需尺寸 + /// 单元尺寸 + /// 单元空隙 + /// 外围空隙 + /// 起始角 + /// 起始轴 + /// 容器尺寸 + /// 需要的列数 + /// 子项对齐 + /// + public static List CalculateGridLayout( + int childCount, + Vector2 containerSize, + Vector2 cellSize, + Vector2 spacing, + RectOffset padding, + GridLayoutGroup.Corner startCorner, + GridLayoutGroup.Axis startAxis, + GridLayoutGroup.Constraint constraint, + int constraintCount, + TextAnchor childAlignment = TextAnchor.UpperLeft) + { + List layoutItems = new List(childCount); + + // 1. 计算行列数 + int cellCountX, cellCountY; + CalculateGridDimensions(childCount, containerSize, cellSize, spacing, padding, + constraint, constraintCount, startAxis, out cellCountX, out cellCountY); + + // 2. 计算实际使用的行列数 + int actualCellCountX, actualCellCountY; + CalculateActualGridDimensions(childCount, cellCountX, cellCountY, constraint, constraintCount, + startAxis, out actualCellCountX, out actualCellCountY); + + // 3. 计算所需空间和起始偏移 + Vector2 requiredSpace = new Vector2( + actualCellCountX * cellSize.x + (actualCellCountX - 1) * spacing.x, + actualCellCountY * cellSize.y + (actualCellCountY - 1) * spacing.y + ); + + Vector2 startOffset = new Vector2( + GetStartOffset(0, requiredSpace.x, containerSize.x, padding, childAlignment), + GetStartOffset(1, requiredSpace.y, containerSize.y, padding, childAlignment) + ); + + // 4. 处理特殊情况(约束条件下的子项重新排列) + int childrenToMove = 0; + if (childCount > constraintCount && constraint != GridLayoutGroup.Constraint.Flexible) + { + int cellsPerMainAxis = startAxis == GridLayoutGroup.Axis.Horizontal ? cellCountX : cellCountY; + if (Mathf.CeilToInt((float)childCount / (float)cellsPerMainAxis) < constraintCount) + { + childrenToMove = constraintCount - Mathf.CeilToInt((float)childCount / (float)cellsPerMainAxis); + childrenToMove += Mathf.FloorToInt((float)childrenToMove / ((float)cellsPerMainAxis - 1)); + if (childCount % cellsPerMainAxis == 1) + childrenToMove += 1; + } + } + + // 5. 计算每个子项的位置 + int cornerX = (int)startCorner % 2; + int cornerY = (int)startCorner / 2; + + for (int i = 0; i < childCount; i++) + { + int positionX, positionY; + CalculateItemPosition(i, childCount, cellCountX, cellCountY, startAxis, constraint, + constraintCount, childrenToMove, cornerX, cornerY, + actualCellCountX, actualCellCountY, out positionX, out positionY); + + Vector2 position = new Vector2( + startOffset.x + (cellSize.x + spacing.x) * positionX, + startOffset.y + (cellSize.y + spacing.y) * positionY + ); + + layoutItems.Add(new LayoutItem + { + position = position, + size = cellSize, + indexInGrid = i, + rowIndex = positionY, + columnIndex = positionX + }); + } + + return layoutItems; + } + + private static void CalculateGridDimensions( + int childCount, + Vector2 containerSize, + Vector2 cellSize, + Vector2 spacing, + RectOffset padding, + GridLayoutGroup.Constraint constraint, + int constraintCount, + GridLayoutGroup.Axis startAxis, + out int cellCountX, + out int cellCountY) + { + cellCountX = 1; + cellCountY = 1; + + if (constraint == GridLayoutGroup.Constraint.FixedColumnCount) + { + cellCountX = constraintCount; + if (childCount > cellCountX) + cellCountY = childCount / cellCountX + (childCount % cellCountX > 0 ? 1 : 0); + } + else if (constraint == GridLayoutGroup.Constraint.FixedRowCount) + { + cellCountY = constraintCount; + if (childCount > cellCountY) + cellCountX = childCount / cellCountY + (childCount % cellCountY > 0 ? 1 : 0); + } + else + { + // Flexible constraint - based on container size + float availableWidth = containerSize.x - padding.horizontal; + float availableHeight = containerSize.y - padding.vertical; + + if (cellSize.x + spacing.x > 0) + cellCountX = Mathf.Max(1, + Mathf.FloorToInt((availableWidth + spacing.x + 0.001f) / (cellSize.x + spacing.x))); + else + cellCountX = int.MaxValue; + + if (cellSize.y + spacing.y > 0) + cellCountY = Mathf.Max(1, + Mathf.FloorToInt((availableHeight + spacing.y + 0.001f) / (cellSize.y + spacing.y))); + else + cellCountY = int.MaxValue; + } + } + + private static void CalculateActualGridDimensions( + int childCount, + int cellCountX, + int cellCountY, + GridLayoutGroup.Constraint constraint, + int constraintCount, + GridLayoutGroup.Axis startAxis, + out int actualCellCountX, + out int actualCellCountY) + { + if (startAxis == GridLayoutGroup.Axis.Horizontal) + { + actualCellCountX = Mathf.Clamp(cellCountX, 1, childCount); + + if (constraint == GridLayoutGroup.Constraint.FixedRowCount) + actualCellCountY = Mathf.Min(cellCountY, childCount); + else + actualCellCountY = Mathf.Clamp(cellCountY, 1, Mathf.CeilToInt(childCount / (float)cellCountX)); + } + else + { + actualCellCountY = Mathf.Clamp(cellCountY, 1, childCount); + + if (constraint == GridLayoutGroup.Constraint.FixedColumnCount) + actualCellCountX = Mathf.Min(cellCountX, childCount); + else + actualCellCountX = Mathf.Clamp(cellCountX, 1, Mathf.CeilToInt(childCount / (float)cellCountY)); + } + } + + private static void CalculateItemPosition( + int index, + int childCount, + int cellCountX, + int cellCountY, + GridLayoutGroup.Axis startAxis, + GridLayoutGroup.Constraint constraint, + int constraintCount, + int childrenToMove, + int cornerX, + int cornerY, + int actualCellCountX, + int actualCellCountY, + out int positionX, + out int positionY) + { + if (startAxis == GridLayoutGroup.Axis.Horizontal) + { + if (constraint == GridLayoutGroup.Constraint.FixedRowCount && childCount - index <= childrenToMove) + { + positionX = 0; + positionY = constraintCount - (childCount - index); + } + else + { + positionX = index % cellCountX; + positionY = index / cellCountX; + } + } + else + { + if (constraint == GridLayoutGroup.Constraint.FixedColumnCount && childCount - index <= childrenToMove) + { + positionX = constraintCount - (childCount - index); + positionY = 0; + } + else + { + positionX = index / cellCountY; + positionY = index % cellCountY; + } + } + + // Apply corner flipping + if (cornerX == 1) + positionX = actualCellCountX - 1 - positionX; + if (cornerY == 1) + positionY = actualCellCountY - 1 - positionY; + } + + private static float GetStartOffset(int axis, float requiredSpace, float availableSpace, + RectOffset padding, TextAnchor childAlignment) + { + float requiredSpaceWithPadding = requiredSpace + (axis == 0 ? padding.horizontal : padding.vertical); + float surplusSpace = availableSpace - requiredSpaceWithPadding; + float alignmentOnAxis = GetAlignmentOnAxis(axis, childAlignment); + + return (axis == 0 ? padding.left : padding.top) + surplusSpace * alignmentOnAxis; + } + + private static float GetAlignmentOnAxis(int axis, TextAnchor childAlignment) + { + if (axis == 0) + return ((int)childAlignment % 3) * 0.5f; + else + return ((int)childAlignment / 3) * 0.5f; + } + } +} \ No newline at end of file diff --git a/Runtime/Helper/GridLayoutCalculator.cs.meta b/Runtime/Helper/GridLayoutCalculator.cs.meta new file mode 100644 index 0000000..58fd450 --- /dev/null +++ b/Runtime/Helper/GridLayoutCalculator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4f08db3ea0984233a1e58bbea59a576e +timeCreated: 1776324158 \ No newline at end of file diff --git a/Runtime/Helper/UIVesselHelp.cs b/Runtime/Helper/UIVesselHelp.cs new file mode 100644 index 0000000..8308037 --- /dev/null +++ b/Runtime/Helper/UIVesselHelp.cs @@ -0,0 +1,19 @@ +using XericUI.Core.Interface; + +namespace XericUI.Helper +{ + public static class UIVesselHelper + { + public static void CallVisibal(this IUIVesselVisibalControl control, bool underVisible) + { + if (underVisible) + { + control.UnderVisibal(); + } + else + { + control.OutOfVisibal(); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Helper/UIVesselHelp.cs.meta b/Runtime/Helper/UIVesselHelp.cs.meta new file mode 100644 index 0000000..b1c2e67 --- /dev/null +++ b/Runtime/Helper/UIVesselHelp.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 05b1a77f026f46a5b6744ad2e8486164 +timeCreated: 1776309745 \ No newline at end of file diff --git a/Runtime/OverrideLayout/XericScrollRect.cs b/Runtime/OverrideLayout/XericScrollRect.cs index 5b699e6..8228d56 100644 --- a/Runtime/OverrideLayout/XericScrollRect.cs +++ b/Runtime/OverrideLayout/XericScrollRect.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using UnityEngine; using UnityEngine.Pool; +using UnityEngine.Serialization; using UnityEngine.UI; using XericLibrary.Runtime.MacroLibrary; +using XericUI.Core.Interface; using XericUI.Helper; #if UNITY_EDITOR @@ -23,58 +25,95 @@ namespace XericUI.OverrideLayout [RequireComponent(typeof(RectTransform))] public class XericScrollRect : ScrollRect { - #region 事件委托 - - // public Action - - #endregion - #region 字段属性 - /// - /// 启用自动剔除功能 - /// - public bool enableAutoActiveByMaskVisible = true; + [Tooltip("启用自动剔除功能")] [SerializeField] + protected bool m_enableAdvancedFunc = true; + + [Tooltip("容器预加载像素范围")] [SerializeField] + protected float m_preloadMargin = 200f; + + [Tooltip("容器加载响应可被察觉的变化长度")] [SerializeField] + protected float m_discernibleDistance = 10f; + + [Tooltip("自动剔除更新速率,当设定趋近零时,实际上是期望每帧执行更新")] [SerializeField] + protected float m_AAMVUpdateRate = .5f; + + // grid布局组件 + [SerializeField] protected Vector2 m_cellSize = new Vector2(100, 100); + [SerializeField] protected Vector2 m_spacing = Vector2.zero; + [SerializeField] protected RectOffset m_padding; + [SerializeField] protected GridLayoutGroup.Corner m_startCorner = GridLayoutGroup.Corner.UpperLeft; + [SerializeField] protected GridLayoutGroup.Axis m_startAxis = GridLayoutGroup.Axis.Horizontal; + [SerializeField] protected TextAnchor m_childAlignment = TextAnchor.UpperLeft; + [SerializeField] protected GridLayoutGroup.Constraint m_constraint = GridLayoutGroup.Constraint.Flexible; /// - /// 容器布局 + /// 对象池 /// - public LayoutGroup layoutGroup; + [SerializeField] protected MacroPool.UnionSet childrenPool = new MacroPool.UnionSet(); /// - /// 容器预加载像素范围 + /// 对象数据 /// - public float preloadMargin = 200f; - - /// - /// 容器加载响应可被察觉的变化长度 - /// - public float discernibleDistance = 10f; - - /// - /// 自动剔除更新速率 - /// - [Tooltip("当设定满速率时,实际上是期望每帧执行更新")] - // ReSharper disable once InconsistentNaming - [Range(0.02f, 1)] - public float AAMVUpdateRate = .9f; + [SerializeField] private readonly List _childrenDatas = ListPool.Get(); + public Vector2 ContextSize + { + get => new Vector2(content.rect.width, content.rect.height); + set + { + UpdateContentLayout(0, value.x); + UpdateContentLayout(1, value.y); + } + } + #endregion #region 布局参考 + [SerializeField] private LayoutGroup layoutGroup; + private float _aamvUpdateTime; private Vector2 _lastContextSize; private Vector2 _lastContextAnchoredPosition; - // 脏标记处理率,每次激活脏标记时给予多份权重,每次空处理则返还一部分权重,如果出现标记大于0的情况,说明脏标记处理次数太多 - private float _dirtyTakeRate; + private Queue _activeQueue = new Queue(); - private HashSet _lastActiveSet; - #endregion + [Serializable] + public class ScrollItemData : IUIVesselVisibalControl + { + public GridLayoutCalculator.LayoutItem layout; + + protected internal XericScrollRect parentScrollRect; + private bool _uiVisibal; + + protected GameObject item; + + public bool GetUIVisibal => _uiVisibal; + + public virtual void UnderVisibal() + { + item = parentScrollRect.childrenPool.Get(); + var rectTransform = item.RectTransform(); + parentScrollRect.UpdateChildPivot(layout, item.RectTransform()); + rectTransform.anchoredPosition = layout.position; + } + + public virtual void OutOfVisibal() + { + parentScrollRect.childrenPool.DirectlyRelease(item); + } + + public void UpdateLayout(GridLayoutCalculator.LayoutItem layoutData) + { + layout = layoutData; + } + } + #region 生命周期 #if UNITY_EDITOR @@ -84,34 +123,57 @@ namespace XericUI.OverrideLayout private void OnDrawGizmosSelected() { var lossyScale = transform.lossyScale; - var scale = lossyScale * preloadMargin; - var dis2 = lossyScale * discernibleDistance; - + var scale = lossyScale * m_preloadMargin; + var dis2 = lossyScale * m_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); - + + vertex3[0] = new Vector3(horizontal ? rect.min.x - scale.x : rect.min.x, + vertical ? rect.min.y - scale.y : rect.min.y, vertex2[0].z); + vertex3[1] = new Vector3(horizontal ? rect.min.x - scale.x : rect.min.x, + vertical ? rect.max.y + scale.y : rect.max.y, vertex2[1].z); + vertex3[2] = new Vector3(horizontal ? rect.max.x + scale.x : rect.max.x, + vertical ? rect.max.y + scale.y : rect.max.y, vertex2[2].z); + vertex3[3] = new Vector3(horizontal ? rect.max.x + scale.x : rect.max.x, + vertical ? rect.min.y - scale.y : rect.min.y, vertex2[3].z); + Gizmos.color = Color.yellow; - MacroGizmosDraw.DrawGizmos2RectLinkCube(vertex1, vertex3, Color.grey, Color.cyan, true); - + MacroGizmosDraw.DrawGizmos2RectLinkCube(vertex3, vertex1, Color.cyan, Color.red, true); + MacroGizmosDraw.Label(vertex3[1], "preload Rect"); + MacroGizmosDraw.Label(vertex1[1], "display Rect"); } #endif + protected override void OnValidate() + { + base.OnValidate(); + if (childrenPool.Parent == null) + childrenPool.Parent = content; + + var layouts = GetChildrenLayout(content.transform.childCount); + for (int i = 0; i < layouts.Count; i++) + { + var trans = content.transform.GetChild(i) as RectTransform; + UpdateChildPivot(layouts[i], trans); + trans.anchoredPosition = layouts[i].position; + } + + RefreshContentSizeByChildrenLayouts(layouts); + } + protected override void Awake() { base.Awake(); - + // 布局 if (layoutGroup == null) layoutGroup = transform.GetComponentInParent(); @@ -121,113 +183,244 @@ namespace XericUI.OverrideLayout } } - protected override void LateUpdate() + protected override void Start() { - if (!enableAutoActiveByMaskVisible) - { - base.LateUpdate(); - return; - } + base.Start(); + childrenPool.Initialize(); + } + + protected override void SetContentAnchoredPosition(Vector2 position) + { + base.SetContentAnchoredPosition(position); + // 如果不启动就直接跳过 + if (!m_enableAdvancedFunc) return; // 计算更新速率 - var targetFrameRate = Application.targetFrameRate; - var frameAamvRate = Time.deltaTime / Mathf.Max(0.02f, AAMVUpdateRate); - _dirtyTakeRate = Mathf.Max(_dirtyTakeRate - AAMVUpdateRate, 0); - if (AAMVUpdateRate < 1) + if (m_AAMVUpdateRate <= 0) + Next(); + else if ((_aamvUpdateTime += Time.deltaTime) >= m_AAMVUpdateRate) { - if (_aamvUpdateTime < frameAamvRate) + // var targetFrameRate = Application.targetFrameRate; + _aamvUpdateTime %= m_AAMVUpdateRate; + Next(); + } + + + void Next() + { + // 记录移动矢量 + var vector = position - _lastContextAnchoredPosition; + + // 移动矢量距离大于可查觉距离 + if (vector.sqrMagnitude > m_discernibleDistance * m_discernibleDistance) { - _aamvUpdateTime += Time.deltaTime; - base.LateUpdate(); - return; + // 只有距离大于这个才会触发更新 + _lastContextAnchoredPosition = position; + UpdateAAMVSVisibleStatus(position); } - _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]; + /// /// 自动剔除更新标记 /// - protected void SetAAMVDirty() + protected void UpdateAAMVSVisibleStatus(Vector2 position) { - _dirtyTakeRate += AAMVUpdateRate * 2; - - var nowActiveChildrenSet = HashSetPool.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 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 visibleMin = viewportRect.min - new Vector2(m_preloadMargin, m_preloadMargin); + var visibleMax = viewportRect.max + new Vector2(m_preloadMargin, m_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 scrollPos = new Vector2( + // horizontal ? horizontalScrollbar?.value ?? 0 : 0, + // vertical ? 1 - (verticalScrollbar?.value ?? 1) : 0); + + // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop + foreach (RectTransform child in content.GetChildren()) { + var visControl = child.GetComponent(); + var isVisControlValid = visControl != null; + var rectTransform = child.RectTransform(); // 直接使用anchoredPosition进行边界检测 var childPos = rectTransform.anchoredPosition; - var isVisible = - childPos.x >= visibleMin.x && + var isVisible = + childPos.x >= visibleMin.x && childPos.x <= visibleMax.x && - childPos.y >= visibleMin.y && + childPos.y >= visibleMin.y && childPos.y <= visibleMax.y; - var isActive = isinit || _lastActiveSet.Contains(child); - if (isVisible) - nowActiveChildrenSet.Add(child); - if (isVisible == isActive) + var nowActive = child.GetActivity(); + + // 以接口为准 + if (isVisControlValid) + nowActive = visControl.GetUIVisibal; + + // 现在的状态与实际一致,跳过 + if (isVisible == nowActive) continue; - - child.gameObject.SetActive(isVisible); + + // 向成员发送状态(如果成员没有实现接口,就直接通过可见性控制) + if (isVisControlValid) + visControl.CallVisibal(isVisible); + else + child.gameObject.SetActive(isVisible); } - - if (!isinit) - HashSetPool.Release(_lastActiveSet); - _lastActiveSet = nowActiveChildrenSet; } - - protected override void SetContentAnchoredPosition(Vector2 position) - { - base.SetContentAnchoredPosition(position); - Debug.Log("123"); - } - #endregion + + #region 数据管理 + + public T GetScrollItemData() + where T : ScrollItemData, new() + { + var result = new T + { + parentScrollRect = this + }; + return result; + } + + public void AddChildData(ScrollItemData Data) + { + _childrenDatas.Add(Data); + } + + public void RemoveChildData(int index) + { + } + + public void RemoveChildData(ScrollItemData index) + { + } + + public ScrollItemData GetChildData(int index) + { + return _childrenDatas[index]; + } + + #endregion + + #region 布局计算 + + private void UpdateContentLayout(int axis, float overrideSize) + { + content.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, overrideSize); + // m_HorizontalFit == ContentSizeFitter.FitMode.MinSize ? + // LayoutUtility.GetMinSize(content, axis) : + // LayoutUtility.GetPreferredSize(content, axis)); + } + + private void RefreshContentSizeByChildrenLayouts(List layoutItems) + { + var first = layoutItems[0]; + var last = layoutItems[^1]; + + var size = first.position - last.position; + size = size.Abs(); + var spacing = new Vector2(first.columnIndex - last.columnIndex * m_spacing.x, first.rowIndex - last.rowIndex * m_spacing.y); + switch (m_startAxis) + { + case GridLayoutGroup.Axis.Horizontal: + var ySpacing = first.rowIndex - last.rowIndex * m_spacing.y; + size.x = Mathf.Max(size.x, viewport.rect.width); + size.y = Mathf.Max(size.y + spacing.y, m_cellSize.y); + break; + case GridLayoutGroup.Axis.Vertical: + var xSpacing = last.columnIndex * m_spacing.x; + size.x = Mathf.Max(size.x + spacing.x, m_cellSize.x); + size.y = Mathf.Max(size.y, viewport.rect.height); + break; + } + ContextSize = size; + } + + internal void UpdateChildPivot(GridLayoutCalculator.LayoutItem layoutItems, RectTransform rectTransform) + { + rectTransform.anchorMax = rectTransform.anchorMin = m_startCorner switch + { + // todo 对齐算法总的方向应该是写错了,找找 + GridLayoutGroup.Corner.UpperLeft => new Vector2(0, 1), + GridLayoutGroup.Corner.UpperRight => new Vector2(1, 1), + GridLayoutGroup.Corner.LowerLeft => new Vector2(0, 0), + GridLayoutGroup.Corner.LowerRight => new Vector2(1, 0), + _ => throw new ArgumentOutOfRangeException() + }; + // rectTransform.pivot = m_childAlignment switch + // { + // TextAnchor.UpperLeft => new Vector2(0, 1), + // TextAnchor.UpperCenter => new Vector2(.5f, 1), + // TextAnchor.UpperRight => new Vector2(1, 1), + // TextAnchor.MiddleLeft => new Vector2(0, .5f), + // TextAnchor.MiddleCenter => new Vector2(.5f, .5f), + // TextAnchor.MiddleRight => new Vector2(1, .5f), + // TextAnchor.LowerLeft => new Vector2(0, 0), + // TextAnchor.LowerCenter => new Vector2(.5f, 0), + // TextAnchor.LowerRight => new Vector2(1, 0), + // _ => throw new ArgumentOutOfRangeException() + // }; + } + + private void GetOffset(List layoutItems) + { + var first = layoutItems[0]; + var last = layoutItems[^1]; + + // first.position + } + private List GetChildrenLayout(int childrenCount) + => GridLayoutCalculator.CalculateGridLayout( + childCount: childrenCount, + containerSize: ContextSize, + cellSize: m_cellSize, + spacing: m_spacing, + padding: m_padding, + startCorner: m_startCorner, + startAxis: m_startAxis, + constraint: m_constraint, + constraintCount: 0, + childAlignment: m_childAlignment + ); + + public void RefreshChildrenLayout() + { + var layoutItems = GetChildrenLayout(_childrenDatas.Count); + RefreshContentSizeByChildrenLayouts(layoutItems); + // 应用计算结果 + for (int i = 0; i < layoutItems.Count; i++) + { + var item = layoutItems[i]; + _childrenDatas[i].UpdateLayout(item); + Debug.Log($"Item {i}: Position{item.position}, Row{item.rowIndex}, Column{item.columnIndex}"); + } + } + + #endregion + #region 组件替换 + #if UNITY_EDITOR [UnityEditor.MenuItem("CONTEXT/ScrollRect/Replace To XericScrollView", true)] private static bool ValidateReplaceToXeric(UnityEditor.MenuCommand command) => command.context is ScrollRect and not XericScrollRect; [UnityEditor.MenuItem("CONTEXT/ScrollRect/Replace To XericScrollView", false, 10)] - private static void ReplaceToXeric(UnityEditor.MenuCommand command) + private static void ReplaceToXeric(UnityEditor.MenuCommand command) => command.ReplaceCommandContext( o => @@ -245,7 +438,7 @@ namespace XericUI.OverrideLayout => command.context is XericScrollRect; [UnityEditor.MenuItem("CONTEXT/ScrollRect/Replace To ScrollView", false, 10)] - private static void ReplaceToUnity(UnityEditor.MenuCommand command) + private static void ReplaceToUnity(UnityEditor.MenuCommand command) => command.ReplaceCommandContext( o => @@ -258,6 +451,7 @@ namespace XericUI.OverrideLayout t.verticalScrollbar = d.VScrollbar; }); #endif + #endregion } } \ No newline at end of file