xericScrollRect组件中添加了网格布局和尺寸计算功能
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05b1a77f026f46a5b6744ad2e8486164
|
||||
timeCreated: 1776309745
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user