using UnityEditor; using UnityEngine; using System; using System.Collections.Generic; using System.Linq; using AlicizaX.UI.RecyclerView; using UnityEngine.UI; using Object = UnityEngine.Object; [CustomEditor(typeof(RecyclerView))] public class RecyclerViewEditor : 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() { // Layout Manager _layoutManagerTypeName = serializedObject.FindProperty("_layoutManagerTypeName"); _layoutManager = serializedObject.FindProperty("_layoutManager"); RefreshLayoutTypes(); // Scroller scroll = serializedObject.FindProperty("scroll"); _scroller = serializedObject.FindProperty("_scroller"); _scrollerTypeName = serializedObject.FindProperty("_scrollerTypeName"); RefreshScrollerTypes(); SyncExistingScroller(); // 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"); } #region Layout Manager void RefreshLayoutTypes() { _layoutTypeNames.Clear(); _layoutTypeNames.Add(NoneOptionName); // 获取所有实现ILayoutManager的非Mono类型 var types = AlicizaX.Utility.Assembly.GetRuntimeTypes(typeof(ILayoutManager)); foreach (var type in types) { if (!typeof(MonoBehaviour).IsAssignableFrom(type)) { _layoutTypeNames.Add(type.FullName); } } _selectedLayoutIndex = Mathf.Clamp( _layoutTypeNames.IndexOf(_layoutManagerTypeName.stringValue), 0, _layoutTypeNames.Count - 1 ); } #endregion #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) { _scrollerTypeName.stringValue = existing.GetType().FullName; _selectedScrollerIndex = _scrollerTypeNames.IndexOf(_scrollerTypeName.stringValue); } else { _selectedScrollerIndex = 0; } } #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"); { 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); } } // 显示布局属性或警告 if (_layoutManager.managedReferenceValue != null) { EditorGUILayout.Space(3); DrawManagedProperties(_layoutManager); } else { EditorGUILayout.HelpBox("Need Choose LayoutManager", MessageType.Error); } } EditorGUILayout.EndVertical(); } void DrawBaseSettings(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 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) { HandleScrollBarToggle(); } } } 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) { UpdateScroller(newIndex); } } var rv = target as RecyclerView; if (rv != null) { var scrollerComponent = rv.GetComponent(); if (scrollerComponent != null) { DrawComponentProperties(scrollerComponent as MonoBehaviour, "Scroller Properties"); } else { EditorGUILayout.HelpBox("Must choose to use a Scroller.", 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); } SerializedObject so = new SerializedObject(component); so.Update(); SerializedProperty prop = so.GetIterator(); bool enterChildren = true; while (prop.NextVisible(enterChildren)) { enterChildren = false; if (prop.name == "m_Script") continue; EditorGUILayout.PropertyField(prop, true); } so.ApplyModifiedProperties(); } void DrawTemplatesSection() { EditorGUILayout.Space(5); EditorGUILayout.PropertyField(templates, new GUIContent("Item Templates"), true); } #endregion #region Update Handlers void UpdateLayoutManager(int selectedIndex) { try { // 强制清空逻辑 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 { var rv = target as RecyclerView; if (rv == null) return; Undo.RecordObjects(new UnityEngine.Object[] { rv, this }, "Update Scroller"); // 移除旧组件 var oldScroller = rv.GetComponent(); if (oldScroller != null) { Undo.DestroyObjectImmediate(oldScroller as MonoBehaviour); } if (selectedIndex == 0) { // 清除序列化引用 _scroller.objectReferenceValue = null; _scrollerTypeName.stringValue = ""; return; } // 添加新组件 Type selectedType = _scrollerTypes[selectedIndex - 1]; var newScroller = Undo.AddComponent(rv.gameObject, selectedType) as IScroller; // 同步到序列化属性 _scroller.objectReferenceValue = newScroller as MonoBehaviour; _scrollerTypeName.stringValue = selectedType.FullName; _selectedScrollerIndex = selectedIndex; // 立即应用修改 serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(rv); // 标记对象需要保存 } catch (Exception e) { Debug.LogError($"Scroller Error: {e}"); _selectedScrollerIndex = 0; _scrollerTypeName.stringValue = ""; _scroller.objectReferenceValue = null; } } void HandleScrollToggle() { var rv = target as RecyclerView; if (rv == null) return; if (!scroll.boolValue) { // Remove scroller component var scrollerComponent = rv.GetComponent(); if (scrollerComponent != null) { Undo.DestroyObjectImmediate(scrollerComponent as MonoBehaviour); } _scrollerTypeName.stringValue = ""; _selectedScrollerIndex = 0; } } void HandleScrollBarToggle() { var rv = target as RecyclerView; if (rv == null) return; if (_showScrollBar.boolValue) { Direction direction = (Direction)this.direction.enumValueIndex; if (direction == Direction.Vertical) { const string path = "Packages/com.alicizax.unity.ui.extension/Editor/RecyclerView/Res/vertical.prefab"; InstantiateScrollBar(path, rv.transform); } else if (direction == Direction.Horizontal) { const string path = "Packages/com.alicizax.unity.ui.extension/Editor/RecyclerView/Res/horizontal.prefab"; InstantiateScrollBar(path, rv.transform); } } else { ClearScrollBar(); } } void ClearScrollBar() { _showScrollBar.boolValue = false; if (_scrollbar.objectReferenceValue != null) { Scrollbar scrollbar = _scrollbar.objectReferenceValue as Scrollbar; _scrollbar.objectReferenceValue = null; Object.DestroyImmediate(scrollbar.gameObject); } } #endregion void DrawManagedProperties(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); } } 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(); } }