From 26c205ddc0ba7167abd365b425d286a20c94115a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Fri, 26 Dec 2025 14:22:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Editor/RecyclerView/RecyclerViewEditor.cs | 1219 ++++++++++------- Editor/RecyclerView/Res/ScrollView.prefab | 23 +- Runtime/RecyclerView/Adapter/Adapter.cs | 2 +- Runtime/RecyclerView/Adapter/GroupAdapter.cs | 2 +- Runtime/RecyclerView/Data.meta | 4 +- .../RecyclerView/Data/ISimpleViewData.cs.meta | 4 +- Runtime/RecyclerView/EaseUtil.cs.meta | 4 +- .../Layout/AlignableLinearLayoutManager.cs | 48 - .../AlignableLinearLayoutManager.cs.meta | 3 - .../Layout/CircleLayoutManager.cs | 21 +- .../RecyclerView/Layout/GridLayoutManager.cs | 9 +- Runtime/RecyclerView/Layout/LayoutManager.cs | 41 +- .../Layout/LinearLayoutManager.cs | 10 +- .../RecyclerView/Layout/MixedLayoutManager.cs | 2 + .../RecyclerView/Layout/PageLayoutManager.cs | 4 +- Runtime/RecyclerView/ObjectPool.meta | 7 +- .../ObjectPool/IMixedObjectFactory.cs | 7 +- .../ObjectPool/IMixedObjectPool.cs | 4 +- .../RecyclerView/ObjectPool/IObjectFactory.cs | 7 +- .../RecyclerView/ObjectPool/IObjectPool.cs | 6 +- .../RecyclerView/ObjectPool/IPooledObject.cs | 7 +- .../ObjectPool/MixedObjectPool.cs | 4 +- Runtime/RecyclerView/ObjectPool/ObjectPool.cs | 4 +- .../ObjectPool/UnityComponentFactory.cs | 4 +- .../ObjectPool/UnityGameObjectFactory.cs | 4 +- .../ObjectPool/UnityMixedComponentFactory.cs | 4 +- .../ObjectPool/UnityMixedGameObjectFactory.cs | 5 +- Runtime/RecyclerView/RecyclerView.cs | 709 ++++++---- Runtime/RecyclerView/RecyclerView.cs.meta | 2 +- .../RecyclerView/Scroller/CircleScroller.cs | 2 - Runtime/RecyclerView/Scroller/IScroller.cs | 18 +- Runtime/RecyclerView/Scroller/Scroller.cs | 58 +- .../{UGList/UGListBase.cs => UGList.cs} | 2 +- Runtime/RecyclerView/UGList.cs.meta | 3 + Runtime/RecyclerView/UGList.meta | 3 - .../RecyclerView/UGList/UGListBase.cs.meta | 3 - .../ViewProvider/MixedViewProvider.cs | 1 - .../ViewProvider/SimpleViewProvider.cs | 1 - .../RecyclerView/ViewProvider/ViewProvider.cs | 16 +- 39 files changed, 1273 insertions(+), 1004 deletions(-) delete mode 100644 Runtime/RecyclerView/Layout/AlignableLinearLayoutManager.cs delete mode 100644 Runtime/RecyclerView/Layout/AlignableLinearLayoutManager.cs.meta rename Runtime/RecyclerView/{UGList/UGListBase.cs => UGList.cs} (98%) create mode 100644 Runtime/RecyclerView/UGList.cs.meta delete mode 100644 Runtime/RecyclerView/UGList.meta delete mode 100644 Runtime/RecyclerView/UGList/UGListBase.cs.meta diff --git a/Editor/RecyclerView/RecyclerViewEditor.cs b/Editor/RecyclerView/RecyclerViewEditor.cs index 0ac3e60..d8d886f 100644 --- a/Editor/RecyclerView/RecyclerViewEditor.cs +++ b/Editor/RecyclerView/RecyclerViewEditor.cs @@ -1,590 +1,841 @@ -using UnityEditor; -using UnityEngine; using System; using System.Collections.Generic; using System.Linq; using AlicizaX.UI; +using UnityEditor; +using UnityEngine; using UnityEngine.UI; using Object = UnityEngine.Object; -[CustomEditor(typeof(RecyclerView))] -public class RecyclerViewEditor : Editor +namespace AlicizaX.UI.Editor { - // Layout Manager - private SerializedProperty _layoutManagerTypeName; - private SerializedProperty _layoutManager; - private List _layoutTypeNames = new List(); - private int _selectedLayoutIndex; - - // Scroller - private SerializedProperty scroll; - private SerializedProperty _scroller; - private SerializedProperty _scrollerTypeName; - private List _scrollerTypes = new List(); - private List _scrollerTypeNames; - private int _selectedScrollerIndex; - - // Base Properties - private SerializedProperty direction; - private SerializedProperty alignment; - private SerializedProperty content; - private SerializedProperty spacing; - private SerializedProperty padding; - private SerializedProperty snap; - private SerializedProperty scrollSpeed; - private SerializedProperty wheelSpeed; - private SerializedProperty templates; - - private SerializedProperty _showScrollBar; - private SerializedProperty _scrollbar; - - private const string NoneOptionName = "None"; - - private void OnEnable() + [CustomEditor(typeof(RecyclerView))] + public class RecyclerViewEditor : UnityEditor.Editor { - // Layout Manager - _layoutManagerTypeName = serializedObject.FindProperty("_layoutManagerTypeName"); - _layoutManager = serializedObject.FindProperty("_layoutManager"); - RefreshLayoutTypes(); + #region Constants - // Scroller - scroll = serializedObject.FindProperty("scroll"); - _scroller = serializedObject.FindProperty("_scroller"); - _scrollerTypeName = serializedObject.FindProperty("_scrollerTypeName"); - RefreshScrollerTypes(); - SyncExistingScroller(); + private const string NoneOptionName = "None"; + private const string VerticalScrollbarPath = "Packages/com.alicizax.unity.ui.extension/Editor/RecyclerView/Res/vertical.prefab"; + private const string HorizontalScrollbarPath = "Packages/com.alicizax.unity.ui.extension/Editor/RecyclerView/Res/horizontal.prefab"; - // Base Properties - direction = serializedObject.FindProperty("direction"); - alignment = serializedObject.FindProperty("alignment"); - content = serializedObject.FindProperty("content"); - spacing = serializedObject.FindProperty("spacing"); - padding = serializedObject.FindProperty("padding"); - snap = serializedObject.FindProperty("snap"); - scrollSpeed = serializedObject.FindProperty("scrollSpeed"); - wheelSpeed = serializedObject.FindProperty("wheelSpeed"); - templates = serializedObject.FindProperty("templates"); - _showScrollBar = serializedObject.FindProperty("_showScrollBar"); - _scrollbar = serializedObject.FindProperty("_scrollbar"); - } + #endregion - #region Layout Manager + #region Serialized Properties - Layout Manager - void RefreshLayoutTypes() - { - _layoutTypeNames.Clear(); - _layoutTypeNames.Add(NoneOptionName); + private SerializedProperty _layoutManagerTypeName; + private SerializedProperty _layoutManager; + private List _layoutTypeNames = new List(); + private int _selectedLayoutIndex; - // 获取所有实现ILayoutManager的非Mono类型 - var types = AlicizaX.Utility.Assembly.GetRuntimeTypes(typeof(ILayoutManager)); - foreach (var type in types) + #endregion + + #region Serialized Properties - Scroller + + private SerializedProperty _scroll; + private SerializedProperty _scroller; + private SerializedProperty _scrollerTypeName; + private List _scrollerTypes = new List(); + private List _scrollerTypeNames = new List(); + private int _selectedScrollerIndex; + + #endregion + + #region Serialized Properties - Base Settings + + private SerializedProperty _direction; + private SerializedProperty _alignment; + private SerializedProperty _content; + private SerializedProperty _spacing; + private SerializedProperty _padding; + private SerializedProperty _snap; + private SerializedProperty _scrollSpeed; + private SerializedProperty _wheelSpeed; + + #endregion + + #region Serialized Properties - Templates & Scrollbar + + private SerializedProperty _templates; + private SerializedProperty _showScrollBar; + private SerializedProperty _scrollbar; + + #endregion + + #region Unity Lifecycle + + private void OnEnable() { - if (!typeof(MonoBehaviour).IsAssignableFrom(type)) - { - _layoutTypeNames.Add(type.FullName); - } + // 先绑定所有 SerializedProperty + InitializeLayoutManagerProperties(); + InitializeScrollerProperties(); + InitializeBaseProperties(); + InitializeTemplateProperties(); + + // 确保序列化对象是最新的 + serializedObject.Update(); + + // 如果 layoutManager 的 managedReferenceValue 丢失但有记录的 typeName,则尝试恢复实例 + RestoreLayoutManagerFromTypeNameIfMissing(); + + // 如果 scroller 组件丢失但有记录的 typeName,则尝试恢复组件到目标 GameObject 上 + RestoreScrollerFromTypeNameIfMissing(); + + // 应用修改(若有) + serializedObject.ApplyModifiedProperties(); } - _selectedLayoutIndex = Mathf.Clamp( - _layoutTypeNames.IndexOf(_layoutManagerTypeName.stringValue), - 0, - _layoutTypeNames.Count - 1 - ); - } + #endregion - #endregion + #region Initialization - #region Scroller - - void RefreshScrollerTypes() - { - _scrollerTypes = TypeCache.GetTypesDerivedFrom() - .Where(t => t.IsSubclassOf(typeof(MonoBehaviour))) - .ToList(); - - _scrollerTypeNames = _scrollerTypes - .Select(t => t.FullName) - .Prepend(NoneOptionName) - .ToList(); - } - - void SyncExistingScroller() - { - var rv = target as RecyclerView; - if (rv == null) return; - - var existing = rv.GetComponent(); - if (existing != null) + private void InitializeLayoutManagerProperties() { - _scrollerTypeName.stringValue = existing.GetType().FullName; - _selectedScrollerIndex = _scrollerTypeNames.IndexOf(_scrollerTypeName.stringValue); + _layoutManagerTypeName = serializedObject.FindProperty("_layoutManagerTypeName"); + _layoutManager = serializedObject.FindProperty("layoutManager"); + RefreshLayoutTypes(); } - else + + private void InitializeScrollerProperties() { - _selectedScrollerIndex = 0; + _scroll = serializedObject.FindProperty("scroll"); + _scroller = serializedObject.FindProperty("scroller"); + _scrollerTypeName = serializedObject.FindProperty("_scrollerTypeName"); + RefreshScrollerTypes(); + SyncExistingScroller(); } - } - #endregion - - public override void OnInspectorGUI() - { - serializedObject.Update(); - bool isPlaying = Application.isPlaying; - - DrawLayoutManagerSection(isPlaying); - DrawBaseSettings(isPlaying); - DrawScrollerSettings(isPlaying); - DrawTemplatesSection(); - - serializedObject.ApplyModifiedProperties(); - } - - #region Drawing Methods - - void DrawLayoutManagerSection(bool isPlaying) - { - EditorGUILayout.BeginVertical("box"); + private void InitializeBaseProperties() { - EditorGUILayout.LabelField("Layout Manager", EditorStyles.boldLabel); - - using (new EditorGUI.DisabledScope(isPlaying)) - { - // 强制允许选择空值 - int newIndex = EditorGUILayout.Popup("Layout Type", _selectedLayoutIndex, _layoutTypeNames.ToArray()); - if (newIndex != _selectedLayoutIndex) - { - _selectedLayoutIndex = newIndex; // 立即更新索引 - UpdateLayoutManager(newIndex); - serializedObject.ApplyModifiedProperties(); - } - } - - // 显示布局属性或警告 - if (_layoutManager.managedReferenceValue != null) - { - EditorGUILayout.Space(3); - DrawManagedProperties(_layoutManager); - } - else - { - EditorGUILayout.HelpBox("Need Choose LayoutManager", MessageType.Error); - } + _direction = serializedObject.FindProperty("direction"); + _alignment = serializedObject.FindProperty("alignment"); + _content = serializedObject.FindProperty("content"); + _spacing = serializedObject.FindProperty("spacing"); + _padding = serializedObject.FindProperty("padding"); + _snap = serializedObject.FindProperty("snap"); + _scrollSpeed = serializedObject.FindProperty("scrollSpeed"); + _wheelSpeed = serializedObject.FindProperty("wheelSpeed"); } - EditorGUILayout.EndVertical(); - } - void DrawBaseSettings(bool isPlaying) - { - EditorGUILayout.BeginVertical("box"); + private void InitializeTemplateProperties() { - EditorGUILayout.LabelField("Base Settings", EditorStyles.boldLabel); + _templates = serializedObject.FindProperty("templates"); + _showScrollBar = serializedObject.FindProperty("showScrollBar"); + _scrollbar = serializedObject.FindProperty("scrollbar"); + } - using (new EditorGUI.DisabledScope(isPlaying)) + #endregion + + #region Inspector GUI + + public override void OnInspectorGUI() + { + serializedObject.Update(); + bool isPlaying = Application.isPlaying; + + DrawLayoutManagerSection(isPlaying); + DrawBaseSettingsSection(isPlaying); + DrawScrollerSection(isPlaying); + DrawTemplatesSection(); + + serializedObject.ApplyModifiedProperties(); + } + + #endregion + + #region Layout Manager Section + + private void DrawLayoutManagerSection(bool isPlaying) + { + EditorGUILayout.BeginVertical("box"); { - EditorGUILayout.PropertyField(direction); - EditorGUILayout.PropertyField(alignment); - EditorGUILayout.PropertyField(content); - } + EditorGUILayout.LabelField("Layout Manager", EditorStyles.boldLabel); - EditorGUILayout.Space(5); - EditorGUILayout.LabelField("Spacing", EditorStyles.boldLabel); - EditorGUILayout.PropertyField(spacing); - EditorGUILayout.PropertyField(padding); - - EditorGUILayout.Space(5); - EditorGUILayout.LabelField("Scrolling", EditorStyles.boldLabel); - - using (new EditorGUI.DisabledScope(isPlaying)) - { - bool prevScrollValue = scroll.boolValue; - EditorGUILayout.PropertyField(scroll); - - if (scroll.boolValue != prevScrollValue) - { - HandleScrollToggle(); - if (!scroll.boolValue) - { - ClearScrollBar(); - } - } - } - - if (scroll.boolValue) - { using (new EditorGUI.DisabledScope(isPlaying)) { - bool prevShowScrollBarValue = _showScrollBar.boolValue; - EditorGUILayout.PropertyField(_showScrollBar); - - if (_showScrollBar.boolValue != prevShowScrollBarValue) + int newIndex = EditorGUILayout.Popup("Layout Type", _selectedLayoutIndex, _layoutTypeNames.ToArray()); + if (newIndex != _selectedLayoutIndex) { - HandleScrollBarToggle(); + _selectedLayoutIndex = newIndex; + UpdateLayoutManager(newIndex); + serializedObject.ApplyModifiedProperties(); } } - } - - EditorGUILayout.PropertyField(snap); - } - EditorGUILayout.EndVertical(); - } - - void DrawScrollerSettings(bool isPlaying) - { - if (!scroll.boolValue) return; - - EditorGUILayout.BeginVertical("box"); - { - EditorGUILayout.LabelField("Scroller Settings", EditorStyles.boldLabel); - - using (new EditorGUI.DisabledScope(isPlaying)) - { - int newIndex = EditorGUILayout.Popup("Scroller Type", _selectedScrollerIndex, _scrollerTypeNames.ToArray()); - if (newIndex != _selectedScrollerIndex) + if (_layoutManager.managedReferenceValue != null) { - UpdateScroller(newIndex); - serializedObject.ApplyModifiedProperties(); - } - } - - var rv = target as RecyclerView; - if (rv != null) - { - var scrollerComponent = rv.GetComponent(); - if (scrollerComponent != null) - { - DrawComponentProperties(scrollerComponent as MonoBehaviour, "Scroller Properties"); + EditorGUILayout.Space(3); + DrawManagedReferenceProperties(_layoutManager); } else { - EditorGUILayout.HelpBox("Must choose to use a Scroller.", MessageType.Error); + EditorGUILayout.HelpBox("Please select a Layout Manager", MessageType.Error); } } - - EditorGUILayout.Space(3); - EditorGUILayout.PropertyField(scrollSpeed); - EditorGUILayout.PropertyField(wheelSpeed); - } - EditorGUILayout.EndVertical(); - } - - void DrawComponentProperties(MonoBehaviour component, string header = null) - { - if (component == null) return; - - EditorGUILayout.Space(3); - if (!string.IsNullOrEmpty(header)) - { - EditorGUILayout.LabelField(header, EditorStyles.boldLabel); + EditorGUILayout.EndVertical(); } - SerializedObject so = new SerializedObject(component); - so.Update(); - - SerializedProperty prop = so.GetIterator(); - bool enterChildren = true; - while (prop.NextVisible(enterChildren)) + private void RefreshLayoutTypes() { - enterChildren = false; - if (prop.name == "m_Script") continue; - EditorGUILayout.PropertyField(prop, true); - } + _layoutTypeNames.Clear(); + _layoutTypeNames.Add(NoneOptionName); - so.ApplyModifiedProperties(); - } - - void DrawTemplatesSection() - { - EditorGUILayout.Space(5); - EditorGUILayout.LabelField("Item Templates", EditorStyles.boldLabel); - - DrawTemplatesList(); - - DrawDragAndDropArea(); - } - - void DrawTemplatesList() - { - if (templates == null || !templates.isArray) return; - - serializedObject.Update(); - - for (int i = 0; i < templates.arraySize; i++) - { - DrawTemplateItem(i); - } - } - - void DrawTemplateItem(int index) - { - if (index < 0 || index >= templates.arraySize) return; - - SerializedProperty item = templates.GetArrayElementAtIndex(index); - - EditorGUILayout.BeginHorizontal(); - { - EditorGUI.BeginDisabledGroup(true); - EditorGUILayout.PropertyField(item, GUIContent.none, true); - EditorGUI.EndDisabledGroup(); - - if (GUILayout.Button("×", GUILayout.Width(20), GUILayout.Height(EditorGUIUtility.singleLineHeight))) + var types = AlicizaX.Utility.Assembly.GetRuntimeTypes(typeof(ILayoutManager)); + foreach (var type in types) { - RemoveTemplateItem(index); - return; - } - } - EditorGUILayout.EndHorizontal(); - } - - void RemoveTemplateItem(int index) - { - if (templates == null || index < 0 || index >= templates.arraySize) return; - - templates.DeleteArrayElementAtIndex(index); - serializedObject.ApplyModifiedProperties(); - GUIUtility.ExitGUI(); - } - - void DrawDragAndDropArea() - { - Rect dropArea = GUILayoutUtility.GetRect(0f, 50f, GUILayout.ExpandWidth(true)); - GUI.Box(dropArea, "Drag Template to here", EditorStyles.helpBox); - - // 处理拖拽事件 - HandleDragAndDrop(dropArea); - } - - void HandleDragAndDrop(Rect dropArea) - { - Event evt = Event.current; - - switch (evt.type) - { - case EventType.DragUpdated: - case EventType.DragPerform: - if (!dropArea.Contains(evt.mousePosition)) - return; - - DragAndDrop.visualMode = DragAndDropVisualMode.Copy; - - if (evt.type == EventType.DragPerform) + if (!typeof(MonoBehaviour).IsAssignableFrom(type)) { - DragAndDrop.AcceptDrag(); - AddTemplatesFromDrag(); - evt.Use(); + _layoutTypeNames.Add(type.FullName); + } + } + + _selectedLayoutIndex = Mathf.Clamp( + _layoutTypeNames.IndexOf(_layoutManagerTypeName.stringValue), + 0, + _layoutTypeNames.Count - 1 + ); + } + + private void UpdateLayoutManager(int selectedIndex) + { + try + { + if (selectedIndex == 0) + { + ClearLayoutManager(); + return; } - break; - } - } + if (!IsValidLayoutIndex(selectedIndex)) + { + Debug.LogError($"Invalid layout index: {selectedIndex}"); + ClearLayoutManager(); + return; + } - void AddTemplatesFromDrag() - { - foreach (UnityEngine.Object draggedObject in DragAndDrop.objectReferences) - { - if (draggedObject is GameObject gameObject && gameObject.GetComponent() != null) - { - AddTemplate(gameObject); + string typeName = _layoutTypeNames[selectedIndex]; + Type type = AlicizaX.Utility.Assembly.GetType(typeName); + + if (type != null && typeof(ILayoutManager).IsAssignableFrom(type)) + { + _layoutManager.managedReferenceValue = Activator.CreateInstance(type); + _layoutManagerTypeName.stringValue = typeName; + _selectedLayoutIndex = selectedIndex; + } + else + { + Debug.LogError($"Invalid layout type: {typeName}"); + ClearLayoutManager(); + } } - else + catch (Exception e) { - Debug.LogWarning("模板必须为GameObject资源 同时挂载继承ViewHolder脚本!"); - } - } - } - - void AddTemplate(GameObject templatePrefab) - { - if (templates == null) return; - - for (int i = 0; i < templates.arraySize; i++) - { - SerializedProperty existingItem = templates.GetArrayElementAtIndex(i); - var refrence = (ViewHolder)existingItem.objectReferenceValue; - if (refrence.GetType() != templatePrefab.GetType()) - { - Debug.LogWarning("列表元素重复项,禁止再次添加!"); - return; + Debug.LogError($"Layout Manager Error: {e.Message}"); + ClearLayoutManager(); } } - templates.arraySize++; - SerializedProperty newItem = templates.GetArrayElementAtIndex(templates.arraySize - 1); - newItem.objectReferenceValue = templatePrefab; - - serializedObject.ApplyModifiedProperties(); - } - - #endregion - - #region Update Handlers - - void UpdateLayoutManager(int selectedIndex) - { - try + private void ClearLayoutManager() { - // 强制清空逻辑 - if (selectedIndex == 0) - { - _layoutManager.managedReferenceValue = null; - _layoutManagerTypeName.stringValue = ""; - _selectedLayoutIndex = 0; // 确保索引同步 - return; - } - - // 有效性检查 - if (selectedIndex < 0 || selectedIndex >= _layoutTypeNames.Count) - { - Debug.LogError($"Invalid layout index: {selectedIndex}"); - _selectedLayoutIndex = 0; - return; - } - - string typeName = _layoutTypeNames[selectedIndex]; - Type type = AlicizaX.Utility.Assembly.GetType(typeName); - - if (type != null && typeof(ILayoutManager).IsAssignableFrom(type)) - { - _layoutManager.managedReferenceValue = Activator.CreateInstance(type); - _layoutManagerTypeName.stringValue = typeName; - _selectedLayoutIndex = selectedIndex; - } - else - { - Debug.LogError($"Invalid layout type: {typeName}"); - _selectedLayoutIndex = 0; - } - } - catch (Exception e) - { - Debug.LogError($"Layout Manager Error: {e.Message}"); _layoutManager.managedReferenceValue = null; _layoutManagerTypeName.stringValue = ""; _selectedLayoutIndex = 0; } - } - void UpdateScroller(int selectedIndex) - { - try + private bool IsValidLayoutIndex(int index) { - var rv = target as RecyclerView; - if (rv == null) return; + return index >= 0 && index < _layoutTypeNames.Count; + } - Undo.RecordObjects(new UnityEngine.Object[] { rv, this }, "Update Scroller"); + #endregion - // 移除旧组件 - var oldScroller = rv.GetComponent(); + #region Base Settings Section + + private void DrawBaseSettingsSection(bool isPlaying) + { + EditorGUILayout.BeginVertical("box"); + { + EditorGUILayout.LabelField("Base Settings", EditorStyles.boldLabel); + + using (new EditorGUI.DisabledScope(isPlaying)) + { + EditorGUILayout.PropertyField(_direction); + EditorGUILayout.PropertyField(_alignment); + EditorGUILayout.PropertyField(_content); + } + + EditorGUILayout.Space(5); + EditorGUILayout.LabelField("Spacing", EditorStyles.boldLabel); + EditorGUILayout.PropertyField(_spacing); + EditorGUILayout.PropertyField(_padding); + + EditorGUILayout.Space(5); + EditorGUILayout.LabelField("Scrolling", EditorStyles.boldLabel); + + using (new EditorGUI.DisabledScope(isPlaying)) + { + bool previousScrollValue = _scroll.boolValue; + EditorGUILayout.PropertyField(_scroll); + + if (_scroll.boolValue != previousScrollValue) + { + HandleScrollToggle(); + } + } + + if (_scroll.boolValue) + { + DrawScrollBarSettings(isPlaying); + } + + EditorGUILayout.PropertyField(_snap); + } + EditorGUILayout.EndVertical(); + } + + private void DrawScrollBarSettings(bool isPlaying) + { + using (new EditorGUI.DisabledScope(isPlaying)) + { + bool previousShowScrollBarValue = _showScrollBar.boolValue; + EditorGUILayout.PropertyField(_showScrollBar); + + if (_showScrollBar.boolValue != previousShowScrollBarValue) + { + HandleScrollBarToggle(); + } + } + } + + #endregion + + #region Scroller Section + + private void DrawScrollerSection(bool isPlaying) + { + if (!_scroll.boolValue) return; + + EditorGUILayout.BeginVertical("box"); + { + EditorGUILayout.LabelField("Scroller Settings", EditorStyles.boldLabel); + + using (new EditorGUI.DisabledScope(isPlaying)) + { + int newIndex = EditorGUILayout.Popup("Scroller Type", _selectedScrollerIndex, _scrollerTypeNames.ToArray()); + if (newIndex != _selectedScrollerIndex) + { + UpdateScroller(newIndex); + serializedObject.ApplyModifiedProperties(); + } + } + + DrawScrollerComponentProperties(); + DrawScrollerSpeedSettings(); + } + EditorGUILayout.EndVertical(); + } + + private void DrawScrollerComponentProperties() + { + var recyclerView = target as RecyclerView; + if (recyclerView == null) return; + + var scrollerComponent = recyclerView.GetComponent(); + if (scrollerComponent != null) + { + DrawComponentProperties(scrollerComponent as MonoBehaviour, "Scroller Properties"); + } + else + { + EditorGUILayout.HelpBox("Please select a Scroller type", MessageType.Error); + } + } + + private void DrawScrollerSpeedSettings() + { + EditorGUILayout.Space(3); + EditorGUILayout.PropertyField(_scrollSpeed); + EditorGUILayout.PropertyField(_wheelSpeed); + } + + private void RefreshScrollerTypes() + { + _scrollerTypes = TypeCache.GetTypesDerivedFrom() + .Where(t => t.IsSubclassOf(typeof(MonoBehaviour))) + .ToList(); + + _scrollerTypeNames = _scrollerTypes + .Select(t => t.FullName) + .Prepend(NoneOptionName) + .ToList(); + } + + private void SyncExistingScroller() + { + var recyclerView = target as RecyclerView; + if (recyclerView == null) return; + + var existingScroller = recyclerView.GetComponent(); + if (existingScroller != null) + { + _scroller.objectReferenceValue = existingScroller as MonoBehaviour; + _scrollerTypeName.stringValue = existingScroller.GetType().FullName; + _selectedScrollerIndex = _scrollerTypeNames.IndexOf(_scrollerTypeName.stringValue); + } + else + { + // 如果组件不存在,但属性里存了类型名,这里不清理 typeName(恢复逻辑会处理) + _selectedScrollerIndex = Mathf.Clamp(_scrollerTypeNames.IndexOf(_scrollerTypeName.stringValue), 0, _scrollerTypeNames.Count - 1); + } + } + + private void UpdateScroller(int selectedIndex) + { + try + { + var recyclerView = target as RecyclerView; + if (recyclerView == null) return; + + Undo.RecordObjects(new Object[] { recyclerView, this }, "Update Scroller"); + + RemoveExistingScroller(recyclerView); + + if (selectedIndex == 0) + { + ClearScrollerReferences(); + return; + } + + AddNewScroller(recyclerView, selectedIndex); + } + catch (Exception e) + { + Debug.LogError($"Scroller Error: {e}"); + ClearScrollerReferences(); + } + } + + private void RemoveExistingScroller(RecyclerView recyclerView) + { + var oldScroller = recyclerView.GetComponent(); if (oldScroller != null) { Undo.DestroyObjectImmediate(oldScroller as MonoBehaviour); } + } - if (selectedIndex == 0) - { - // 清除序列化引用 - _scroller.objectReferenceValue = null; - _scrollerTypeName.stringValue = ""; - return; - } - - // 添加新组件 + private void AddNewScroller(RecyclerView recyclerView, int selectedIndex) + { Type selectedType = _scrollerTypes[selectedIndex - 1]; - var newScroller = Undo.AddComponent(rv.gameObject, selectedType) as IScroller; + var newScroller = Undo.AddComponent(recyclerView.gameObject, selectedType) as IScroller; - // 同步到序列化属性 _scroller.objectReferenceValue = newScroller as MonoBehaviour; _scrollerTypeName.stringValue = selectedType.FullName; _selectedScrollerIndex = selectedIndex; - // 立即应用修改 serializedObject.ApplyModifiedProperties(); - EditorUtility.SetDirty(rv); // 标记对象需要保存 + EditorUtility.SetDirty(recyclerView); } - catch (Exception e) + + private void ClearScrollerReferences() { - Debug.LogError($"Scroller Error: {e}"); - _selectedScrollerIndex = 0; - _scrollerTypeName.stringValue = ""; _scroller.objectReferenceValue = null; + _scrollerTypeName.stringValue = ""; + _selectedScrollerIndex = 0; } - } - void HandleScrollToggle() - { - var rv = target as RecyclerView; - if (rv == null) return; - - if (!scroll.boolValue) + private void HandleScrollToggle() { - // Remove scroller component - var scrollerComponent = rv.GetComponent(); + if (!_scroll.boolValue) + { + RemoveScrollerComponent(); + ClearScrollBar(); + } + } + + private void RemoveScrollerComponent() + { + var recyclerView = target as RecyclerView; + if (recyclerView == null) return; + + var scrollerComponent = recyclerView.GetComponent(); if (scrollerComponent != null) { Undo.DestroyObjectImmediate(scrollerComponent as MonoBehaviour); } - _scrollerTypeName.stringValue = ""; - _selectedScrollerIndex = 0; + ClearScrollerReferences(); } - } - void HandleScrollBarToggle() - { - var rv = target as RecyclerView; - if (rv == null) return; - if (_showScrollBar.boolValue) + #endregion + + #region Scrollbar Handling + + private void HandleScrollBarToggle() { - Direction direction = (Direction)this.direction.enumValueIndex; - if (direction == Direction.Vertical) + if (_showScrollBar.boolValue) { - const string path = "Packages/com.alicizax.unity.ui.extension/Editor/RecyclerView/Res/vertical.prefab"; - InstantiateScrollBar(path, rv.transform); + CreateScrollBar(); } - else if (direction == Direction.Horizontal) + else { - const string path = "Packages/com.alicizax.unity.ui.extension/Editor/RecyclerView/Res/horizontal.prefab"; - InstantiateScrollBar(path, rv.transform); + ClearScrollBar(); } } - else + + private void CreateScrollBar() { - ClearScrollBar(); - } - } + var recyclerView = target as RecyclerView; + if (recyclerView == null) return; - void ClearScrollBar() - { - _showScrollBar.boolValue = false; - if (_scrollbar.objectReferenceValue != null) + Direction direction = (Direction)_direction.enumValueIndex; + string prefabPath = GetScrollbarPrefabPath(direction); + if (!string.IsNullOrEmpty(prefabPath)) + { + InstantiateScrollBar(prefabPath, recyclerView.transform); + } + } + + private string GetScrollbarPrefabPath(Direction direction) { - Scrollbar scrollbar = _scrollbar.objectReferenceValue as Scrollbar; - _scrollbar.objectReferenceValue = null; - Object.DestroyImmediate(scrollbar.gameObject); + + return direction switch + { + Direction.Vertical => VerticalScrollbarPath, + Direction.Horizontal => HorizontalScrollbarPath, + _ => null + }; } - } - #endregion - - void DrawManagedProperties(SerializedProperty property) - { - SerializedProperty iterator = property.Copy(); - bool enterChildren = true; - - while (iterator.NextVisible(enterChildren)) + private void InstantiateScrollBar(string path, Transform parent) { - enterChildren = false; - if (iterator.name == "m_Script") continue; - EditorGUILayout.PropertyField(iterator, true); - } - } + GameObject prefab = AssetDatabase.LoadAssetAtPath(path); + if (prefab == null) + { + Debug.LogError($"Scrollbar prefab not found at path: {path}"); + return; + } - void InstantiateScrollBar(string path, Transform parent) - { - GameObject prefab = AssetDatabase.LoadAssetAtPath(path); - GameObject instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent); - PrefabUtility.UnpackPrefabInstance(instance, PrefabUnpackMode.Completely, InteractionMode.UserAction); - _scrollbar.objectReferenceValue = instance.GetComponent(); + GameObject instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent); + PrefabUtility.UnpackPrefabInstance(instance, PrefabUnpackMode.Completely, InteractionMode.UserAction); + + _scrollbar.objectReferenceValue = instance.GetComponent(); + serializedObject.ApplyModifiedProperties(); + } + + private void ClearScrollBar() + { + _showScrollBar.boolValue = false; + + if (_scrollbar.objectReferenceValue != null) + { + Scrollbar scrollbarComponent = _scrollbar.objectReferenceValue as Scrollbar; + _scrollbar.objectReferenceValue = null; + + if (scrollbarComponent != null) + { + Object.DestroyImmediate(scrollbarComponent.gameObject); + } + } + + serializedObject.ApplyModifiedProperties(); + } + + #endregion + + #region Templates Section + + private void DrawTemplatesSection() + { + EditorGUILayout.Space(5); + EditorGUILayout.LabelField("Item Templates", EditorStyles.boldLabel); + + DrawTemplatesList(); + DrawDragAndDropArea(); + } + + private void DrawTemplatesList() + { + if (_templates == null || !_templates.isArray) return; + + for (int i = 0; i < _templates.arraySize; i++) + { + DrawTemplateItem(i); + } + } + + private void DrawTemplateItem(int index) + { + if (index < 0 || index >= _templates.arraySize) return; + + SerializedProperty item = _templates.GetArrayElementAtIndex(index); + + EditorGUILayout.BeginHorizontal(); + { + using (new EditorGUI.DisabledScope(true)) + { + EditorGUILayout.PropertyField(item, GUIContent.none, true); + } + + if (GUILayout.Button("×", GUILayout.Width(20), GUILayout.Height(EditorGUIUtility.singleLineHeight))) + { + RemoveTemplateItem(index); + } + } + EditorGUILayout.EndHorizontal(); + } + + private void RemoveTemplateItem(int index) + { + if (_templates == null || index < 0 || index >= _templates.arraySize) return; + + _templates.DeleteArrayElementAtIndex(index); + serializedObject.ApplyModifiedProperties(); + GUIUtility.ExitGUI(); + } + + private void DrawDragAndDropArea() + { + Rect dropArea = GUILayoutUtility.GetRect(0f, 50f, GUILayout.ExpandWidth(true)); + GUI.Box(dropArea, "Drag ViewHolder Templates Here", EditorStyles.helpBox); + + HandleDragAndDrop(dropArea); + } + + private void HandleDragAndDrop(Rect dropArea) + { + Event currentEvent = Event.current; + + switch (currentEvent.type) + { + case EventType.DragUpdated: + case EventType.DragPerform: + if (!dropArea.Contains(currentEvent.mousePosition)) + return; + + DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + + if (currentEvent.type == EventType.DragPerform) + { + DragAndDrop.AcceptDrag(); + ProcessDraggedTemplates(); + currentEvent.Use(); + } + break; + } + } + + private void ProcessDraggedTemplates() + { + foreach (Object draggedObject in DragAndDrop.objectReferences) + { + if (draggedObject is GameObject gameObject) + { + ProcessDraggedGameObject(gameObject); + } + } + } + + private void ProcessDraggedGameObject(GameObject gameObject) + { + ViewHolder viewHolder = gameObject.GetComponent(); + + if (viewHolder != null) + { + AddTemplate(gameObject); + } + else + { + Debug.LogWarning($"GameObject '{gameObject.name}' must have a ViewHolder component!"); + } + } + + private void AddTemplate(GameObject templatePrefab) + { + if (_templates == null || templatePrefab == null) return; + + if (IsTemplateDuplicate(templatePrefab)) + { + Debug.LogWarning($"Template '{templatePrefab.name}' already exists in the list!"); + return; + } + + _templates.arraySize++; + SerializedProperty newItem = _templates.GetArrayElementAtIndex(_templates.arraySize - 1); + newItem.objectReferenceValue = templatePrefab; + + serializedObject.ApplyModifiedProperties(); + } + + private bool IsTemplateDuplicate(GameObject templatePrefab) + { + Type templateType = templatePrefab.GetComponent().GetType(); + + for (int i = 0; i < _templates.arraySize; i++) + { + SerializedProperty existingItem = _templates.GetArrayElementAtIndex(i); + var existingViewHolder = existingItem.objectReferenceValue as GameObject; + + if (existingViewHolder != null) + { + var existingType = existingViewHolder.GetComponent().GetType(); + if (existingType == templateType) + { + return true; + } + } + } + + return false; + } + + #endregion + + #region Helper Methods + + private void DrawManagedReferenceProperties(SerializedProperty property) + { + SerializedProperty iterator = property.Copy(); + bool enterChildren = true; + + while (iterator.NextVisible(enterChildren)) + { + enterChildren = false; + if (iterator.name == "m_Script") continue; + + EditorGUILayout.PropertyField(iterator, true); + } + } + + private void DrawComponentProperties(MonoBehaviour component, string header = null) + { + if (component == null) return; + + EditorGUILayout.Space(3); + + if (!string.IsNullOrEmpty(header)) + { + EditorGUILayout.LabelField(header, EditorStyles.boldLabel); + } + + SerializedObject componentSerializedObject = new SerializedObject(component); + componentSerializedObject.Update(); + + SerializedProperty property = componentSerializedObject.GetIterator(); + bool enterChildren = true; + + while (property.NextVisible(enterChildren)) + { + enterChildren = false; + if (property.name == "m_Script") continue; + + EditorGUILayout.PropertyField(property, true); + } + + componentSerializedObject.ApplyModifiedProperties(); + } + + #endregion + + #region Restore Helpers (新增) + + private void RestoreLayoutManagerFromTypeNameIfMissing() + { + try + { + if (_layoutManager == null || _layoutManagerTypeName == null) return; + + // 如果 managedReferenceValue 已经存在就不必恢复 + if (_layoutManager.managedReferenceValue != null) return; + + string typeName = _layoutManagerTypeName.stringValue; + if (string.IsNullOrEmpty(typeName)) return; + + Type type = AlicizaX.Utility.Assembly.GetType(typeName); + if (type == null) + { + Debug.LogWarning($"LayoutManager type '{typeName}' not found. Cannot restore layout manager."); + return; + } + + if (!typeof(ILayoutManager).IsAssignableFrom(type)) + { + Debug.LogWarning($"Type '{typeName}' does not implement ILayoutManager. Cannot restore layout manager."); + _layoutManagerTypeName.stringValue = ""; + return; + } + + // 实例化并赋值 + var instance = Activator.CreateInstance(type); + _layoutManager.managedReferenceValue = instance; + // 尝试刷新下拉列表并更新选择索引 + RefreshLayoutTypes(); + _selectedLayoutIndex = Mathf.Clamp(_layoutTypeNames.IndexOf(typeName), 0, _layoutTypeNames.Count - 1); + + Debug.Log($"LayoutManager restored from type name '{typeName}'."); + } + catch (Exception e) + { + Debug.LogError($"Error restoring LayoutManager: {e}"); + _layoutManager.managedReferenceValue = null; + _layoutManagerTypeName.stringValue = ""; + } + } + + private void RestoreScrollerFromTypeNameIfMissing() + { + try + { + if (_scroller == null || _scrollerTypeName == null) return; + + // 如果 objectReferenceValue 已经存在就不必恢复 + if (_scroller.objectReferenceValue != null) return; + + string typeName = _scrollerTypeName.stringValue; + if (string.IsNullOrEmpty(typeName)) return; + + Type type = AlicizaX.Utility.Assembly.GetType(typeName) ?? Type.GetType(typeName); + if (type == null) + { + Debug.LogWarning($"Scroller type '{typeName}' not found. Cannot restore scroller component."); + _scrollerTypeName.stringValue = ""; + return; + } + + if (!typeof(MonoBehaviour).IsAssignableFrom(type) || !typeof(IScroller).IsAssignableFrom(type)) + { + Debug.LogWarning($"Type '{typeName}' is not a MonoBehaviour implementing IScroller. Cannot restore scroller."); + _scrollerTypeName.stringValue = ""; + return; + } + + var recyclerView = target as RecyclerView; + if (recyclerView == null) return; + + // 给目标 GameObject 添加组件(使用 Undo 以支持撤销) + var newComp = Undo.AddComponent(recyclerView.gameObject, type) as MonoBehaviour; + if (newComp == null) + { + Debug.LogError($"Failed to add scroller component of type '{typeName}' to GameObject '{recyclerView.gameObject.name}'."); + _scrollerTypeName.stringValue = ""; + return; + } + + _scroller.objectReferenceValue = newComp; + // 刷新 scroller 类型列表并更新索引(如果存在) + RefreshScrollerTypes(); + _selectedScrollerIndex = Mathf.Clamp(_scrollerTypeNames.IndexOf(typeName), 0, _scrollerTypeNames.Count - 1); + + EditorUtility.SetDirty(recyclerView); + Debug.Log($"Scroller component of type '{typeName}' restored and attached to GameObject '{recyclerView.gameObject.name}'."); + } + catch (Exception e) + { + Debug.LogError($"Error restoring Scroller: {e}"); + _scroller.objectReferenceValue = null; + _scrollerTypeName.stringValue = ""; + } + } + + #endregion } } diff --git a/Editor/RecyclerView/Res/ScrollView.prefab b/Editor/RecyclerView/Res/ScrollView.prefab index 2b19426..04a6fe1 100644 --- a/Editor/RecyclerView/Res/ScrollView.prefab +++ b/Editor/RecyclerView/Res/ScrollView.prefab @@ -12,7 +12,7 @@ GameObject: - component: {fileID: 409256563818501030} - component: {fileID: 4967247000384896254} - component: {fileID: 2527097672867102998} - - component: {fileID: 144409482669617178} + - component: {fileID: 222070472718835377} m_Layer: 5 m_Name: ScrollView m_TagString: Untagged @@ -90,9 +90,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 7efd8e83d2092b347952108134dc37eb, type: 3} m_Name: m_EditorClassIdentifier: - direction: 1 + direction: 0 alignment: 1 - content: {fileID: 7227160576944475251} spacing: {x: 0, y: 0} padding: {x: 0, y: 0} scroll: 1 @@ -100,20 +99,21 @@ MonoBehaviour: scrollSpeed: 10 wheelSpeed: 30 templates: [] - _scrollerTypeName: AlicizaX.UI.RecyclerView.Scroller - _scroller: {fileID: 144409482669617178} - _showScrollBar: 0 - _scrollbar: {fileID: 0} + content: {fileID: 7227160576944475251} + showScrollBar: 0 + scrollbar: {fileID: 0} _layoutManagerTypeName: AlicizaX.UI.LinearLayoutManager - _layoutManager: - rid: 7492395943315111994 + layoutManager: + rid: 6739296571988901898 + _scrollerTypeName: AlicizaX.UI.Scroller + scroller: {fileID: 222070472718835377} references: version: 2 RefIds: - - rid: 7492395943315111994 + - rid: 6739296571988901898 type: {class: LinearLayoutManager, ns: AlicizaX.UI, asm: AlicizaX.UI.Extension} data: ---- !u!114 &144409482669617178 +--- !u!114 &222070472718835377 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -125,6 +125,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 7b7de4cb3a1546e4a9ade6b8dbf8af92, type: 3} m_Name: m_EditorClassIdentifier: + dragStopTime: 0 --- !u!1 &9220717789715235424 GameObject: m_ObjectHideFlags: 0 diff --git a/Runtime/RecyclerView/Adapter/Adapter.cs b/Runtime/RecyclerView/Adapter/Adapter.cs index aa3720f..e9ed5d8 100644 --- a/Runtime/RecyclerView/Adapter/Adapter.cs +++ b/Runtime/RecyclerView/Adapter/Adapter.cs @@ -69,7 +69,7 @@ namespace AlicizaX.UI recyclerView.Refresh(); } - protected internal virtual void SetList(List list) + public virtual void SetList(List list) { this.list = list; recyclerView.Reset(); diff --git a/Runtime/RecyclerView/Adapter/GroupAdapter.cs b/Runtime/RecyclerView/Adapter/GroupAdapter.cs index 4f0cb5b..1bd2740 100644 --- a/Runtime/RecyclerView/Adapter/GroupAdapter.cs +++ b/Runtime/RecyclerView/Adapter/GroupAdapter.cs @@ -87,7 +87,7 @@ namespace AlicizaX.UI base.NotifyDataChanged(); } - protected internal override void SetList(List list) + public override void SetList(List list) { showList.Clear(); base.SetList(list); diff --git a/Runtime/RecyclerView/Data.meta b/Runtime/RecyclerView/Data.meta index 9bd39ec..e3d1b21 100644 --- a/Runtime/RecyclerView/Data.meta +++ b/Runtime/RecyclerView/Data.meta @@ -1,3 +1,3 @@ fileFormatVersion: 2 -guid: 05736c35a54b467a966b416c1461ba61 -timeCreated: 1763617414 \ No newline at end of file +guid: d43a3e6aece04b86ab8eb139abb89e46 +timeCreated: 1766661642 \ No newline at end of file diff --git a/Runtime/RecyclerView/Data/ISimpleViewData.cs.meta b/Runtime/RecyclerView/Data/ISimpleViewData.cs.meta index 2f33bfc..6a976bf 100644 --- a/Runtime/RecyclerView/Data/ISimpleViewData.cs.meta +++ b/Runtime/RecyclerView/Data/ISimpleViewData.cs.meta @@ -1,3 +1,3 @@ fileFormatVersion: 2 -guid: a059b7119e284e37a88a569f76b40579 -timeCreated: 1763617420 \ No newline at end of file +guid: cacaed2312884ce9ba739399b8271b93 +timeCreated: 1766661647 \ No newline at end of file diff --git a/Runtime/RecyclerView/EaseUtil.cs.meta b/Runtime/RecyclerView/EaseUtil.cs.meta index 3b13f5e..a45123a 100644 --- a/Runtime/RecyclerView/EaseUtil.cs.meta +++ b/Runtime/RecyclerView/EaseUtil.cs.meta @@ -1,3 +1,3 @@ fileFormatVersion: 2 -guid: d42e2db77425447490cb9f68003e818b -timeCreated: 1741771999 \ No newline at end of file +guid: fdf59811e23f47f19be6398684473df2 +timeCreated: 1766647995 \ No newline at end of file diff --git a/Runtime/RecyclerView/Layout/AlignableLinearLayoutManager.cs b/Runtime/RecyclerView/Layout/AlignableLinearLayoutManager.cs deleted file mode 100644 index 20c6767..0000000 --- a/Runtime/RecyclerView/Layout/AlignableLinearLayoutManager.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using UnityEngine; - -namespace AlicizaX.UI -{ - [Serializable] - public class AlignableLinearLayoutManager : LinearLayoutManager - { - [SerializeField] private float alignmentCount = 0f; // 对齐比例 (0=顶部, 1=底部, 0.5=居中) - - public override Vector2 CalculatePosition(int index) - { - float position; - - if (direction == Direction.Vertical) - { - position = index * (lineHeight + spacing.y) - ScrollPosition ; - return new Vector2(0, position + padding.y); - } - - position = index * (lineHeight + spacing.x) - ScrollPosition ; - var a = new Vector2(position + padding.x, 0); - return a; - } - - public override float IndexToPosition(int index) - { - if (index < 0 || index >= adapter.GetItemCount()) return 0; - - float len, viewLength, position; - - if (direction == Direction.Vertical) - { - len = index * (lineHeight + spacing.y) - ((lineHeight + spacing.y) * alignmentCount); - viewLength = viewportSize.y; - position = len + viewLength > contentSize.y ? contentSize.y - viewportSize.y : len; - } - else - { - len = index * (lineHeight + spacing.x); - viewLength = viewportSize.x; - position = len + viewLength > contentSize.x ? contentSize.x - viewportSize.x : len; - } - - return position; - } - } -} diff --git a/Runtime/RecyclerView/Layout/AlignableLinearLayoutManager.cs.meta b/Runtime/RecyclerView/Layout/AlignableLinearLayoutManager.cs.meta deleted file mode 100644 index 12a94a1..0000000 --- a/Runtime/RecyclerView/Layout/AlignableLinearLayoutManager.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: e5cb3f76e0f84a7aa75959e194f524b5 -timeCreated: 1748588163 \ No newline at end of file diff --git a/Runtime/RecyclerView/Layout/CircleLayoutManager.cs b/Runtime/RecyclerView/Layout/CircleLayoutManager.cs index 8bd0015..7667219 100644 --- a/Runtime/RecyclerView/Layout/CircleLayoutManager.cs +++ b/Runtime/RecyclerView/Layout/CircleLayoutManager.cs @@ -3,23 +3,20 @@ using UnityEngine; namespace AlicizaX.UI { + [System.Serializable] public class CircleLayoutManager : LayoutManager { - private float radius; - private float intervalAngle; - - private new CircleDirection direction; [SerializeField] + private CircleDirection circleDirection= CircleDirection.Positive; + [SerializeField] + private float intervalAngle=0; + + private float radius; private float initalAngle; - - public CircleLayoutManager(CircleDirection direction = CircleDirection.Positive) - { - this.direction = direction; - } - public CircleLayoutManager() { + } public override Vector2 CalculateContentSize() @@ -49,7 +46,7 @@ namespace AlicizaX.UI public override Vector2 CalculatePosition(int index) { float angle = index * intervalAngle; - angle = direction == CircleDirection.Positive ? angle : -angle; + angle = circleDirection == CircleDirection.Positive ? angle : -angle; angle += initalAngle + ScrollPosition; float radian = angle * (Mathf.PI / 180f); float x = radius * Mathf.Sin(radian); @@ -97,7 +94,7 @@ namespace AlicizaX.UI for (int i = 0; i < viewHolders.Count; i++) { float angle = i * intervalAngle + initalAngle; - angle = direction == CircleDirection.Positive ? angle + ScrollPosition : angle - ScrollPosition; + angle = circleDirection == CircleDirection.Positive ? angle + ScrollPosition : angle - ScrollPosition; float delta = (angle - initalAngle) % 360; delta = delta < 0 ? delta + 360 : delta; delta = delta > 180 ? 360 - delta : delta; diff --git a/Runtime/RecyclerView/Layout/GridLayoutManager.cs b/Runtime/RecyclerView/Layout/GridLayoutManager.cs index 5d6a1b7..92e5896 100644 --- a/Runtime/RecyclerView/Layout/GridLayoutManager.cs +++ b/Runtime/RecyclerView/Layout/GridLayoutManager.cs @@ -1,18 +1,17 @@ -using System; using UnityEngine; namespace AlicizaX.UI { - [Serializable] + [System.Serializable] public class GridLayoutManager : LayoutManager { private Vector2 cellSize; - [SerializeField] private int cellCount; + [SerializeField] private int cellCounnt = 1; public GridLayoutManager() { - unit = cellCount; + this.unit = cellCounnt; } public override Vector2 CalculateContentSize() @@ -63,7 +62,6 @@ namespace AlicizaX.UI width = viewportSize.x; height = viewportSize.y; } - return new Vector2((width - cellSize.x) / 2, (height - cellSize.y) / 2); } @@ -80,7 +78,6 @@ namespace AlicizaX.UI width = viewportSize.x; height = viewportSize.y; } - return new Vector2((width - cellSize.x) / 2, (height - cellSize.y) / 2); } diff --git a/Runtime/RecyclerView/Layout/LayoutManager.cs b/Runtime/RecyclerView/Layout/LayoutManager.cs index e0a8e5a..2b34e39 100644 --- a/Runtime/RecyclerView/Layout/LayoutManager.cs +++ b/Runtime/RecyclerView/Layout/LayoutManager.cs @@ -1,13 +1,11 @@ -using System; using UnityEngine; namespace AlicizaX.UI { - [Serializable] + [System.Serializable] public abstract class LayoutManager : ILayoutManager { protected Vector2 viewportSize; - public Vector2 ViewportSize { get => viewportSize; @@ -15,7 +13,6 @@ namespace AlicizaX.UI } protected Vector2 contentSize; - public Vector2 ContentSize { get => contentSize; @@ -23,7 +20,6 @@ namespace AlicizaX.UI } protected Vector2 contentOffset; - public Vector2 ContentOffset { get => contentOffset; @@ -31,7 +27,6 @@ namespace AlicizaX.UI } protected Vector2 viewportOffset; - public Vector2 ViewportOffset { get => viewportOffset; @@ -39,7 +34,6 @@ namespace AlicizaX.UI } protected IAdapter adapter; - public IAdapter Adapter { get => adapter; @@ -47,7 +41,6 @@ namespace AlicizaX.UI } protected ViewProvider viewProvider; - public ViewProvider ViewProvider { get => viewProvider; @@ -55,7 +48,6 @@ namespace AlicizaX.UI } protected RecyclerView recyclerView; - public virtual RecyclerView RecyclerView { get => recyclerView; @@ -63,7 +55,6 @@ namespace AlicizaX.UI } protected Direction direction; - public Direction Direction { get => direction; @@ -71,7 +62,6 @@ namespace AlicizaX.UI } protected Alignment alignment; - public Alignment Alignment { get => alignment; @@ -79,7 +69,6 @@ namespace AlicizaX.UI } protected Vector2 spacing; - public Vector2 Spacing { get => spacing; @@ -87,7 +76,6 @@ namespace AlicizaX.UI } protected Vector2 padding; - public Vector2 Padding { get => padding; @@ -95,26 +83,16 @@ namespace AlicizaX.UI } protected int unit = 1; - public int Unit { get => unit; set => unit = value; } - protected bool canScroll; - - public bool CanScroll - { - get => canScroll; - set => canScroll = value; - } public float ScrollPosition => recyclerView.GetScrollPosition(); - public LayoutManager() - { - } + public LayoutManager() { } public void SetContentSize() { @@ -135,7 +113,9 @@ namespace AlicizaX.UI public virtual void Layout(ViewHolder viewHolder, int index) { Vector2 pos = CalculatePosition(index); - Vector3 position = direction == Direction.Vertical ? new Vector3(pos.x - contentOffset.x, -pos.y + contentOffset.y, 0) : new Vector3(pos.x - contentOffset.x, -pos.y + contentOffset.y, 0); + Vector3 position = direction == Direction.Vertical ? + new Vector3(pos.x - contentOffset.x, -pos.y + contentOffset.y, 0) : + new Vector3(pos.x - contentOffset.x, -pos.y + contentOffset.y, 0); viewHolder.RectTransform.anchoredPosition3D = position; } @@ -155,9 +135,7 @@ namespace AlicizaX.UI public abstract int PositionToIndex(float position); - public virtual void DoItemAnimation() - { - } + public virtual void DoItemAnimation() { } public virtual bool IsFullVisibleStart(int index) { @@ -222,7 +200,6 @@ namespace AlicizaX.UI { len = alignment == Alignment.Center ? Mathf.Min(contentSize.x, viewportSize.x) : viewportSize.x; } - return len; } @@ -234,9 +211,9 @@ namespace AlicizaX.UI public enum Direction { - Vertical, - Horizontal, - Custom, + Vertical = 0, + Horizontal = 1, + Custom = 2 } public enum Alignment diff --git a/Runtime/RecyclerView/Layout/LinearLayoutManager.cs b/Runtime/RecyclerView/Layout/LinearLayoutManager.cs index f0d0281..2742cd1 100644 --- a/Runtime/RecyclerView/Layout/LinearLayoutManager.cs +++ b/Runtime/RecyclerView/Layout/LinearLayoutManager.cs @@ -1,14 +1,14 @@ +using System; using UnityEngine; namespace AlicizaX.UI { + [Serializable] public class LinearLayoutManager : LayoutManager { protected float lineHeight; - public LinearLayoutManager() - { - } + public LinearLayoutManager() { } public override Vector2 CalculateContentSize() { @@ -22,7 +22,6 @@ namespace AlicizaX.UI position = index * (lineHeight + spacing.y) - spacing.y; return new Vector2(contentSize.x, position + padding.y * 2); } - position = index * (lineHeight + spacing.x) - spacing.x; return new Vector2(position + padding.x * 2, contentSize.y); } @@ -35,7 +34,6 @@ namespace AlicizaX.UI position = index * (lineHeight + spacing.y) - ScrollPosition; return new Vector2(0, position + padding.y); } - position = index * (lineHeight + spacing.x) - ScrollPosition; return new Vector2(position + padding.x, 0); } @@ -47,7 +45,6 @@ namespace AlicizaX.UI { return new Vector2(0, (len - lineHeight) / 2); } - return new Vector2((len - lineHeight) / 2, 0); } @@ -57,7 +54,6 @@ namespace AlicizaX.UI { return new Vector2(0, (viewportSize.y - lineHeight) / 2); } - return new Vector2((viewportSize.x - lineHeight) / 2, 0); } diff --git a/Runtime/RecyclerView/Layout/MixedLayoutManager.cs b/Runtime/RecyclerView/Layout/MixedLayoutManager.cs index 0986bf6..07a0247 100644 --- a/Runtime/RecyclerView/Layout/MixedLayoutManager.cs +++ b/Runtime/RecyclerView/Layout/MixedLayoutManager.cs @@ -1,7 +1,9 @@ +using System; using UnityEngine; namespace AlicizaX.UI { + [Serializable] public class MixedLayoutManager : LayoutManager { public MixedLayoutManager() { } diff --git a/Runtime/RecyclerView/Layout/PageLayoutManager.cs b/Runtime/RecyclerView/Layout/PageLayoutManager.cs index f6e7234..91e0b97 100644 --- a/Runtime/RecyclerView/Layout/PageLayoutManager.cs +++ b/Runtime/RecyclerView/Layout/PageLayoutManager.cs @@ -4,10 +4,10 @@ using UnityEngine; namespace AlicizaX.UI { + [Serializable] public class PageLayoutManager : LinearLayoutManager { - [SerializeField] - private float minScale; + [SerializeField] private float minScale = 0.9f; public PageLayoutManager() { diff --git a/Runtime/RecyclerView/ObjectPool.meta b/Runtime/RecyclerView/ObjectPool.meta index 2e7ac88..2aa51c6 100644 --- a/Runtime/RecyclerView/ObjectPool.meta +++ b/Runtime/RecyclerView/ObjectPool.meta @@ -1,3 +1,8 @@ fileFormatVersion: 2 guid: 3a9bb27da68d4451a7d79cdb7abc1506 -timeCreated: 1748431180 \ No newline at end of file +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/RecyclerView/ObjectPool/IMixedObjectFactory.cs b/Runtime/RecyclerView/ObjectPool/IMixedObjectFactory.cs index 918803a..01a58cd 100644 --- a/Runtime/RecyclerView/ObjectPool/IMixedObjectFactory.cs +++ b/Runtime/RecyclerView/ObjectPool/IMixedObjectFactory.cs @@ -1,8 +1,6 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { - - - internal interface IMixedObjectFactory where T : class + public interface IMixedObjectFactory where T : class { T Create(string typeName); @@ -12,5 +10,4 @@ namespace SimpleObjectPool bool Validate(string typeName, T obj); } - } diff --git a/Runtime/RecyclerView/ObjectPool/IMixedObjectPool.cs b/Runtime/RecyclerView/ObjectPool/IMixedObjectPool.cs index d182123..cfb6287 100644 --- a/Runtime/RecyclerView/ObjectPool/IMixedObjectPool.cs +++ b/Runtime/RecyclerView/ObjectPool/IMixedObjectPool.cs @@ -1,8 +1,8 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { using System; - internal interface IMixedObjectPool : IDisposable where T : class + public interface IMixedObjectPool : IDisposable where T : class { T Allocate(string typeName); diff --git a/Runtime/RecyclerView/ObjectPool/IObjectFactory.cs b/Runtime/RecyclerView/ObjectPool/IObjectFactory.cs index 06271e9..b2e2d27 100644 --- a/Runtime/RecyclerView/ObjectPool/IObjectFactory.cs +++ b/Runtime/RecyclerView/ObjectPool/IObjectFactory.cs @@ -1,8 +1,6 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { - - - internal interface IObjectFactory where T : class + public interface IObjectFactory where T : class { /// /// 创建对象 @@ -29,5 +27,4 @@ namespace SimpleObjectPool /// bool Validate(T obj); } - } diff --git a/Runtime/RecyclerView/ObjectPool/IObjectPool.cs b/Runtime/RecyclerView/ObjectPool/IObjectPool.cs index bf0abe0..29da139 100644 --- a/Runtime/RecyclerView/ObjectPool/IObjectPool.cs +++ b/Runtime/RecyclerView/ObjectPool/IObjectPool.cs @@ -1,8 +1,8 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { using System; - internal interface IObjectPool : IDisposable + public interface IObjectPool : IDisposable { /// /// 从池子中分配一个可用对象,没有的话就创建一个 @@ -17,7 +17,7 @@ namespace SimpleObjectPool void Free(object obj); } - internal interface IObjectPool : IObjectPool, IDisposable where T : class + public interface IObjectPool : IObjectPool, IDisposable where T : class { new T Allocate(); diff --git a/Runtime/RecyclerView/ObjectPool/IPooledObject.cs b/Runtime/RecyclerView/ObjectPool/IPooledObject.cs index f9d5e56..6749fdd 100644 --- a/Runtime/RecyclerView/ObjectPool/IPooledObject.cs +++ b/Runtime/RecyclerView/ObjectPool/IPooledObject.cs @@ -1,10 +1,7 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { - - - internal interface IPooledObject + public interface IPooledObject { void Free(); } - } diff --git a/Runtime/RecyclerView/ObjectPool/MixedObjectPool.cs b/Runtime/RecyclerView/ObjectPool/MixedObjectPool.cs index 2c88f2d..fdb4180 100644 --- a/Runtime/RecyclerView/ObjectPool/MixedObjectPool.cs +++ b/Runtime/RecyclerView/ObjectPool/MixedObjectPool.cs @@ -1,10 +1,10 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { using System; using System.Collections.Concurrent; using System.Collections.Generic; - internal class MixedObjectPool : IMixedObjectPool where T : class + public class MixedObjectPool : IMixedObjectPool where T : class { private const int DEFAULT_MAX_SIZE_PER_TYPE = 10; diff --git a/Runtime/RecyclerView/ObjectPool/ObjectPool.cs b/Runtime/RecyclerView/ObjectPool/ObjectPool.cs index 483487b..983504d 100644 --- a/Runtime/RecyclerView/ObjectPool/ObjectPool.cs +++ b/Runtime/RecyclerView/ObjectPool/ObjectPool.cs @@ -1,9 +1,9 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { using System; using System.Threading; - internal class ObjectPool : IObjectPool where T : class + public class ObjectPool : IObjectPool where T : class { private int maxSize; private int initialSize; diff --git a/Runtime/RecyclerView/ObjectPool/UnityComponentFactory.cs b/Runtime/RecyclerView/ObjectPool/UnityComponentFactory.cs index 25fa23d..0729269 100644 --- a/Runtime/RecyclerView/ObjectPool/UnityComponentFactory.cs +++ b/Runtime/RecyclerView/ObjectPool/UnityComponentFactory.cs @@ -1,8 +1,8 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { using UnityEngine; - internal class UnityComponentFactory : IObjectFactory where T : Component + public class UnityComponentFactory : IObjectFactory where T : Component { private T template; private Transform parent; diff --git a/Runtime/RecyclerView/ObjectPool/UnityGameObjectFactory.cs b/Runtime/RecyclerView/ObjectPool/UnityGameObjectFactory.cs index 45043f3..3fb9f5c 100644 --- a/Runtime/RecyclerView/ObjectPool/UnityGameObjectFactory.cs +++ b/Runtime/RecyclerView/ObjectPool/UnityGameObjectFactory.cs @@ -1,8 +1,8 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { using UnityEngine; - internal class UnityGameObjectFactory : IObjectFactory + public class UnityGameObjectFactory : IObjectFactory { protected GameObject template; protected Transform parent; diff --git a/Runtime/RecyclerView/ObjectPool/UnityMixedComponentFactory.cs b/Runtime/RecyclerView/ObjectPool/UnityMixedComponentFactory.cs index 1e73bad..92cee18 100644 --- a/Runtime/RecyclerView/ObjectPool/UnityMixedComponentFactory.cs +++ b/Runtime/RecyclerView/ObjectPool/UnityMixedComponentFactory.cs @@ -1,10 +1,10 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { using System.Collections; using System.Collections.Generic; using UnityEngine; - internal class UnityMixedComponentFactory : IMixedObjectFactory where T : Component + public class UnityMixedComponentFactory : IMixedObjectFactory where T : Component { protected T template; protected Transform parent; diff --git a/Runtime/RecyclerView/ObjectPool/UnityMixedGameObjectFactory.cs b/Runtime/RecyclerView/ObjectPool/UnityMixedGameObjectFactory.cs index 56745e6..5b8e507 100644 --- a/Runtime/RecyclerView/ObjectPool/UnityMixedGameObjectFactory.cs +++ b/Runtime/RecyclerView/ObjectPool/UnityMixedGameObjectFactory.cs @@ -1,8 +1,8 @@ -namespace SimpleObjectPool +namespace AlicizaX.UI { using UnityEngine; - internal class UnityMixedGameObjectFactory : IMixedObjectFactory + public class UnityMixedGameObjectFactory : IMixedObjectFactory { protected GameObject template; protected Transform parent; @@ -40,5 +40,4 @@ namespace SimpleObjectPool return true; } } - } diff --git a/Runtime/RecyclerView/RecyclerView.cs b/Runtime/RecyclerView/RecyclerView.cs index 0bf3528..1a8eeaf 100644 --- a/Runtime/RecyclerView/RecyclerView.cs +++ b/Runtime/RecyclerView/RecyclerView.cs @@ -1,13 +1,61 @@ using System; using UnityEngine; -using UnityEngine.Serialization; using UnityEngine.UI; namespace AlicizaX.UI { public class RecyclerView : MonoBehaviour { - [SerializeField] private Direction direction; + #region Serialized Fields - Layout Settings + + [HideInInspector] [SerializeField] private Direction direction; + [HideInInspector] [SerializeField] private Alignment alignment; + [HideInInspector] [SerializeField] private Vector2 spacing; + [HideInInspector] [SerializeField] private Vector2 padding; + + #endregion + + #region Serialized Fields - Scroll Settings + + [HideInInspector] [SerializeField] private bool scroll; + [HideInInspector] [SerializeField] private bool snap; + + [HideInInspector] [SerializeField, Range(1f, 50f)] + private float scrollSpeed = 7f; + + [HideInInspector] [SerializeField, Range(10f, 50f)] + private float wheelSpeed = 30f; + + #endregion + + #region Serialized Fields - Components + + [HideInInspector] [SerializeField] private ViewHolder[] templates; + [HideInInspector] [SerializeField] private RectTransform content; + [HideInInspector] [SerializeField] private bool showScrollBar; + [HideInInspector] [SerializeField] private Scrollbar scrollbar; + + #endregion + + #region Serialized Fields - Internal (Hidden in Inspector) + + [HideInInspector] [SerializeField] private string _layoutManagerTypeName; + [SerializeReference] private LayoutManager layoutManager; + [HideInInspector] [SerializeField] private string _scrollerTypeName; + [HideInInspector] [SerializeReference] private Scroller scroller; + + #endregion + + #region Private Fields + + private ViewProvider viewProvider; + private int startIndex; + private int endIndex; + private int currentIndex; + + #endregion + + #region Public Properties - Layout Settings public Direction Direction { @@ -15,15 +63,119 @@ namespace AlicizaX.UI set => direction = value; } - [SerializeField] private Alignment alignment; - public Alignment Alignment { get => alignment; set => alignment = value; } - [SerializeField] private RectTransform content; + public Vector2 Spacing + { + get => spacing; + set => spacing = value; + } + + public Vector2 Padding + { + get => padding; + set => padding = value; + } + + #endregion + + #region Public Properties - Scroll Settings + + public bool Scroll + { + get => scroll; + set + { + if (scroll == value) return; + scroll = value; + + // 启/停 scroller(如果存在) + if (scroller != null) + { + // 如果 Scroller 是 MonoBehaviour,可以启/停组件;否则回退到设置一个 Position/flags + scroller.enabled = scroll; + + // 将当前的滚动相关配置下发到 scroller,保持一致性 + scroller.ScrollSpeed = scrollSpeed; + scroller.WheelSpeed = wheelSpeed; + scroller.Snap = snap; + } + + // 更新 scrollbar 显示(只有在 showScrollBar 为 true 时才显示) + if (scrollbar != null) + { + scrollbar.gameObject.SetActive(showScrollBar && scroll); + } + + // 如果启用/禁用滚动后需要调整布局或滚动条大小,刷新布局 + RequestLayout(); + } + } + + public bool Snap + { + get => snap; + set + { + // Snap 依赖于 Scroll(与原逻辑保持一致) + bool newSnap = value & scroll; + if (snap == newSnap) return; + snap = newSnap; + + if (scroller != null) + { + scroller.Snap = snap; + } + + // 如果开启了 snap,可以选做:立即对齐到最近项 + // if (snap && scroller != null) SnapToNearestItem(); + } + } + + public float ScrollSpeed + { + get => scrollSpeed; + set + { + if (Mathf.Approximately(scrollSpeed, value)) return; + scrollSpeed = value; + + if (scroller != null) + { + scroller.ScrollSpeed = scrollSpeed; + } + } + } + + public float WheelSpeed + { + get => wheelSpeed; + set + { + if (Mathf.Approximately(wheelSpeed, value)) return; + wheelSpeed = value; + + if (scroller != null) + { + scroller.WheelSpeed = wheelSpeed; + } + } + } + + #endregion + + + #region Public Properties - Components + + public ViewHolder[] Templates + { + get => templates; + set => templates = value; + } public RectTransform Content { @@ -38,328 +190,354 @@ namespace AlicizaX.UI } } - [SerializeField] private Vector2 spacing; - - public Vector2 Spacing - { - get => spacing; - set => spacing = value; - } - - [SerializeField] private Vector2 padding; - - public Vector2 Padding - { - get => padding; - set => padding = value; - } - - [SerializeField] private bool scroll; - - public bool Scroll - { - get => scroll; - set => scroll = value; - } - - [SerializeField] private bool snap; - - public bool Snap - { - get => snap; - set => snap = value & scroll; - } - - [SerializeField, Range(1f, 50f)] private float scrollSpeed = 7f; - - public float ScrollSpeed - { - get => scrollSpeed; - set => scrollSpeed = value; - } - - [SerializeField, Range(10f, 50f)] private float wheelSpeed = 30f; - - public float WheeelSpeed - { - get => wheelSpeed; - set => wheelSpeed = value; - } - - [SerializeField] private ViewHolder[] templates; - - public ViewHolder[] Templates - { - get => templates; - } - - - [SerializeField] private string _scrollerTypeName; - - [SerializeReference] private Scroller _scroller; - - [SerializeField] private bool _showScrollBar; - [SerializeReference] private Scrollbar _scrollbar; - - - private ViewProvider viewProvider; - - [SerializeField] private string _layoutManagerTypeName; - - [SerializeReference] private LayoutManager _layoutManager; - - - private int startIndex, endIndex; - private int currentIndex; - - public Scroller Scroller => _scroller; - - public int CurrentIndex - { - get => currentIndex; - } - - public bool CanScroll => true; + public Scrollbar Scrollbar => scrollbar; + public Scroller Scroller => scroller; public ViewProvider ViewProvider { get { - viewProvider ??= templates.Length > 1 ? new MixedViewProvider(this, templates) : new SimpleViewProvider(this, templates); + if (viewProvider == null) + { + viewProvider = templates.Length > 1 + ? new MixedViewProvider(this, templates) + : new SimpleViewProvider(this, templates); + } + return viewProvider; } } - public Scrollbar Scrollbar => _scrollbar; + #endregion - private IAdapter _adapter; + #region Public Properties - State + + public IAdapter RecyclerViewAdapter { get; set; } + + public int CurrentIndex + { + get => currentIndex; + set => currentIndex = value; + } + + #endregion + + #region Events public Action OnIndexChanged; public Action OnScrollValueChanged; - public Action OnMoveingChanged; - private void OnValidate() - { - if (_scroller != null) - { - _scroller.ScrollSpeed = scrollSpeed; - _scroller.WheelSpeed = wheelSpeed; - } - } + #endregion - private void OnScrollChanged(float pos) - { - _layoutManager.UpdateLayout(); - - if (Scrollbar != null) - { - Scrollbar.SetValueWithoutNotify(pos / _scroller.MaxPosition); - } - - if (_layoutManager.IsFullInvisibleStart(startIndex)) - { - viewProvider.RemoveViewHolder(startIndex); - startIndex += _layoutManager.Unit; - } - else if (_layoutManager.IsFullVisibleStart(startIndex)) - { - if (startIndex == 0) - { - // TODO Do something, eg: Refresh - } - else - { - startIndex -= _layoutManager.Unit; - viewProvider.CreateViewHolder(startIndex); - } - } - - if (_layoutManager.IsFullInvisibleEnd(endIndex)) - { - viewProvider.RemoveViewHolder(endIndex); - endIndex -= _layoutManager.Unit; - } - else if (_layoutManager.IsFullVisibleEnd(endIndex)) - { - if (endIndex >= viewProvider.GetItemCount() - _layoutManager.Unit) - { - // TODO Do something, eg: Load More - } - else - { - endIndex += _layoutManager.Unit; - viewProvider.CreateViewHolder(endIndex); - } - } - - // 使用滚动条快速定位时,刷新整个列表 - if (!_layoutManager.IsVisible(startIndex) || !_layoutManager.IsVisible(endIndex)) - { - Refresh(); - } - - _layoutManager.DoItemAnimation(); - - OnScrollValueChanged?.Invoke(); - } - - private void OnMoveing() - { - OnMoveingChanged?.Invoke(); - } - - private void OnMoveStoped() - { - if (Snap) - { - SnapTo(); - } - } - - private void OnScrollbarChanged(float ratio) - { - _scroller.ScrollToRatio(ratio); - } - - private void OnScrollbarDragEnd() - { - if (_scroller.Position < _scroller.MaxPosition) - { - if (Snap) - { - SnapTo(); - } - } - } + #region Unity Lifecycle private void Awake() { - for (int i = 0; i < templates.Length; i++) - { - templates[i].gameObject.SetActive(false); - } - - ConfigScroller(); - ConfigScrollbar(); + InitializeTemplates(); + ConfigureScroller(); + ConfigureScrollbar(); } - private void OnDestroy() + #endregion + + #region Initialization + + private void InitializeTemplates() { - viewProvider?.Dispose(); + if (templates == null) return; + + for (int i = 0; i < templates.Length; i++) + { + if (templates[i] != null) + { + templates[i].gameObject.SetActive(false); + } + } + } + + private void ConfigureScroller() + { + if (scroller == null) return; + + scroller.ScrollSpeed = scrollSpeed; + scroller.WheelSpeed = wheelSpeed; + scroller.Snap = snap; + scroller.OnValueChanged.AddListener(OnScrollChanged); + scroller.OnMoveStoped.AddListener(OnMoveStoped); + } + + private void ConfigureScrollbar() + { + if (!showScrollBar || scrollbar == null) return; + + scrollbar.gameObject.SetActive(scroll); + scrollbar.onValueChanged.AddListener(OnScrollbarChanged); + + var scrollbarEx = scrollbar.gameObject.GetComponent(); + if (scrollbarEx == null) + { + scrollbarEx = scrollbar.gameObject.AddComponent(); + } + + scrollbarEx.OnDragEnd = OnScrollbarDragEnd; + } + + #endregion + + #region Public Methods - Setup + + public void SetAdapter(IAdapter adapter) + { + if (adapter == null) + { + Debug.LogError("Adapter cannot be null"); + return; + } + + RecyclerViewAdapter = adapter; + ViewProvider.Adapter = adapter; + ViewProvider.LayoutManager = layoutManager; + + layoutManager.RecyclerView = this; + layoutManager.Adapter = adapter; + layoutManager.ViewProvider = viewProvider; + layoutManager.Direction = direction; + layoutManager.Alignment = alignment; + layoutManager.Spacing = spacing; + layoutManager.Padding = padding; } public void Reset() { viewProvider?.Reset(); - if (_scroller != null) + + if (scroller != null) { - _scroller.Position = 0; + scroller.Position = 0; } - if (_scrollbar != null) + if (scrollbar != null) { - _scrollbar.SetValueWithoutNotify(0); + scrollbar.SetValueWithoutNotify(0); } } - public void SetAdapter(IAdapter adapter) - { - _adapter = adapter; - ViewProvider.Adapter = _adapter; - ViewProvider.LayoutManager = _layoutManager; - _layoutManager.RecyclerView = this; - _layoutManager.Adapter = _adapter; - _layoutManager.ViewProvider = viewProvider; - _layoutManager.Direction = direction; - _layoutManager.Alignment = alignment; - _layoutManager.Spacing = spacing; - _layoutManager.Padding = padding; - _layoutManager.CanScroll = CanScroll; - } + #endregion - - private void ConfigScroller() - { - if (_scroller != null) - { - _scroller.ScrollSpeed = scrollSpeed; - _scroller.WheelSpeed = wheelSpeed; - _scroller.Snap = Snap; - _scroller.OnValueChanged.AddListener(OnScrollChanged); - _scroller.OnMoveStoped.AddListener(OnMoveStoped); - _scroller.OnMoveing.AddListener(OnMoveing); - } - } - - private void ConfigScrollbar() - { - if (_showScrollBar && _scrollbar != null) - { - _scrollbar.gameObject.SetActive(scroll); - _scrollbar.onValueChanged.AddListener(OnScrollbarChanged); - _scrollbar.gameObject.AddComponent().OnDragEnd = OnScrollbarDragEnd; - } - } + #region Public Methods - Layout public void Refresh() { ViewProvider.Clear(); - startIndex = _layoutManager.GetStartIndex(); - endIndex = _layoutManager.GetEndIndex(); - for (int i = startIndex; i <= endIndex; i += _layoutManager.Unit) + startIndex = layoutManager.GetStartIndex(); + endIndex = layoutManager.GetEndIndex(); + + for (int i = startIndex; i <= endIndex; i += layoutManager.Unit) { ViewProvider.CreateViewHolder(i); } - _layoutManager.DoItemAnimation(); + layoutManager.DoItemAnimation(); } public void RequestLayout() { - _layoutManager.SetContentSize(); + layoutManager.SetContentSize(); - if (_scroller == null) return; + if (scroller == null) return; - _scroller.Direction = direction; - _scroller.ViewSize = _layoutManager.ViewportSize; - _scroller.ContentSize = _layoutManager.ContentSize; + scroller.Direction = direction; + scroller.ViewSize = layoutManager.ViewportSize; + scroller.ContentSize = layoutManager.ContentSize; - if (Scrollbar != null && _scroller.ContentSize != Vector2.zero) - { - if ((direction == Direction.Vertical && _layoutManager.ContentSize.y <= _layoutManager.ViewportSize.y) || - (direction == Direction.Horizontal && _layoutManager.ContentSize.x <= _layoutManager.ViewportSize.x) || - (direction == Direction.Custom)) - { - Scrollbar.gameObject.SetActive(false); - } - else - { - Scrollbar.gameObject.SetActive(true); - Scrollbar.direction = direction == Direction.Vertical ? Scrollbar.Direction.TopToBottom : Scrollbar.Direction.LeftToRight; - Scrollbar.size = direction == Direction.Vertical ? _scroller.ViewSize.y / _scroller.ContentSize.y : _scroller.ViewSize.x / _scroller.ContentSize.x; - } - } + UpdateScrollbarVisibility(); } + #endregion + + #region Public Methods - Scrolling + public float GetScrollPosition() { - return _scroller ? _scroller.Position : 0; + return scroller != null ? scroller.Position : 0; } public void ScrollTo(int index, bool smooth = false) { - if (!scroll) return; + if (!scroll || scroller == null) return; + + scroller.ScrollTo(layoutManager.IndexToPosition(index), smooth); - _scroller.ScrollTo(_layoutManager.IndexToPosition(index), smooth); if (!smooth) { Refresh(); } - index %= _adapter.GetItemCount(); - index = index < 0 ? _adapter.GetItemCount() + index : index; + UpdateCurrentIndex(index); + } + + #endregion + + #region Private Methods - Scroll Callbacks + + private void OnScrollChanged(float position) + { + layoutManager.UpdateLayout(); + UpdateScrollbarValue(position); + UpdateVisibleRange(); + layoutManager.DoItemAnimation(); + OnScrollValueChanged?.Invoke(); + } + + private void OnMoveStoped() + { + if (snap) + { + SnapToNearestItem(); + } + } + + private void OnScrollbarChanged(float ratio) + { + if (scroller != null) + { + scroller.ScrollToRatio(ratio); + } + } + + private void OnScrollbarDragEnd() + { + if (scroller == null) return; + + if (scroller.Position < scroller.MaxPosition && snap) + { + SnapToNearestItem(); + } + } + + #endregion + + #region Private Methods - Scroll Helpers + + private void UpdateScrollbarValue(float position) + { + if (scrollbar != null && scroller != null) + { + float ratio = scroller.MaxPosition > 0 ? position / scroller.MaxPosition : 0; + scrollbar.SetValueWithoutNotify(ratio); + } + } + + private void UpdateVisibleRange() + { + // Handle start index + if (layoutManager.IsFullInvisibleStart(startIndex)) + { + viewProvider.RemoveViewHolder(startIndex); + startIndex += layoutManager.Unit; + } + else if (layoutManager.IsFullVisibleStart(startIndex)) + { + if (startIndex == 0) + { + // TODO: Implement refresh logic + } + else + { + startIndex -= layoutManager.Unit; + viewProvider.CreateViewHolder(startIndex); + } + } + + // Handle end index + if (layoutManager.IsFullInvisibleEnd(endIndex)) + { + viewProvider.RemoveViewHolder(endIndex); + endIndex -= layoutManager.Unit; + } + else if (layoutManager.IsFullVisibleEnd(endIndex)) + { + if (endIndex >= viewProvider.GetItemCount() - layoutManager.Unit) + { + // TODO: Implement load more logic + } + else + { + endIndex += layoutManager.Unit; + viewProvider.CreateViewHolder(endIndex); + } + } + + // Refresh if out of visible range + if (!layoutManager.IsVisible(startIndex) || !layoutManager.IsVisible(endIndex)) + { + Refresh(); + } + } + + private void UpdateScrollbarVisibility() + { + if (scrollbar == null || scroller == null || layoutManager.ContentSize == Vector2.zero) + return; + + bool shouldShow = ShouldShowScrollbar(); + scrollbar.gameObject.SetActive(shouldShow); + + if (shouldShow) + { + ConfigureScrollbarDirection(); + ConfigureScrollbarSize(); + } + } + + private bool ShouldShowScrollbar() + { + if (direction == Direction.Custom) return false; + + if (direction == Direction.Vertical) + { + return layoutManager.ContentSize.y > layoutManager.ViewportSize.y; + } + else // Horizontal + { + return layoutManager.ContentSize.x > layoutManager.ViewportSize.x; + } + } + + private void ConfigureScrollbarDirection() + { + scrollbar.direction = direction == Direction.Vertical + ? Scrollbar.Direction.TopToBottom + : Scrollbar.Direction.LeftToRight; + } + + private void ConfigureScrollbarSize() + { + if (direction == Direction.Vertical) + { + scrollbar.size = scroller.ViewSize.y / scroller.ContentSize.y; + } + else + { + scrollbar.size = scroller.ViewSize.x / scroller.ContentSize.x; + } + } + + private void SnapToNearestItem() + { + int index = layoutManager.PositionToIndex(GetScrollPosition()); + ScrollTo(index, true); + } + + private void UpdateCurrentIndex(int index) + { + if (RecyclerViewAdapter == null) return; + + int itemCount = RecyclerViewAdapter.GetItemCount(); + index %= itemCount; + index = index < 0 ? itemCount + index : index; if (currentIndex != index) { @@ -368,11 +546,6 @@ namespace AlicizaX.UI } } - - private void SnapTo() - { - var index = _layoutManager.PositionToIndex(GetScrollPosition()); - ScrollTo(index, true); - } + #endregion } } diff --git a/Runtime/RecyclerView/RecyclerView.cs.meta b/Runtime/RecyclerView/RecyclerView.cs.meta index 6bbf25a..a7f6e1c 100644 --- a/Runtime/RecyclerView/RecyclerView.cs.meta +++ b/Runtime/RecyclerView/RecyclerView.cs.meta @@ -5,7 +5,7 @@ MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 - icon: {fileID: 2800000, guid: a01ce47da31a2e0438fd8d38b203c0d5, type: 3} + icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: diff --git a/Runtime/RecyclerView/Scroller/CircleScroller.cs b/Runtime/RecyclerView/Scroller/CircleScroller.cs index c3de224..818698b 100644 --- a/Runtime/RecyclerView/Scroller/CircleScroller.cs +++ b/Runtime/RecyclerView/Scroller/CircleScroller.cs @@ -1,10 +1,8 @@ -using System; using UnityEngine; using UnityEngine.EventSystems; namespace AlicizaX.UI { - [Serializable] public class CircleScroller : Scroller { private Vector2 centerPosition; diff --git a/Runtime/RecyclerView/Scroller/IScroller.cs b/Runtime/RecyclerView/Scroller/IScroller.cs index 0534c22..fdbbd05 100644 --- a/Runtime/RecyclerView/Scroller/IScroller.cs +++ b/Runtime/RecyclerView/Scroller/IScroller.cs @@ -9,19 +9,7 @@ namespace AlicizaX.UI void ScrollTo(float position, bool smooth = false); } - public class ScrollerEvent : UnityEvent - { - } - - public class MoveStopEvent : UnityEvent - { - } - - public class DraggingEvent : UnityEvent - { - } - - public class MoveingEvent : UnityEvent - { - } + public class ScrollerEvent : UnityEvent { } + public class MoveStopEvent : UnityEvent { } + public class DraggingEvent : UnityEvent { } } diff --git a/Runtime/RecyclerView/Scroller/Scroller.cs b/Runtime/RecyclerView/Scroller/Scroller.cs index b018f07..47d050b 100644 --- a/Runtime/RecyclerView/Scroller/Scroller.cs +++ b/Runtime/RecyclerView/Scroller/Scroller.cs @@ -1,26 +1,18 @@ -using System; using System.Collections; using UnityEngine; using UnityEngine.EventSystems; namespace AlicizaX.UI { - [Serializable] public class Scroller : MonoBehaviour, IScroller, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler { protected float position; - - public float Position - { - get => position; - set => position = value; - } + public float Position { get => position; set => position = value; } protected float velocity; public float Velocity => velocity; protected Direction direction; - public Direction Direction { get => direction; @@ -31,7 +23,6 @@ namespace AlicizaX.UI /// 内容所需要大小 /// protected Vector2 contentSize; - public Vector2 ContentSize { get => contentSize; @@ -42,7 +33,6 @@ namespace AlicizaX.UI /// 所在 View 的真实大小 /// protected Vector2 viewSize; - public Vector2 ViewSize { get => viewSize; @@ -50,7 +40,6 @@ namespace AlicizaX.UI } protected float scrollSpeed = 1f; - public float ScrollSpeed { get => scrollSpeed; @@ -58,7 +47,6 @@ namespace AlicizaX.UI } protected float wheelSpeed = 30f; - public float WheelSpeed { get => wheelSpeed; @@ -66,7 +54,6 @@ namespace AlicizaX.UI } protected bool snap; - public bool Snap { get => snap; @@ -76,38 +63,21 @@ namespace AlicizaX.UI protected ScrollerEvent scrollerEvent = new(); protected MoveStopEvent moveStopEvent = new(); protected DraggingEvent draggingEvent = new(); - protected MoveingEvent moveingEvent = new(); - public float MaxPosition => direction == Direction.Vertical ? Mathf.Max(contentSize.y - viewSize.y, 0) : Mathf.Max(contentSize.x - viewSize.x, 0); + public float MaxPosition => direction == Direction.Vertical ? + Mathf.Max(contentSize.y - viewSize.y, 0) : + Mathf.Max(contentSize.x - viewSize.x, 0); public float ViewLength => direction == Direction.Vertical ? viewSize.y : viewSize.x; - public MoveingEvent OnMoveing - { - get => moveingEvent; - set => moveingEvent = value; - } + public ScrollerEvent OnValueChanged { get => scrollerEvent; set => scrollerEvent = value; } - public ScrollerEvent OnValueChanged - { - get => scrollerEvent; - set => scrollerEvent = value; - } + public MoveStopEvent OnMoveStoped { get => moveStopEvent; set => moveStopEvent = value; } - public MoveStopEvent OnMoveStoped - { - get => moveStopEvent; - set => moveStopEvent = value; - } - - public DraggingEvent OnDragging - { - get => draggingEvent; - set => draggingEvent = value; - } + public DraggingEvent OnDragging { get => draggingEvent; set => draggingEvent = value; } // 停止滑动的时间,但此时并未释放鼠标按键 - private float dragStopTime = 0f; + public float dragStopTime = 0f; public virtual void ScrollTo(float position, bool smooth = false) { @@ -151,10 +121,9 @@ namespace AlicizaX.UI position += velocity; OnValueChanged?.Invoke(position); - OnMoveing?.Invoke(); } - public virtual void OnScrolled(PointerEventData eventData) + public void OnScroll(PointerEventData eventData) { StopAllCoroutines(); @@ -163,7 +132,7 @@ namespace AlicizaX.UI position += velocity; OnValueChanged?.Invoke(position); - OnMoveing?.Invoke(); + Inertia(); Elastic(); } @@ -184,7 +153,6 @@ namespace AlicizaX.UI { rate = Mathf.Max(0, 1 - (Mathf.Abs(position - MaxPosition) / ViewLength)); } - return rate; } @@ -273,11 +241,5 @@ namespace AlicizaX.UI position = targetPos; OnValueChanged?.Invoke(position); } - - - void IScrollHandler.OnScroll(PointerEventData eventData) - { - OnScrolled(eventData); - } } } diff --git a/Runtime/RecyclerView/UGList/UGListBase.cs b/Runtime/RecyclerView/UGList.cs similarity index 98% rename from Runtime/RecyclerView/UGList/UGListBase.cs rename to Runtime/RecyclerView/UGList.cs index bbb7ab9..cec2fdb 100644 --- a/Runtime/RecyclerView/UGList/UGListBase.cs +++ b/Runtime/RecyclerView/UGList.cs @@ -74,7 +74,7 @@ namespace AlicizaX.UI } } - public static class UGList + public static class UGListCreateHelper { public static UGList Create(RecyclerView recyclerView, Action onItemClick = null) where TData : ISimpleViewData => new UGList(recyclerView, onItemClick); diff --git a/Runtime/RecyclerView/UGList.cs.meta b/Runtime/RecyclerView/UGList.cs.meta new file mode 100644 index 0000000..7cfff98 --- /dev/null +++ b/Runtime/RecyclerView/UGList.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fd094e3cb2194cb193221ea9436a169a +timeCreated: 1766729620 \ No newline at end of file diff --git a/Runtime/RecyclerView/UGList.meta b/Runtime/RecyclerView/UGList.meta deleted file mode 100644 index dd7451a..0000000 --- a/Runtime/RecyclerView/UGList.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 369d2829a7a2462ba93212b3d8afe0fa -timeCreated: 1763615925 \ No newline at end of file diff --git a/Runtime/RecyclerView/UGList/UGListBase.cs.meta b/Runtime/RecyclerView/UGList/UGListBase.cs.meta deleted file mode 100644 index c76971e..0000000 --- a/Runtime/RecyclerView/UGList/UGListBase.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 19892a10438348b3b7e7cac3e2201b9e -timeCreated: 1763615934 \ No newline at end of file diff --git a/Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs b/Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs index 3c433f8..705f065 100644 --- a/Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs +++ b/Runtime/RecyclerView/ViewProvider/MixedViewProvider.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using SimpleObjectPool; using UnityEngine; namespace AlicizaX.UI diff --git a/Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs b/Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs index 5815099..7811a29 100644 --- a/Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs +++ b/Runtime/RecyclerView/ViewProvider/SimpleViewProvider.cs @@ -1,5 +1,4 @@ using System; -using SimpleObjectPool; namespace AlicizaX.UI { diff --git a/Runtime/RecyclerView/ViewProvider/ViewProvider.cs b/Runtime/RecyclerView/ViewProvider/ViewProvider.cs index d734103..b68bfe2 100644 --- a/Runtime/RecyclerView/ViewProvider/ViewProvider.cs +++ b/Runtime/RecyclerView/ViewProvider/ViewProvider.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using AlicizaX.UI.Runtime; using UnityEngine; namespace AlicizaX.UI @@ -8,7 +6,7 @@ namespace AlicizaX.UI /// /// 提供和管理 ViewHolder /// - public abstract class ViewProvider : IDisposable + public abstract class ViewProvider { private readonly List viewHolders = new(); @@ -44,10 +42,10 @@ namespace AlicizaX.UI string viewName = Adapter.GetViewName(i); var viewHolder = Allocate(viewName); - viewHolder.OnStart(); viewHolder.Name = viewName; viewHolder.Index = i; viewHolders.Add(viewHolder); + LayoutManager.Layout(viewHolder, i); Adapter.OnBindViewHolder(viewHolder, i); } @@ -131,15 +129,5 @@ namespace AlicizaX.UI { return Adapter == null ? 0 : Adapter.GetItemCount(); } - - public void Dispose() - { - foreach (var viewHolder in viewHolders) - { - Free(viewHolder.Name, viewHolder); - } - - viewHolders.Clear(); - } } }