xericScrollRect组件中添加了网格布局和尺寸计算功能

This commit is contained in:
2026-04-17 12:25:31 +08:00
parent 60d5822a82
commit 21a09edad2
10 changed files with 652 additions and 120 deletions
+37 -8
View File
@@ -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();
}
}
+6 -5
View File
@@ -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);
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f441117a49ca43f4a6bf105e18562875
timeCreated: 1776309301
@@ -0,0 +1,22 @@
namespace XericUI.Core.Interface
{
/// <summary>
/// Xeric UI Vessel容器控制接口
/// </summary>
public interface IUIVesselVisibalControl
{
/// <summary>
/// 获取当前元素的可见性
/// </summary>
public bool GetUIVisibal { get; }
/// <summary>
/// 当前元素处于可见范围时(预载范围)
/// </summary>
public void UnderVisibal();
/// <summary>
/// 当前元素离开可见范围时(预载范围)
/// </summary>
public void OutOfVisibal();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 055f74c72fcd49e5a99faea1af98ebc1
timeCreated: 1776309332
+255
View File
@@ -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;
}
/// <summary>
/// 计算所有项目的布局
/// </summary>
/// <param name="childCount">子项数量</param>
/// <param name="containerSize">所需尺寸</param>
/// <param name="cellSize">单元尺寸</param>
/// <param name="spacing">单元空隙</param>
/// <param name="padding">外围空隙</param>
/// <param name="startCorner">起始角</param>
/// <param name="startAxis">起始轴</param>
/// <param name="constraint">容器尺寸</param>
/// <param name="constraintCount">需要的列数</param>
/// <param name="childAlignment">子项对齐</param>
/// <returns></returns>
public static List<LayoutItem> 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<LayoutItem> layoutItems = new List<LayoutItem>(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;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4f08db3ea0984233a1e58bbea59a576e
timeCreated: 1776324158
+19
View File
@@ -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();
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 05b1a77f026f46a5b6744ad2e8486164
timeCreated: 1776309745
+301 -107
View File
@@ -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 字段属性
/// <summary>
/// 启用自动剔除功能
/// </summary>
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;
/// <summary>
/// 容器布局
/// 对象池
/// </summary>
public LayoutGroup layoutGroup;
[SerializeField] protected MacroPool.UnionSet childrenPool = new MacroPool.UnionSet();
/// <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;
[SerializeField] private readonly List<ScrollItemData> _childrenDatas = ListPool<ScrollItemData>.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<RectTransform> _activeQueue = new Queue<RectTransform>();
private HashSet<Transform> _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<LayoutGroup>();
@@ -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];
/// <summary>
/// 自动剔除更新标记
/// </summary>
protected void SetAAMVDirty()
protected void UpdateAAMVSVisibleStatus(Vector2 position)
{
_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 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<IUIVesselVisibalControl>();
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<Transform>.Release(_lastActiveSet);
_lastActiveSet = nowActiveChildrenSet;
}
protected override void SetContentAnchoredPosition(Vector2 position)
{
base.SetContentAnchoredPosition(position);
Debug.Log("123");
}
#endregion
#region 数据管理
public T GetScrollItemData<T>()
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<GridLayoutCalculator.LayoutItem> 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<GridLayoutCalculator.LayoutItem> layoutItems)
{
var first = layoutItems[0];
var last = layoutItems[^1];
// first.position
}
private List<GridLayoutCalculator.LayoutItem> 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<ScrollRect, XericScrollRect,
(RectTransform Viewport, RectTransform Content, Scrollbar HScrollbar, Scrollbar VScrollbar)>(
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<XericScrollRect, ScrollRect,
(RectTransform Viewport, RectTransform Content, Scrollbar HScrollbar, Scrollbar VScrollbar)>(
o =>
@@ -258,6 +451,7 @@ namespace XericUI.OverrideLayout
t.verticalScrollbar = d.VScrollbar;
});
#endif
#endregion
}
}